diff --git a/spa/include/spa/control/ump-utils.h b/spa/include/spa/control/ump-utils.h index 582c44e2d..dc40ac6ac 100644 --- a/spa/include/spa/control/ump-utils.h +++ b/spa/include/spa/control/ump-utils.h @@ -148,7 +148,7 @@ SPA_API_CONTROL_UMP_UTILS int spa_ump_from_midi(uint8_t **midi, size_t *midi_siz { int size = 0; uint32_t i, prefix = group << 24, to_consume = 0, bytes; - uint8_t status, *m = (*midi), end; + uint8_t status, *m = (*midi), end, prev_state; if (*midi_size < 1) return 0; @@ -156,6 +156,7 @@ SPA_API_CONTROL_UMP_UTILS int spa_ump_from_midi(uint8_t **midi, size_t *midi_siz return -ENOSPC; status = m[0]; + prev_state = *state & 0x3; /* SysEx */ if (*state == 0) { @@ -184,7 +185,11 @@ SPA_API_CONTROL_UMP_UTILS int spa_ump_from_midi(uint8_t **midi, size_t *midi_siz to_consume += bytes; if (end == 0xf7) { - if (*state == 2) { + if (*state == 1) { + /* started and done in one packet */ + prefix |= 0x0 << 20; + *state = 0; + } else { /* continue and done */ prefix |= 0x3 << 20; *state = 0; @@ -205,6 +210,17 @@ SPA_API_CONTROL_UMP_UTILS int spa_ump_from_midi(uint8_t **midi, size_t *midi_siz * ump[1] |= (m[2] & 0x7f) << 24 * ... */ ump[(i+2)/4] |= (m[i] & 0x7f) << ((5-i)%4 * 8); + } else if (status == 0xf7) { + *state = 0; + if (prev_state == 1) { + prefix |= 0x0 << 20; + ump[size++] = prefix; + ump[size++] = 0; + } else if (prev_state == 2) { + prefix |= 0x3 << 20; + ump[size++] = prefix; + ump[size++] = 0; + } } } else { /* regular messages */ diff --git a/spa/tests/meson.build b/spa/tests/meson.build index 81635dea9..7e0fdd77a 100644 --- a/spa/tests/meson.build +++ b/spa/tests/meson.build @@ -27,6 +27,35 @@ if find.found() endforeach endif +test_apps = [ + ['test-ump-utils', []], +] + +foreach a : test_apps + test('spa-' + a[0], + executable('spa-' + a[0], a[0] + '.c', + dependencies : [ spa_dep ], + include_directories : [configinc], + install : installed_tests_enabled, + install_dir : installed_tests_execdir, + ), + env : [ + 'SPA_PLUGIN_DIR=@0@'.format(spa_dep.get_variable('plugindir')), + ] + ) + + if installed_tests_enabled + test_conf = configuration_data() + test_conf.set('exec', installed_tests_execdir / 'spa-' + a[0]) + configure_file( + input: installed_tests_template, + output: 'spa-' + a[0] + '.test', + install_dir: installed_tests_metadir, + configuration: test_conf, + ) + endif +endforeach + benchmark_apps = [ ['stress-ringbuffer', []], ['benchmark-pod', []], diff --git a/spa/tests/test-ump-utils.c b/spa/tests/test-ump-utils.c new file mode 100644 index 000000000..b9b6391ba --- /dev/null +++ b/spa/tests/test-ump-utils.c @@ -0,0 +1,572 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#include +#include + +#include + +#define spa_assert(cond) \ + do { \ + if (SPA_UNLIKELY(!(cond))) { \ + fprintf(stderr, "FAIL: %s at %s:%d\n", \ + #cond, __FILE__, __LINE__); \ + abort(); \ + } \ + } while (0) + +/* spa_ump_from_midi returns bytes (size * 4) */ +/* spa_ump_to_midi returns number of MIDI bytes written */ + +static void test_ump_from_midi_note_on(void) +{ + uint8_t midi[] = { 0x90, 0x3c, 0x7f }; + uint8_t *p = midi; + size_t midi_size = sizeof(midi); + uint32_t ump[4] = {0}; + uint64_t state = 0; + + int size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 4); + spa_assert(midi_size == 0); + spa_assert(ump[0] == 0x20903c7f); +} + +static void test_ump_from_midi_program_change(void) +{ + uint8_t midi[] = { 0xc0, 0x05 }; + uint8_t *p = midi; + size_t midi_size = sizeof(midi); + uint32_t ump[4] = {0}; + uint64_t state = 0; + + int size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 4); + spa_assert(midi_size == 0); + spa_assert(ump[0] == 0x20c00500); +} + +static void test_ump_from_midi_sysex_complete(void) +{ + /* Short sysex that fits in one UMP packet: F0 01 02 03 F7 */ + uint8_t midi[] = { 0xf0, 0x01, 0x02, 0x03, 0xf7 }; + uint8_t *p = midi; + size_t midi_size = sizeof(midi); + uint32_t ump[4] = {0}; + uint64_t state = 0; + + int size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 8); + spa_assert(midi_size == 0); + /* message type 0x3, status 0x0 (complete), 3 bytes */ + spa_assert((ump[0] >> 20) == 0x300); + spa_assert(((ump[0] >> 16) & 0xf) == 3); + spa_assert(state == 0); +} + +static void test_ump_from_midi_sysex_complete_max(void) +{ + /* Sysex with exactly 6 data bytes: F0 01 02 03 04 05 06 F7 */ + uint8_t midi[] = { 0xf0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0xf7 }; + uint8_t *p = midi; + size_t midi_size = sizeof(midi); + uint32_t ump[4] = {0}; + uint64_t state = 0; + + int size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 8); + spa_assert(midi_size == 0); + /* status 0x0 (complete), 6 bytes */ + spa_assert((ump[0] >> 20) == 0x300); + spa_assert(((ump[0] >> 16) & 0xf) == 6); + spa_assert(state == 0); +} + +static void test_ump_from_midi_sysex_multi_packet(void) +{ + /* Sysex longer than 6 data bytes: F0 01 02 03 04 05 06 07 08 F7 */ + uint8_t midi[] = { 0xf0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xf7 }; + uint8_t *p = midi; + size_t midi_size = sizeof(midi); + uint32_t ump[4]; + uint64_t state = 0; + int size; + + /* First call: Start, 6 data bytes */ + memset(ump, 0, sizeof(ump)); + size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 8); + /* status 0x1 (start), 6 bytes */ + spa_assert(((ump[0] >> 20) & 0xf) == 0x1); + spa_assert(((ump[0] >> 16) & 0xf) == 6); + spa_assert(state == 2); + + /* Second call: End, 2 data bytes */ + memset(ump, 0, sizeof(ump)); + size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 8); + /* status 0x3 (end) */ + spa_assert(((ump[0] >> 20) & 0xf) == 0x3); + spa_assert(((ump[0] >> 16) & 0xf) == 2); + spa_assert(state == 0); + spa_assert(midi_size == 0); +} + +static void test_ump_from_midi_sysex_continue(void) +{ + /* Sysex with 13 data bytes needs start + continue + end */ + uint8_t midi[] = { 0xf0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, + 0x0d, 0xf7 }; + uint8_t *p = midi; + size_t midi_size = sizeof(midi); + uint32_t ump[4]; + uint64_t state = 0; + int size; + + /* First: Start, 6 bytes */ + memset(ump, 0, sizeof(ump)); + size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 8); + spa_assert(((ump[0] >> 20) & 0xf) == 0x1); + spa_assert(((ump[0] >> 16) & 0xf) == 6); + spa_assert(state == 2); + + /* Second: Continue, 6 bytes */ + memset(ump, 0, sizeof(ump)); + size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 8); + spa_assert(((ump[0] >> 20) & 0xf) == 0x2); + spa_assert(((ump[0] >> 16) & 0xf) == 6); + spa_assert(state == 2); + + /* Third: End, 1 byte */ + memset(ump, 0, sizeof(ump)); + size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 8); + spa_assert(((ump[0] >> 20) & 0xf) == 0x3); + spa_assert(((ump[0] >> 16) & 0xf) == 1); + spa_assert(state == 0); + spa_assert(midi_size == 0); +} + +static void test_ump_from_midi_sysex_minimal(void) +{ + /* Minimal sysex: F0 F7 */ + uint8_t midi[] = { 0xf0, 0xf7 }; + uint8_t *p = midi; + size_t midi_size = sizeof(midi); + uint32_t ump[4] = {0}; + uint64_t state = 0; + + int size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 8); + /* status 0x0 (complete), 0 data bytes */ + spa_assert(((ump[0] >> 20) & 0xf) == 0x0); + spa_assert(((ump[0] >> 16) & 0xf) == 0); + spa_assert(state == 0); + spa_assert(midi_size == 0); +} + +static void test_ump_to_midi_note_on(void) +{ + uint32_t ump[] = { 0x20903c7f }; + const uint32_t *p = ump; + size_t ump_size = sizeof(ump); + uint8_t midi[8]; + uint64_t state = 0; + + int size = spa_ump_to_midi(&p, &ump_size, midi, sizeof(midi), &state); + spa_assert(size == 3); + spa_assert(midi[0] == 0x90); + spa_assert(midi[1] == 0x3c); + spa_assert(midi[2] == 0x7f); +} + +static void test_ump_to_midi_sysex_complete(void) +{ + /* Complete sysex: status 0x0, 3 bytes */ + uint32_t ump[] = { 0x30030102, 0x03000000 }; + const uint32_t *p = ump; + size_t ump_size = sizeof(ump); + uint8_t midi[8]; + uint64_t state = 0; + + int size = spa_ump_to_midi(&p, &ump_size, midi, sizeof(midi), &state); + spa_assert(size == 5); + spa_assert(midi[0] == 0xf0); + spa_assert(midi[1] == 0x01); + spa_assert(midi[2] == 0x02); + spa_assert(midi[3] == 0x03); + spa_assert(midi[4] == 0xf7); +} + +static void test_ump_to_midi_sysex_start_end(void) +{ + /* Start: status 0x1, 3 bytes */ + uint32_t ump_start[] = { 0x30130102, 0x03000000 }; + /* End: status 0x3, 2 bytes */ + uint32_t ump_end[] = { 0x30320405, 0x00000000 }; + const uint32_t *p; + size_t ump_size; + uint8_t midi[8]; + uint64_t state = 0; + + p = ump_start; + ump_size = sizeof(ump_start); + int size = spa_ump_to_midi(&p, &ump_size, midi, sizeof(midi), &state); + spa_assert(size == 4); + spa_assert(midi[0] == 0xf0); + spa_assert(midi[1] == 0x01); + spa_assert(midi[2] == 0x02); + spa_assert(midi[3] == 0x03); + + p = ump_end; + ump_size = sizeof(ump_end); + size = spa_ump_to_midi(&p, &ump_size, midi, sizeof(midi), &state); + spa_assert(size == 3); + spa_assert(midi[0] == 0x04); + spa_assert(midi[1] == 0x05); + spa_assert(midi[2] == 0xf7); +} + +static void test_roundtrip_sysex_short(void) +{ + /* Roundtrip: MIDI -> UMP -> MIDI for short sysex */ + uint8_t orig[] = { 0xf0, 0x7e, 0x7f, 0x06, 0x01, 0xf7 }; + uint8_t *p = orig; + size_t midi_size = sizeof(orig); + uint32_t ump[4] = {0}; + uint64_t state = 0; + + int ump_bytes = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(ump_bytes == 8); + spa_assert(midi_size == 0); + spa_assert(state == 0); + + /* Convert back */ + const uint32_t *up = ump; + size_t usize = ump_bytes; + uint8_t result[8]; + state = 0; + + int rsize = spa_ump_to_midi(&up, &usize, result, sizeof(result), &state); + spa_assert(rsize == (int)sizeof(orig)); + spa_assert(memcmp(result, orig, sizeof(orig)) == 0); +} + +static void test_roundtrip_sysex_long(void) +{ + /* Roundtrip: MIDI -> UMP -> MIDI for long sysex (10 data bytes) */ + uint8_t orig[] = { 0xf0, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, 0x0a, 0xf7 }; + uint8_t *p = orig; + size_t midi_size = sizeof(orig); + uint32_t ump[8]; + uint64_t state = 0; + int total_bytes = 0; + + /* Encode all UMP packets */ + while (midi_size > 0) { + memset(&ump[total_bytes / 4], 0, 8); + int s = spa_ump_from_midi(&p, &midi_size, &ump[total_bytes / 4], 16, 0, &state); + spa_assert(s == 8); + total_bytes += s; + } + spa_assert(state == 0); + + /* Decode back */ + const uint32_t *up = ump; + size_t usize = total_bytes; + uint8_t result[32]; + int rsize = 0; + state = 0; + + while (usize > 0) { + int s = spa_ump_to_midi(&up, &usize, &result[rsize], sizeof(result) - rsize, &state); + if (s <= 0) + break; + rsize += s; + } + spa_assert(rsize == (int)sizeof(orig)); + spa_assert(memcmp(result, orig, sizeof(orig)) == 0); +} + +static void test_roundtrip_note_on(void) +{ + uint8_t orig[] = { 0x90, 0x3c, 0x7f }; + uint8_t *p = orig; + size_t midi_size = sizeof(orig); + uint32_t ump[4] = {0}; + uint64_t state = 0; + + int ump_bytes = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(ump_bytes == 4); + + const uint32_t *up = ump; + size_t usize = ump_bytes; + uint8_t result[8]; + state = 0; + + int rsize = spa_ump_to_midi(&up, &usize, result, sizeof(result), &state); + spa_assert(rsize == 3); + spa_assert(memcmp(result, orig, 3) == 0); +} + +static void test_ump_from_midi_system_realtime(void) +{ + uint8_t midi[] = { 0xf8 }; /* timing clock */ + uint8_t *p = midi; + size_t midi_size = sizeof(midi); + uint32_t ump[4] = {0}; + uint64_t state = 0; + + int size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 4); + spa_assert((ump[0] >> 16) == 0x10f8); +} + +static void test_ump_from_midi_sysex_trailing_f0(void) +{ + /* Sysex start with trailing F0: F0 01 02 03 F0 */ + uint8_t midi[] = { 0xf0, 0x01, 0x02, 0x03, 0xf0 }; + uint8_t *p = midi; + size_t midi_size = sizeof(midi); + uint32_t ump[4] = {0}; + uint64_t state = 0; + + int size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 8); + spa_assert(midi_size == 0); + /* status 0x1 (start), 3 bytes */ + spa_assert(((ump[0] >> 20) & 0xf) == 0x1); + spa_assert(((ump[0] >> 16) & 0xf) == 3); + spa_assert(state == 2); +} + +static void test_ump_from_midi_continuation_trailing_f0(void) +{ + /* Continuation with trailing F0, sysex already active */ + uint8_t midi[] = { 0x01, 0x02, 0x03, 0xf0 }; + uint8_t *p = midi; + size_t midi_size = sizeof(midi); + uint32_t ump[4] = {0}; + uint64_t state = 2; + + int size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 8); + spa_assert(midi_size == 0); + /* status 0x2 (continue), 3 bytes */ + spa_assert(((ump[0] >> 20) & 0xf) == 0x2); + spa_assert(((ump[0] >> 16) & 0xf) == 3); + spa_assert(state == 2); +} + +static void test_ump_from_midi_sysex_split_with_f0(void) +{ + /* Full sysex split using F0 continuation markers */ + uint8_t midi1[] = { 0xf0, 0x01, 0x02, 0x03, 0xf0 }; + uint8_t *p = midi1; + size_t midi_size = sizeof(midi1); + uint32_t ump[4]; + uint64_t state = 0; + + /* First call: Start, 3 data bytes */ + memset(ump, 0, sizeof(ump)); + int size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 8); + spa_assert(midi_size == 0); + spa_assert(((ump[0] >> 20) & 0xf) == 0x1); + spa_assert(((ump[0] >> 16) & 0xf) == 3); + spa_assert(state == 2); + + /* Second call: continuation data with F7 end */ + uint8_t midi2[] = { 0x04, 0x05, 0xf7 }; + p = midi2; + midi_size = sizeof(midi2); + memset(ump, 0, sizeof(ump)); + size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 8); + spa_assert(midi_size == 0); + spa_assert(((ump[0] >> 20) & 0xf) == 0x3); + spa_assert(((ump[0] >> 16) & 0xf) == 2); + spa_assert(state == 0); +} + +static void test_ump_from_midi_f7_continuation(void) +{ + /* F7 continuation with data, no prior sysex */ + uint8_t midi[] = { 0xf7, 0x01, 0x02, 0x03 }; + uint8_t *p = midi; + size_t midi_size = sizeof(midi); + uint32_t ump[4] = {0}; + uint64_t state = 0; + + int size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 8); + spa_assert(midi_size == 0); + /* status 0x2 (continue), 3 bytes */ + spa_assert(((ump[0] >> 20) & 0xf) == 0x2); + spa_assert(((ump[0] >> 16) & 0xf) == 3); + spa_assert(state == 2); +} + +static void test_ump_from_midi_f7_continuation_with_end(void) +{ + /* F7 continuation with data and F7 end, no prior sysex */ + uint8_t midi[] = { 0xf7, 0x01, 0x02, 0xf7 }; + uint8_t *p = midi; + size_t midi_size = sizeof(midi); + uint32_t ump[4] = {0}; + uint64_t state = 0; + + int size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 8); + spa_assert(midi_size == 0); + /* status 0x3 (end), 2 bytes */ + spa_assert(((ump[0] >> 20) & 0xf) == 0x3); + spa_assert(((ump[0] >> 16) & 0xf) == 2); + spa_assert(state == 0); +} + +static void test_ump_from_midi_f7_continuation_active(void) +{ + /* F7 continuation when sysex already active */ + uint8_t midi[] = { 0xf7, 0x01, 0x02, 0x03 }; + uint8_t *p = midi; + size_t midi_size = sizeof(midi); + uint32_t ump[4] = {0}; + uint64_t state = 2; + + int size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 8); + spa_assert(midi_size == 0); + /* status 0x2 (continue), 3 bytes */ + spa_assert(((ump[0] >> 20) & 0xf) == 0x2); + spa_assert(((ump[0] >> 16) & 0xf) == 3); + spa_assert(state == 2); +} + +static void test_ump_from_midi_bare_f7_end(void) +{ + /* Bare F7 ending an active sysex */ + uint8_t midi[] = { 0xf7 }; + uint8_t *p = midi; + size_t midi_size = sizeof(midi); + uint32_t ump[4] = {0}; + uint64_t state = 2; + + int size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 8); + spa_assert(midi_size == 0); + /* status 0x3 (end), 0 bytes */ + spa_assert(((ump[0] >> 20) & 0xf) == 0x3); + spa_assert(((ump[0] >> 16) & 0xf) == 0); + spa_assert(state == 0); +} + +static void test_ump_from_midi_bare_f7_complete(void) +{ + /* F0 alone in first call, then bare F7 → Complete */ + uint8_t midi1[] = { 0xf0 }; + uint8_t *p = midi1; + size_t midi_size = sizeof(midi1); + uint32_t ump[4] = {0}; + uint64_t state = 0; + + int size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 0); + spa_assert(midi_size == 0); + spa_assert(state == 1); + + uint8_t midi2[] = { 0xf7 }; + p = midi2; + midi_size = sizeof(midi2); + memset(ump, 0, sizeof(ump)); + + size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 8); + spa_assert(midi_size == 0); + /* status 0x0 (complete), 0 bytes */ + spa_assert(((ump[0] >> 20) & 0xf) == 0x0); + spa_assert(((ump[0] >> 16) & 0xf) == 0); + spa_assert(state == 0); +} + +static void test_ump_from_midi_bare_f7_orphan(void) +{ + /* Bare F7 with no sysex in progress: consume, no UMP */ + uint8_t midi[] = { 0xf7 }; + uint8_t *p = midi; + size_t midi_size = sizeof(midi); + uint32_t ump[4] = {0}; + uint64_t state = 0; + + int size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 0); + spa_assert(midi_size == 0); + spa_assert(state == 0); +} + +static void test_ump_from_midi_sysex_split_with_f7(void) +{ + /* Full sysex split with F7 continuation markers */ + uint8_t midi1[] = { 0xf0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + uint8_t *p = midi1; + size_t midi_size = sizeof(midi1); + uint32_t ump[4]; + uint64_t state = 0; + + /* First call: Start, 6 data bytes */ + memset(ump, 0, sizeof(ump)); + int size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 8); + spa_assert(midi_size == 0); + spa_assert(((ump[0] >> 20) & 0xf) == 0x1); + spa_assert(((ump[0] >> 16) & 0xf) == 6); + spa_assert(state == 2); + + /* Second call with F7 continuation marker */ + uint8_t midi2[] = { 0xf7, 0x07, 0x08, 0xf7 }; + p = midi2; + midi_size = sizeof(midi2); + memset(ump, 0, sizeof(ump)); + size = spa_ump_from_midi(&p, &midi_size, ump, sizeof(ump), 0, &state); + spa_assert(size == 8); + spa_assert(midi_size == 0); + spa_assert(((ump[0] >> 20) & 0xf) == 0x3); + spa_assert(((ump[0] >> 16) & 0xf) == 2); + spa_assert(state == 0); +} + +int main(void) +{ + test_ump_from_midi_note_on(); + test_ump_from_midi_program_change(); + test_ump_from_midi_sysex_complete(); + test_ump_from_midi_sysex_complete_max(); + test_ump_from_midi_sysex_multi_packet(); + test_ump_from_midi_sysex_continue(); + test_ump_from_midi_sysex_minimal(); + test_ump_to_midi_note_on(); + test_ump_to_midi_sysex_complete(); + test_ump_to_midi_sysex_start_end(); + test_roundtrip_sysex_short(); + test_roundtrip_sysex_long(); + test_roundtrip_note_on(); + test_ump_from_midi_system_realtime(); + test_ump_from_midi_sysex_trailing_f0(); + test_ump_from_midi_continuation_trailing_f0(); + test_ump_from_midi_sysex_split_with_f0(); + test_ump_from_midi_f7_continuation(); + test_ump_from_midi_f7_continuation_with_end(); + test_ump_from_midi_f7_continuation_active(); + test_ump_from_midi_bare_f7_end(); + test_ump_from_midi_bare_f7_complete(); + test_ump_from_midi_bare_f7_orphan(); + test_ump_from_midi_sysex_split_with_f7(); + + printf("All UMP utils tests passed.\n"); + return 0; +}