From 6d5563a978902b8288fb65eb3d82c466764c8e87 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Sun, 3 May 2020 11:51:51 +0200 Subject: [PATCH] tools: add tool to dump midi messages --- src/tools/meson.build | 12 +++ src/tools/pw-mididump.c | 185 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 src/tools/pw-mididump.c diff --git a/src/tools/meson.build b/src/tools/meson.build index c07be6016..b9ec2d8f8 100644 --- a/src/tools/meson.build +++ b/src/tools/meson.build @@ -31,6 +31,11 @@ if get_option('pw-cat') and sndfile_dep.found() 'midifile.c', ] + pwmididump_sources = [ + 'pw-mididump.c', + 'midifile.c', + ] + pwcat_aliases = [ 'pw-play', 'pw-record', @@ -45,6 +50,13 @@ if get_option('pw-cat') and sndfile_dep.found() dependencies : [sndfile_dep, pipewire_dep, mathlib], ) + executable('pw-mididump', + pwmididump_sources, + c_args : [ '-D_GNU_SOURCE' ], + install: true, + dependencies : [pipewire_dep, mathlib], + ) + foreach alias : pwcat_aliases dst = join_paths(pipewire_bindir, alias) cmd = 'ln -fs @0@ $DESTDIR@1@'.format('pw-cat', dst) diff --git a/src/tools/pw-mididump.c b/src/tools/pw-mididump.c new file mode 100644 index 000000000..17eaa57b7 --- /dev/null +++ b/src/tools/pw-mididump.c @@ -0,0 +1,185 @@ +/* PipeWire + * + * Copyright © 2020 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "midifile.h" + +struct data; + +struct port { + struct data *data; +}; + +struct data { + struct pw_main_loop *loop; + struct pw_filter *filter; + struct port *in_port; + int64_t clock_time; +}; + +static int dump_file(const char *filename) +{ + struct midi_file *file; + struct midi_file_info info; + struct midi_event ev; + + file = midi_file_open(filename, "r", &info); + if (file == NULL) { + fprintf(stderr, "error opening %s: %m\n", filename); + return -1; + } + + printf("opened %s\n", filename); + + while (midi_file_read_event(file, &ev) == 1) { + midi_file_dump_event(stdout, &ev); + } + midi_file_close(file); + + return 0; +} + +static void on_process(void *userdata, struct spa_io_position *position) +{ + struct data *data = userdata; + struct pw_buffer *b; + struct spa_buffer *buf; + struct spa_data *d; + struct spa_pod *pod; + struct spa_pod_control *c; + uint8_t *p; + uint64_t frame; + + frame = data->clock_time; + data->clock_time += position->clock.duration; + + b = pw_filter_dequeue_buffer(data->in_port); + if (b == NULL) + return; + + buf = b->buffer; + d = &buf->datas[0]; + + if ((p = d->data) == NULL) + return; + + if ((pod = spa_pod_from_data(d->data, d->maxsize, d->chunk->offset, d->chunk->size)) == NULL) + return; + if (!spa_pod_is_sequence(pod)) + return; + + SPA_POD_SEQUENCE_FOREACH((struct spa_pod_sequence*)pod, c) { + struct midi_event ev; + + if (c->type != SPA_CONTROL_Midi) + continue; + + ev.track = 0; + ev.sec = (frame + c->offset) / (float) position->clock.rate.denom; + ev.data = SPA_POD_BODY(&c->value), + ev.size = SPA_POD_BODY_SIZE(&c->value); + + midi_file_dump_event(stdout, &ev); + } + + pw_filter_queue_buffer(data->in_port, b); +} + +static const struct pw_filter_events filter_events = { + PW_VERSION_FILTER_EVENTS, + .process = on_process, +}; + +static void do_quit(void *userdata, int signal_number) +{ + struct data *data = userdata; + pw_main_loop_quit(data->loop); +} + +static int dump_filter(struct data *data) +{ + 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); + + data->filter = pw_filter_new_simple( + pw_main_loop_get_loop(data->loop), + "midi-dump", + pw_properties_new( + PW_KEY_MEDIA_TYPE, "Midi", + PW_KEY_MEDIA_CATEGORY, "Filter", + PW_KEY_MEDIA_ROLE, "DSP", + NULL), + &filter_events, + data); + + data->in_port = pw_filter_add_port(data->filter, + PW_DIRECTION_INPUT, + PW_FILTER_PORT_FLAG_MAP_BUFFERS, + sizeof(struct port), + pw_properties_new( + PW_KEY_FORMAT_DSP, "8 bit raw midi", + PW_KEY_PORT_NAME, "input", + NULL), + NULL, 0); + + if (pw_filter_connect(data->filter, PW_FILTER_FLAG_RT_PROCESS, NULL, 0) < 0) { + fprintf(stderr, "can't connect\n"); + return -1; + } + + pw_main_loop_run(data->loop); + + pw_filter_destroy(data->filter); + pw_main_loop_destroy(data->loop); + + return 0; +} + +int main(int argc, char *argv[]) +{ + struct data data = { 0, }; + int res = 0; + + pw_init(&argc, &argv); + + if (argc > 1) { + res = dump_file(argv[1]); + } else { + res = dump_filter(&data); + } + return res; +}