pipewire/spa/tests/test-ump-utils.c

573 lines
16 KiB
C
Raw Normal View History

/* 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;
}