From 737ca4ee7befd47130d9b8181a231fdaa0d69ed4 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 23 May 2025 16:49:17 +0200 Subject: [PATCH] tools: add sysex play option in pw-cat This makes it possible to upload raw midi (sysex) into the graph. --- src/tools/meson.build | 1 + src/tools/pw-cat.c | 62 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/tools/meson.build b/src/tools/meson.build index 8fbc8e210..103b48adc 100644 --- a/src/tools/meson.build +++ b/src/tools/meson.build @@ -59,6 +59,7 @@ if get_option('pw-cat').allowed() and sndfile_dep.found() 'pw-midirecord', 'pw-dsdplay', 'pw-encplay', + 'pw-sysex', ] pw_cat = executable('pw-cat', diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index 928afed0e..5fdb9aa93 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -102,6 +102,7 @@ struct data { #define TYPE_DSD 2 #ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION #define TYPE_ENCODED 3 +#define TYPE_SYSEX 4 #endif int data_type; bool raw; @@ -162,6 +163,9 @@ struct data { int64_t accumulated_excess_playtime; } encoded; #endif + struct { + FILE *file; + } sysex; }; #define STR_FMTS "(ulaw|alaw|u8|s8|s16|s32|f32|f64)" @@ -942,6 +946,7 @@ static const struct option long_options[] = { { "midi", no_argument, NULL, 'm' }, { "dsd", no_argument, NULL, 'd' }, { "encoded", no_argument, NULL, 'o' }, + { "sysex", no_argument, NULL, 's' }, { "remote", required_argument, NULL, 'R' }, @@ -1020,6 +1025,7 @@ static void show_usage(const char *name, bool is_error) #ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION " -o, --encoded Encoded mode\n" #endif + " -s, --sysex SysEx mode\n" "\n"), fp); } } @@ -1163,6 +1169,47 @@ static int setup_midifile(struct data *data) return 0; } +static int sysex_play(struct data *d, void *dst, unsigned int n_frames, bool *null_frame) +{ + struct spa_pod_builder b; + struct spa_pod_frame f; + size_t size, to_read = n_frames - 64; + uint8_t bytes[to_read]; + + spa_zero(b); + spa_pod_builder_init(&b, dst, n_frames); + + spa_pod_builder_push_sequence(&b, &f, 0); + spa_pod_builder_control(&b, 0, SPA_CONTROL_Midi); + + size = fread(bytes, 1, to_read, d->sysex.file); + + spa_pod_builder_bytes(&b, bytes, size); + spa_pod_builder_pop(&b, &f); + + return b.state.offset; +} + +static int setup_sysex(struct data *data) +{ + if (data->mode == mode_record) + return -ENOTSUP; + + data->sysex.file = fopen(data->filename, "r"); + if (data->sysex.file == NULL) { + fprintf(stderr, "sysex: can't read file '%s': %m\n", data->filename); + return -errno; + } + + if (data->verbose) + fprintf(stderr, "sysex: opened file \"%s\"\n", data->filename); + + data->fill = sysex_play; + data->stride = 1; + + return 0; +} + struct dsd_layout_info { uint32_t type; struct spa_audio_layout_info info; @@ -1672,6 +1719,9 @@ int main(int argc, char *argv[]) } else if (spa_streq(prog, "pw-midirecord")) { data.mode = mode_record; data.data_type = TYPE_MIDI; + } else if (spa_streq(prog, "pw-sysex")) { + data.mode = mode_playback; + data.data_type = TYPE_SYSEX; } else if (spa_streq(prog, "pw-dsdplay")) { data.mode = mode_playback; data.data_type = TYPE_DSD; @@ -1697,9 +1747,9 @@ int main(int argc, char *argv[]) } #ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION - while ((c = getopt_long(argc, argv, "hvprmdoR:q:P:a", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hvprmdosR:q:P:a", long_options, NULL)) != -1) { #else - while ((c = getopt_long(argc, argv, "hvprmdR:q:P:a", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hvprmdsR:q:P:a", long_options, NULL)) != -1) { #endif switch (c) { @@ -1742,6 +1792,9 @@ int main(int argc, char *argv[]) data.data_type = TYPE_ENCODED; break; #endif + case 's': + data.data_type = TYPE_SYSEX; + break; case 'R': data.remote_name = optarg; @@ -1831,6 +1884,7 @@ int main(int argc, char *argv[]) if (!data.media_type) { switch (data.data_type) { case TYPE_MIDI: + case TYPE_SYSEX: data.media_type = DEFAULT_MIDI_MEDIA_TYPE; break; default: @@ -1918,6 +1972,9 @@ int main(int argc, char *argv[]) ret = setup_encodedfile(&data); break; #endif + case TYPE_SYSEX: + ret = setup_sysex(&data); + break; default: ret = -ENOTSUP; break; @@ -1980,6 +2037,7 @@ int main(int argc, char *argv[]) break; } case TYPE_MIDI: + case TYPE_SYSEX: params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application),