mirror of
				https://gitlab.freedesktop.org/pipewire/pipewire.git
				synced 2025-11-03 09:01:54 -05:00 
			
		
		
		
	examples: add MIDI example
There wasn't a MIDI example, so add one.
This commit is contained in:
		
							parent
							
								
									75eec2ae85
								
							
						
					
					
						commit
						69d69ce757
					
				
					 2 changed files with 265 additions and 0 deletions
				
			
		| 
						 | 
					@ -14,6 +14,7 @@ examples = [
 | 
				
			||||||
  'video-src-reneg',
 | 
					  'video-src-reneg',
 | 
				
			||||||
  'video-src-fixate',
 | 
					  'video-src-fixate',
 | 
				
			||||||
  'video-play-fixate',
 | 
					  'video-play-fixate',
 | 
				
			||||||
 | 
					  'midi-src',
 | 
				
			||||||
  'internal',
 | 
					  'internal',
 | 
				
			||||||
  'export-sink',
 | 
					  'export-sink',
 | 
				
			||||||
  'export-source',
 | 
					  'export-source',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										264
									
								
								src/examples/midi-src.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										264
									
								
								src/examples/midi-src.c
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,264 @@
 | 
				
			||||||
 | 
					/* PipeWire */
 | 
				
			||||||
 | 
					/* SPDX-FileCopyrightText: Copyright © 2024 Pauli Virtanen */
 | 
				
			||||||
 | 
					/* SPDX-License-Identifier: MIT */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 [title]
 | 
				
			||||||
 | 
					 MIDI source using \ref pw_filter "pw_filter".
 | 
				
			||||||
 | 
					 [title]
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <stdio.h>
 | 
				
			||||||
 | 
					#include <errno.h>
 | 
				
			||||||
 | 
					#include <math.h>
 | 
				
			||||||
 | 
					#include <signal.h>
 | 
				
			||||||
 | 
					#include <getopt.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <pipewire/pipewire.h>
 | 
				
			||||||
 | 
					#include <pipewire/filter.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <spa/pod/builder.h>
 | 
				
			||||||
 | 
					#include <spa/control/control.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define PERIOD_NSEC	(SPA_NSEC_PER_SEC/8)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct port {
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct data {
 | 
				
			||||||
 | 
						struct pw_main_loop *loop;
 | 
				
			||||||
 | 
						struct pw_filter *filter;
 | 
				
			||||||
 | 
						struct port *port;
 | 
				
			||||||
 | 
						uint32_t clock_id;
 | 
				
			||||||
 | 
						int64_t offset;
 | 
				
			||||||
 | 
						uint64_t position;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void on_process(void *userdata, struct spa_io_position *position)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct data *data = userdata;
 | 
				
			||||||
 | 
						struct port *port = data->port;
 | 
				
			||||||
 | 
						struct pw_buffer *buf;
 | 
				
			||||||
 | 
						struct spa_data *d;
 | 
				
			||||||
 | 
						struct spa_pod_builder builder;
 | 
				
			||||||
 | 
						struct spa_pod_frame frame;
 | 
				
			||||||
 | 
						uint64_t sample_offset, sample_period, sample_position, cycle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Use the clock sample position.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * If the playback switches to using a different clock, we reset
 | 
				
			||||||
 | 
						 * playback as the sample position can then be discontinuous.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						if (data->clock_id != position->clock.id) {
 | 
				
			||||||
 | 
							pw_log_info("switch to clock %u", position->clock.id);
 | 
				
			||||||
 | 
							data->offset = position->clock.position - data->position;
 | 
				
			||||||
 | 
							data->clock_id = position->clock.id;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sample_position = position->clock.position - data->offset;
 | 
				
			||||||
 | 
						data->position = sample_position + position->clock.duration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Produce note on/off every `PERIOD_NSEC` nanoseconds (rounded down to
 | 
				
			||||||
 | 
						 * samples, for simplicity).
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * We want to place the notes on the playback timeline, so we use sample
 | 
				
			||||||
 | 
						 * positions (not real time!).
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sample_period = PERIOD_NSEC * position->clock.rate.denom
 | 
				
			||||||
 | 
							/ position->clock.rate.num / SPA_NSEC_PER_SEC;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cycle = sample_position / sample_period;
 | 
				
			||||||
 | 
						if (sample_position % sample_period != 0)
 | 
				
			||||||
 | 
							++cycle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sample_offset = cycle*sample_period - sample_position;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (sample_offset >= position->clock.duration)
 | 
				
			||||||
 | 
							return;  /* don't need to produce anything yet */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Get output buffer */
 | 
				
			||||||
 | 
						if ((buf = pw_filter_dequeue_buffer(port)) == NULL)
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Midi buffers always have exactly one data block */
 | 
				
			||||||
 | 
						spa_assert(buf->buffer->n_datas == 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						d = &buf->buffer->datas[0];
 | 
				
			||||||
 | 
						d->chunk->offset = 0;
 | 
				
			||||||
 | 
						d->chunk->size = 0;
 | 
				
			||||||
 | 
						d->chunk->stride = 1;
 | 
				
			||||||
 | 
						d->chunk->flags = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * MIDI buffers contain a SPA POD with a sequence of
 | 
				
			||||||
 | 
						 * control messages and their raw MIDI data.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						spa_pod_builder_init(&builder, d->data, d->maxsize);
 | 
				
			||||||
 | 
						spa_pod_builder_push_sequence(&builder, &frame, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						while (sample_offset < position->clock.duration) {
 | 
				
			||||||
 | 
							if (cycle % 2 == 0) {
 | 
				
			||||||
 | 
								/* MIDI note on, channel 0, middle C, max velocity */
 | 
				
			||||||
 | 
								uint8_t buf[] = { 0x90, 0x3c, 0x7f };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								/* The time position of the message in the graph cycle
 | 
				
			||||||
 | 
								 * is given as offset from the cycle start, in
 | 
				
			||||||
 | 
								 * samples. The cycle has duration of `clock.duration`
 | 
				
			||||||
 | 
								 * samples, and the sample offset should satisfy
 | 
				
			||||||
 | 
								 * 0 <= sample_offset < position->clock.duration.
 | 
				
			||||||
 | 
								 */
 | 
				
			||||||
 | 
								spa_pod_builder_control(&builder, sample_offset, SPA_CONTROL_Midi);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								/* Raw MIDI data for the message */
 | 
				
			||||||
 | 
								spa_pod_builder_bytes(&builder, buf, sizeof(buf));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								pw_log_info("note on at %"PRIu64, sample_position + sample_offset);
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								/* MIDI note off, channel 0, middle C, max velocity */
 | 
				
			||||||
 | 
								uint8_t buf[] = { 0x80, 0x3c, 0x7f };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								spa_pod_builder_control(&builder, sample_offset, SPA_CONTROL_Midi);
 | 
				
			||||||
 | 
								spa_pod_builder_bytes(&builder, buf, sizeof(buf));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								pw_log_info("note off at %"PRIu64, sample_position + sample_offset);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							sample_offset += sample_period;
 | 
				
			||||||
 | 
							++cycle;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Finish the sequence and queue buffer to output.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
					        spa_pod_builder_pop(&builder, &frame);
 | 
				
			||||||
 | 
					        d->chunk->size = builder.state.offset;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pw_log_trace("produced %u/%u bytes", d->chunk->size, d->maxsize);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pw_filter_queue_buffer(port, buf);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void state_changed(void *userdata, enum pw_filter_state old,
 | 
				
			||||||
 | 
							enum pw_filter_state state, const char *error)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct data *data = userdata;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch (state) {
 | 
				
			||||||
 | 
						case PW_FILTER_STATE_STREAMING:
 | 
				
			||||||
 | 
							/* reset playback position */
 | 
				
			||||||
 | 
							pw_log_info("start playback");
 | 
				
			||||||
 | 
							data->clock_id = SPA_ID_INVALID;
 | 
				
			||||||
 | 
							data->offset = 0;
 | 
				
			||||||
 | 
							data->position = 0;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct pw_filter_events filter_events = {
 | 
				
			||||||
 | 
						PW_VERSION_FILTER_EVENTS,
 | 
				
			||||||
 | 
						.process = on_process,
 | 
				
			||||||
 | 
						.state_changed = state_changed,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void do_quit(void *userdata, int signal_number)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct data *data = userdata;
 | 
				
			||||||
 | 
						pw_main_loop_quit(data->loop);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int main(int argc, char *argv[])
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct data data = {};
 | 
				
			||||||
 | 
						uint8_t buffer[1024];
 | 
				
			||||||
 | 
						struct spa_pod_builder builder;
 | 
				
			||||||
 | 
						struct spa_pod *params[1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pw_init(&argc, &argv);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* make a main loop. If you already have another main loop, you can add
 | 
				
			||||||
 | 
						 * the fd of this pipewire mainloop to it. */
 | 
				
			||||||
 | 
						data.loop = pw_main_loop_new(NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data);
 | 
				
			||||||
 | 
						pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Create a simple filter, the simple filter manages the core and remote
 | 
				
			||||||
 | 
						 * objects for you if you don't need to deal with them.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * Pass your events and a user_data pointer as the last arguments. This
 | 
				
			||||||
 | 
						 * will inform you about the filter state. The most important event
 | 
				
			||||||
 | 
						 * you need to listen to is the process event where you need to process
 | 
				
			||||||
 | 
						 * the data.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						data.filter = pw_filter_new_simple(
 | 
				
			||||||
 | 
								pw_main_loop_get_loop(data.loop),
 | 
				
			||||||
 | 
								"midi-src",
 | 
				
			||||||
 | 
								pw_properties_new(
 | 
				
			||||||
 | 
									PW_KEY_MEDIA_TYPE, "Midi",
 | 
				
			||||||
 | 
									PW_KEY_MEDIA_CATEGORY, "Playback",
 | 
				
			||||||
 | 
									PW_KEY_MEDIA_CLASS, "Midi/Source",
 | 
				
			||||||
 | 
									NULL),
 | 
				
			||||||
 | 
								&filter_events,
 | 
				
			||||||
 | 
								&data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Make a midi output port */
 | 
				
			||||||
 | 
						data.port = pw_filter_add_port(data.filter,
 | 
				
			||||||
 | 
								PW_DIRECTION_OUTPUT,
 | 
				
			||||||
 | 
								PW_FILTER_PORT_FLAG_MAP_BUFFERS,
 | 
				
			||||||
 | 
								sizeof(struct port),
 | 
				
			||||||
 | 
								pw_properties_new(
 | 
				
			||||||
 | 
									PW_KEY_FORMAT_DSP, "8 bit raw midi",
 | 
				
			||||||
 | 
									PW_KEY_PORT_NAME, "output",
 | 
				
			||||||
 | 
									NULL),
 | 
				
			||||||
 | 
								NULL, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Update SPA_PARAM_Buffers to request a specific sizes and counts.
 | 
				
			||||||
 | 
						 * This is not mandatory: if you skip this, you'll get default sized
 | 
				
			||||||
 | 
						 * buffers, usually 4k or 32k bytes or so.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * We'll here ask for 4096 bytes as that's enough.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						spa_pod_builder_init(&builder, buffer, sizeof(buffer));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						params[0] = spa_pod_builder_add_object(&builder,
 | 
				
			||||||
 | 
								/* POD Object for the buffer parameter */
 | 
				
			||||||
 | 
								SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
 | 
				
			||||||
 | 
								/* Default 1 buffer, minimum of 1, max of 32 buffers.
 | 
				
			||||||
 | 
								 * We can do with 1 buffer as we dequeue and queue in the same
 | 
				
			||||||
 | 
								 * cycle.
 | 
				
			||||||
 | 
								 */
 | 
				
			||||||
 | 
								SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, 32),
 | 
				
			||||||
 | 
								/* MIDI buffers always have 1 data block */
 | 
				
			||||||
 | 
								SPA_PARAM_BUFFERS_blocks,  SPA_POD_Int(1),
 | 
				
			||||||
 | 
								/* Buffer size: request default 4096 bytes, min 4096, no maximum */
 | 
				
			||||||
 | 
								SPA_PARAM_BUFFERS_size,    SPA_POD_CHOICE_RANGE_Int(4096, 4096, INT32_MAX),
 | 
				
			||||||
 | 
								/* MIDI buffers have stride 1 */
 | 
				
			||||||
 | 
								SPA_PARAM_BUFFERS_stride,  SPA_POD_Int(1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pw_filter_update_params(data.filter, data.port,
 | 
				
			||||||
 | 
								(const struct spa_pod **)params, SPA_N_ELEMENTS(params));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Now connect this filter. We ask that our process function is
 | 
				
			||||||
 | 
						 * called in a realtime thread. */
 | 
				
			||||||
 | 
						if (pw_filter_connect(data.filter,
 | 
				
			||||||
 | 
									PW_FILTER_FLAG_RT_PROCESS,
 | 
				
			||||||
 | 
									NULL, 0) < 0) {
 | 
				
			||||||
 | 
							fprintf(stderr, "can't connect\n");
 | 
				
			||||||
 | 
							return -1;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* and wait while we let things run */
 | 
				
			||||||
 | 
						pw_main_loop_run(data.loop);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pw_filter_destroy(data.filter);
 | 
				
			||||||
 | 
						pw_main_loop_destroy(data.loop);
 | 
				
			||||||
 | 
						pw_deinit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue