mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-05-27 21:37:37 -04:00
ump-utils: fix sysex encoding and add F7 continuation support
Fix spa_ump_from_midi() to correctly encode short sysex messages as Complete (0x0) instead of Start (0x1) when the entire message fits in one UMP packet. Handle bare F7 termination when no data bytes follow. Support 0xF7 as a sysex continuation marker and optional trailing 0xF0 as a continuation boundary. Add unit tests for UMP/MIDI conversion covering note on, program change, sysex complete/multi-packet/continue, system realtime, F7 continuation, bare F7 end/complete/orphan, and trailing F0 scenarios. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4e1257a6e6
commit
552eb1e618
3 changed files with 619 additions and 2 deletions
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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', []],
|
||||
|
|
|
|||
572
spa/tests/test-ump-utils.c
Normal file
572
spa/tests/test-ump-utils.c
Normal file
|
|
@ -0,0 +1,572 @@
|
|||
/* PipeWire */
|
||||
/* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <spa/control/ump-utils.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue