From 802972f1ae6f27298229f958af9149c796de2268 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 29 Jul 2024 09:05:13 +0200 Subject: [PATCH] ump: handle f0 .. f0 f7 .. f0 f7 .. f7 packets Support the RFC 4695 sysex segmentation rules where a sysex packet can be split into multiple chunks using the f0 and f7 patterns like: begin f0 ... f0 continue f7 ... f0 end f7 ... f7 Add a unit test for the sysex UMP conversion. --- spa/include/spa/control/ump-utils.h | 71 +++++++----- test/meson.build | 1 + test/test-spa-control.c | 173 ++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+), 29 deletions(-) create mode 100644 test/test-spa-control.c diff --git a/spa/include/spa/control/ump-utils.h b/spa/include/spa/control/ump-utils.h index 504659f77..b46cdd663 100644 --- a/spa/include/spa/control/ump-utils.h +++ b/spa/include/spa/control/ump-utils.h @@ -106,8 +106,8 @@ static inline int spa_ump_from_midi(uint8_t **midi, size_t *midi_size, uint32_t *ump, size_t ump_maxsize, uint8_t group, uint64_t *state) { int size = 0; - uint32_t prefix = group << 24, to_consume = 0, bytes; - uint8_t status, *m = (*midi); + uint32_t i, prefix = group << 24, to_consume = 0, bytes; + uint8_t status, *m = (*midi), end; if (*midi_size < 1) return 0; @@ -117,41 +117,54 @@ static inline int spa_ump_from_midi(uint8_t **midi, size_t *midi_size, status = m[0]; /* SysEx */ - if (*state == 0 && status == 0xf0) - *state = 1; /* sysex start */ + if (*state == 0) { + if (status == 0xf0) + *state = 1; /* sysex start */ + else if (status == 0xf7) + *state = 2; /* sysex continue */ + } if (*state & 3) { prefix |= 0x30000000; - if (*state == 1) { + if (status & 0x80) { m++; to_consume++; } - bytes = SPA_CLAMP(*midi_size - to_consume, 0u, 6u); - to_consume += bytes; - if (bytes > 0 && m[bytes-1] == 0xf7) { - bytes--; - if (*state == 2) - prefix |= 0x3 << 20; - *state = 0; - } else if (*state == 1) { - prefix |= 0x1 << 20; - *state = 2; /* sysex continue */ - } else { - prefix |= 0x2 << 20; - } + bytes = SPA_CLAMP(*midi_size - to_consume, 0u, 7u); if (bytes > 0) { - ump[size++] = prefix | bytes << 16 | (m[0] & 0x7f) << 8; + end = m[bytes-1]; + if (end & 0x80) { + bytes--; /* skip terminator */ + to_consume++; + } + else + end = 0xf0; /* pretend there is a continue terminator */ + + bytes = SPA_CLAMP(bytes, 0u, 6u); + to_consume += bytes; + + if (end == 0xf7) { + if (*state == 2) { + /* continue and done */ + prefix |= 0x3 << 20; + *state = 0; + } + } else if (*state == 1) { + /* first packet but not finished */ + prefix |= 0x1 << 20; + *state = 2; /* sysex continue */ + } else { + /* continue and not finished */ + prefix |= 0x2 << 20; + } + ump[size++] = prefix | bytes << 16; ump[size++] = 0; + for (i = 0 ; i < bytes; i++) + /* ump[0] |= (m[0] & 0x7f) << 8 + * ump[0] |= (m[1] & 0x7f) + * ump[1] |= (m[2] & 0x7f) << 24 + * ... */ + ump[(i+2)/4] |= (m[i] & 0x7f) << ((5-i)%4 * 8); } - if (bytes > 1) - ump[0] |= (m[1] & 0x7f); - if (bytes > 2) - ump[1] |= (m[2] & 0x7f) << 24; - if (bytes > 3) - ump[1] |= (m[3] & 0x7f) << 16; - if (bytes > 4) - ump[1] |= (m[4] & 0x7f) << 8; - if (bytes > 5) - ump[1] |= (m[5] & 0x7f); } else { /* regular messages */ switch (status) { diff --git a/test/meson.build b/test/meson.build index 3cad6dc56..c3b9e5c4f 100644 --- a/test/meson.build +++ b/test/meson.build @@ -111,6 +111,7 @@ endif test('test-spa', executable('test-spa', 'test-spa-buffer.c', + 'test-spa-control.c', 'test-spa-json.c', 'test-spa-utils.c', 'test-spa-log.c', diff --git a/test/test-spa-control.c b/test/test-spa-control.c new file mode 100644 index 000000000..d493b2934 --- /dev/null +++ b/test/test-spa-control.c @@ -0,0 +1,173 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2024 Wim Taymans. */ +/* SPDX-License-Identifier: MIT */ + +#include "pwtest.h" + +#include +#include + +PWTEST(control_abi_types) +{ + /* contol */ + pwtest_int_eq(SPA_CONTROL_Invalid, 0); + pwtest_int_eq(SPA_CONTROL_Properties, 1); + pwtest_int_eq(SPA_CONTROL_Midi, 2); + pwtest_int_eq(SPA_CONTROL_OSC, 3); + pwtest_int_eq(SPA_CONTROL_UMP, 4); + pwtest_int_eq(_SPA_CONTROL_LAST, 5); + + return PWTEST_PASS; +} + +static inline uint32_t tohex(char v) +{ + if (v >= '0' && v <= '9') + return v - '0'; + if (v >= 'a' && v <= 'f') + return v - 'a' + 10; + return 0; +} + +static size_t parse_midi(const char *midi, uint8_t *data, size_t max_size) +{ + size_t size = 0; + while (*midi) { + while (*midi == ' ') + midi++; + data[size++] = tohex(*(midi+0)) << 4 | + tohex(*(midi+1)); + midi+=2; + } + return size; +} + +static size_t parse_ump(const char *ump, uint32_t *data, size_t max_size) +{ + size_t size = 0; + while (*ump) { + while (*ump == ' ') + ump++; + data[size++] = tohex(*(ump+0)) << 28 | + tohex(*(ump+1)) << 24 | + tohex(*(ump+2)) << 20 | + tohex(*(ump+3)) << 16 | + tohex(*(ump+4)) << 12 | + tohex(*(ump+5)) << 8 | + tohex(*(ump+6)) << 4 | + tohex(*(ump+7)); + ump+=8; + } + return size * 4; +} + +static int do_midi_to_ump_test(char *midi, char *ump) +{ + int i; + size_t m_size, u_size, u_offs = 0; + uint8_t *m_data = alloca(strlen(midi) / 2); + uint32_t *u_data = alloca(strlen(ump) / 2); + uint64_t state = 0; + + m_size = parse_midi(midi, m_data, sizeof(m_data)); + u_size = parse_ump(ump, u_data, sizeof(u_data)); + + while (m_size > 0) { + uint32_t ump[4]; + fprintf(stdout, "%zd %08x\n", m_size, *m_data); + int ump_size = spa_ump_from_midi(&m_data, &m_size, + ump, sizeof(ump), 0, &state); + if (ump_size <= 0) + return -1; + + if (u_size <= u_offs) + return -1; + + for (i = 0; i < ump_size / 4; i++) { + fprintf(stdout, "%08x %08x\n", u_data[u_offs], ump[i]); + spa_assert(u_data[u_offs++] == ump[i]); + } + } + return 0; +} + +PWTEST(control_midi_to_ump) +{ + /* sysex */ + do_midi_to_ump_test("f0 f7", + "30000000 00000000"); + + do_midi_to_ump_test("f0 01 02 03 04 05 f7", + "30050102 03040500"); + + do_midi_to_ump_test("f0 01 02 03 04 05 06 f7", + "30060102 03040506"); + do_midi_to_ump_test("f0 01 02 03 04 05 06 07 f7", + "30160102 03040506 30310700 00000000"); + do_midi_to_ump_test("f0 01 02 03 04 05 06 07 08 09 10 11 12 13 f7", + "30160102 03040506 30260708 09101112 30311300 00000000"); + + do_midi_to_ump_test("f0 01 02 03 04 05 06 f0", + "30160102 03040506"); + do_midi_to_ump_test("f7 01 02 03 04 05 06 07 08 f0", + "30260102 03040506 30220708 00000000"); + do_midi_to_ump_test("f7 01 02 03 04 05 06 07 08 09 f7", + "30260102 03040506 30330708 09000000"); + + return PWTEST_PASS; +} + +static int do_ump_to_midi_test(char *ump, char *midi) +{ + int i; + size_t m_size, u_size, m_offs = 0; + uint8_t *m_data = alloca(strlen(midi) / 2); + uint32_t *u_data = alloca(strlen(ump) / 2); + + u_size = parse_ump(ump, u_data, sizeof(u_data)); + m_size = parse_midi(midi, m_data, sizeof(m_data)); + + spa_assert(u_size > 0); + spa_assert(m_size > 0); + + while (u_size > 0) { + uint8_t midi[32]; + fprintf(stdout, "%zd %08x\n", u_size, *u_data); + int midi_size = spa_ump_to_midi(u_data, u_size, + midi, sizeof(midi)); + if (midi_size <= 0) + return midi_size; + + if (m_size <= m_offs) + return -1; + + for (i = 0; i < midi_size; i++) { + fprintf(stdout, "%08x %08x\n", m_data[m_offs], midi[i]); + spa_assert(m_data[m_offs++] == midi[i]); + } + u_size -= spa_ump_message_size(*u_data >> 28) * 4; + u_data += spa_ump_message_size(*u_data >> 28); + } + return 0; +} + +PWTEST(control_ump_to_midi) +{ + spa_assert(do_ump_to_midi_test("30000000 00000000", + "f0 f7") >= 0); + spa_assert(do_ump_to_midi_test("30050102 03040500", + "f0 01 02 03 04 05 f7") >= 0); + + spa_assert(do_ump_to_midi_test("30160102 03040506 30260708 09101112 30311300 00000000", + "f0 01 02 03 04 05 06 07 08 09 10 11 12 13 f7") >= 0); + return PWTEST_PASS; +} + +PWTEST_SUITE(spa_buffer) +{ + pwtest_add(control_abi_types, PWTEST_NOARG); + pwtest_add(control_midi_to_ump, PWTEST_NOARG); + pwtest_add(control_ump_to_midi, PWTEST_NOARG); + + return PWTEST_PASS; +}