1
0
Fork 0
forked from hanez/xtc
xtc/tests/unit/test_midi.c

167 lines
10 KiB
C
Raw Permalink Normal View History

#define _POSIX_C_SOURCE 200809L
#include "xtc/xtc_midi.h"
#include "xtc/xtc_audio.h"
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define TAU 6.28318530717958647692
static int silent(const float *p, size_t n)
{ size_t i; for (i = 0; i < n; ++i) if (p[i] != 0) return 0; return 1; }
int main(void)
{
xtc_project *p = NULL, *q = NULL;
xtc_midi_plan *a = NULL, *b = NULL;
xtc_plan *graph = NULL;
uint64_t track, clip, note, other;
size_t count, i;
float whole[512], split[512], output[512], routed[512];
xtc_note_info n;
xtc_midi_event event = {4, XTC_MIDI_NOTE_ON, 0, 69, 127}, decoded = {0};
unsigned char on[] = {0x92, 69, 100}, off[] = {0x92, 69, 0}, panic[] = {0xb2, 123, 0}, bad[] = {0x90, 255, 127};
char directory[] = "/tmp/xtc-midi-XXXXXX", path[256], copy[256], bytes[8192], roundtrip[8192];
FILE *f; size_t length;
assert(mkdtemp(directory));
snprintf(path, sizeof(path), "%s/song.xtc", directory);
snprintf(copy, sizeof(copy), "%s/copy.xtc", directory);
assert(xtc_midi_decode(on, 3, 17, &decoded) == XTC_OK && decoded.channel == 2 && decoded.offset == 17 && decoded.type == XTC_MIDI_NOTE_ON);
assert(xtc_midi_decode(off, 3, 0, &decoded) == XTC_OK && decoded.type == XTC_MIDI_NOTE_OFF);
assert(xtc_midi_decode(panic, 3, 0, &decoded) == XTC_OK && decoded.type == XTC_MIDI_ALL_OFF);
assert(xtc_midi_decode(bad, 3, 0, &decoded) == XTC_ERR_INVALID_ARGUMENT);
assert(xtc_midi_decode(on, 2, 0, &decoded) == XTC_ERR_INVALID_ARGUMENT);
assert(xtc_project_create(&p) == XTC_OK && xtc_project_create(&q) == XTC_OK);
assert(xtc_project_add_track(p, "Keys", &track) == XTC_OK);
assert(xtc_project_add_midi_clip(p, track, 1, 3840, &clip) == XTC_OK);
assert(xtc_project_add_note(p, clip, 1, 10, 69, 127, 0, &note) == XTC_OK);
assert(xtc_project_set_clip_asset(p, clip, "/bad.wav") == XTC_ERR_INVALID_STATE);
assert(xtc_project_add_note(p, clip, 3840, 1, 69, 127, 0, &other) == XTC_ERR_INVALID_ARGUMENT);
assert(xtc_project_add_note(p, clip, 0, 1, 128, 127, 0, &other) == XTC_ERR_INVALID_ARGUMENT);
assert(xtc_project_edit_note(p, note, 0, UINT64_MAX, 69, 127, 0) == XTC_ERR_INVALID_ARGUMENT);
assert(xtc_midi_plan_create(p, 48000, &a) == XTC_OK && xtc_midi_plan_create(p, 48000, &b) == XTC_OK);
assert(xtc_midi_plan_render(a, 0, NULL, 0, whole, 512) == XTC_OK);
assert(silent(whole, 51) && whole[51] > 0 && silent(whole + 300, 212));
assert(fabs((double)whole[51] - sin(TAU * 440 / 48000) * 0.1) < 0.000001);
assert(xtc_midi_plan_render(b, 0, NULL, 0, split, 53) == XTC_OK);
assert(xtc_midi_plan_render(b, 53, NULL, 0, split + 53, 247) == XTC_OK);
assert(xtc_midi_plan_render(b, 300, NULL, 0, split + 300, 212) == XTC_OK);
assert(memcmp(whole, split, sizeof(whole)) == 0); /* Block partition independent. */
assert(xtc_graph_compile(xtc_project_graph(p), 48000, 512, &graph) == XTC_OK);
assert(xtc_plan_process_sources(graph, NULL, NULL, whole, routed, 512) == XTC_OK && memcmp(whole, routed, sizeof(whole)) == 0);
xtc_plan_destroy(graph);
xtc_midi_plan_panic(a, 100);
assert(xtc_midi_plan_render(a, 100, NULL, 0, output, 128) == XTC_OK && silent(output, 128));
xtc_midi_plan_reset(a);
assert(xtc_midi_plan_render(a, 100, NULL, 0, output, 128) == XTC_OK && output[0] == whole[100]);
assert(xtc_project_save(p, path) == XTC_OK && xtc_project_load(q, path) == XTC_OK);
assert(xtc_project_get_note(q, 0, &n) == XTC_OK && n.id == note && n.pitch == 69 && n.start_tick == 1);
assert(xtc_project_save(q, copy) == XTC_OK);
f = fopen(path, "rb"); assert(f); length = fread(bytes, 1, sizeof(bytes), f); assert(fclose(f) == 0);
f = fopen(copy, "rb"); assert(f); assert(fread(roundtrip, 1, sizeof(roundtrip), f) == length && fclose(f) == 0);
assert(memcmp(bytes, roundtrip, length) == 0);
/* All truncations rejected without changing the loaded notes. */
for (i = 0; i < length; ++i) {
f = fopen(copy, "wb"); assert(f);
assert(fwrite(bytes, 1, i, f) == i && fclose(f) == 0);
assert(xtc_project_load(q, copy) != XTC_OK);
assert(xtc_project_get_note(q, 0, &n) == XTC_OK && n.id == note);
}
bytes[length] = 0;
{
const char *invalid[] = {
"NOTES 1 1\nNOTE 0 999 1 10 69 127 0\nEND\n",
"NOTES 1 1\nNOTE 0 0 3840 10 69 127 0\nEND\n",
"NOTES 1 1\nNOTE 0 0 1 10 128 127 0\nEND\n",
"NOTES 1 1\nNOTE 0 0 1 10 69 0 0\nEND\n",
"NOTES 1 1\nNOTE 0 0 1 10 69 127 16\nEND\n",
"NOTES 1 1\nNOTE 1 0 1 10 69 127 0\nEND\n",
"NOTES 2 2\nNOTE 0 0 1 10 69 127 0\nNOTE 0 0 1 10 69 127 0\nEND\n"
};
const char *notes = strstr(bytes, "NOTES "); assert(notes);
size_t prefix = (size_t)(notes - bytes), k;
for (k = 0; k < sizeof(invalid) / sizeof(invalid[0]); ++k) {
f = fopen(copy, "wb"); assert(f);
assert(fwrite(bytes, 1, prefix, f) == prefix && fputs(invalid[k], f) >= 0 && fclose(f) == 0);
assert(xtc_project_load(q, copy) == XTC_ERR_FORMAT);
assert(xtc_project_get_note(q, 0, &n) == XTC_OK && n.id == note && n.pitch == 69);
}
}
assert(xtc_project_remove_note(q, note) == XTC_OK);
assert(xtc_project_add_note(q, clip, 0, 960, 60, 100, 1, &other) == XTC_OK && other > note);
assert(xtc_project_remove_clip(q, clip) == XTC_OK && xtc_project_note_count(q, &count) == XTC_OK && count == 0);
assert(xtc_project_edit_clip(p, clip, track, 0, 5, 5) == XTC_OK);
xtc_midi_plan_destroy(a); xtc_midi_plan_destroy(b);
assert(xtc_midi_plan_create(p, 48000, &a) == XTC_OK);
assert(xtc_midi_plan_render(a, 0, NULL, 0, output, 128) == XTC_OK);
assert(fabs((double)output[0] - sin(TAU * 100 * 440 / 48000) * 0.1) < 0.000001);
assert(silent(output + 125, 3)); /* Trim hard-stops note, no stuck tail. */
xtc_midi_plan_destroy(a);
assert(xtc_project_remove_track(p, track) == XTC_OK && xtc_project_note_count(p, &count) == XTC_OK && count == 0);
assert(xtc_midi_plan_create(p, 48000, &a) == XTC_OK);
assert(xtc_midi_plan_render(a, 0, &event, 1, output, 32) == XTC_OK);
assert(silent(output, 5) && output[5] > 0);
event.offset = 8; event.type = XTC_MIDI_NOTE_OFF;
assert(xtc_midi_plan_render(a, 32, &event, 1, output, 32) == XTC_OK && silent(output + 8, 24));
event.offset = 0; event.type = XTC_MIDI_NOTE_ON;
assert(xtc_midi_plan_render(a, 64, &event, 1, output, 32) == XTC_OK);
event.offset = 7; event.type = XTC_MIDI_ALL_OFF;
assert(xtc_midi_plan_render(a, 96, &event, 1, output, 32) == XTC_OK && silent(output + 7, 25));
event.offset = 0; event.type = XTC_MIDI_NOTE_ON;
assert(xtc_midi_plan_render(a, 128, &event, 1, output, 32) == XTC_OK);
assert(xtc_midi_plan_render(a, 500, NULL, 0, output, 32) == XTC_OK && silent(output, 32)); /* Seek kills live. */
for (i = 0; i < 512; ++i) output[i] = 123;
event.offset = 32;
assert(xtc_midi_plan_render(a, 532, &event, 1, output, 32) == XTC_ERR_INVALID_ARGUMENT && output[0] == 123);
xtc_midi_plan_destroy(a);
assert(xtc_project_add_track(p, "Poly", &track) == XTC_OK);
assert(xtc_project_add_midi_clip(p, track, 0, 3840, &clip) == XTC_OK);
for (i = 0; i < 33; ++i) assert(xtc_project_add_note(p, clip, 0, 960, (unsigned int)i + 48, 100, 0, &note) == XTC_OK);
assert(xtc_midi_plan_create(p, 48000, &a) == XTC_ERR_CAPACITY && a == NULL);
assert(xtc_project_remove_note(p, note) == XTC_OK);
assert(xtc_midi_plan_create(p, 48000, &a) == XTC_OK); xtc_midi_plan_destroy(a);
for (i = 32; i < XTC_PROJECT_MAX_NOTES; ++i)
assert(xtc_project_add_note(p, clip, 0, 1, 60, 100, 0, &note) == XTC_OK);
assert(xtc_project_add_note(p, clip, 0, 1, 60, 100, 0, &note) == XTC_ERR_CAPACITY);
assert(xtc_project_save(p, path) == XTC_OK && xtc_project_load(p, path) == XTC_OK);
assert(xtc_project_remove_track(p, track) == XTC_OK && xtc_project_note_count(p, &count) == XTC_OK && count == 0);
assert(xtc_midi_plan_create(q, 48000, &a) == XTC_OK);
{
xtc_midi_event many[33];
for (i = 0; i < 33; ++i) {
many[i].offset = 0; many[i].type = XTC_MIDI_NOTE_ON; many[i].channel = 0;
many[i].pitch = (unsigned int)i + 48; many[i].velocity = 127;
}
assert(xtc_midi_plan_render(a, 0, many, 33, output, 32) == XTC_OK && output[1] > 0);
many[0].type = XTC_MIDI_ALL_OFF;
assert(xtc_midi_plan_render(a, 32, many, 1, output, 32) == XTC_OK && silent(output, 32));
many[0].type = many[1].type = XTC_MIDI_NOTE_ON;
many[0].pitch = many[1].pitch = 69; many[1].channel = 1;
assert(xtc_midi_plan_render(a, 64, many, 2, output, 32) == XTC_OK);
many[0].type = XTC_MIDI_ALL_OFF; many[0].offset = 8;
assert(xtc_midi_plan_render(a, 96, many, 1, output, 32) == XTC_OK);
assert(fabs((double)output[8] - sin(TAU * 40 * 440 / 48000) * 0.1) < 0.000001);
xtc_midi_plan_panic(a, 128);
assert(xtc_midi_plan_render(a, 128, NULL, 0, output, 32) == XTC_OK && silent(output, 32));
}
xtc_midi_plan_destroy(a);
/* Explicit migration from the previous audio format. */
f = fopen(copy, "wb"); assert(f);
assert(fputs("XTC_PROJECT 2\nAPP 0 1 0\nTEMPO 120000\nIDS 1 1\nGRAPH 1 0 0 0\nTRACKS 1\nTRACK 0 41\nCLIPS 1\nCLIP 0 0 0 960 0 960 42\nASSET -\nEND\n", f) >= 0 && fclose(f) == 0);
assert(xtc_project_load(q, copy) == XTC_OK && xtc_project_note_count(q, &count) == XTC_OK && count == 0);
{
xtc_graph *full = NULL;
size_t id, tracks, clips;
uint64_t unchanged = UINT64_MAX;
assert(xtc_graph_create(64, &full) == XTC_OK);
for (i = 0; i < 64; ++i) assert(xtc_graph_add_gain(full, 1, &id) == XTC_OK);
assert(xtc_project_set_graph(q, full) == XTC_OK);
xtc_graph_destroy(full);
assert(xtc_project_add_midi_clip(q, 0, 0, 3840, &unchanged) == XTC_ERR_CAPACITY && unchanged == UINT64_MAX);
assert(xtc_project_get_counts(q, &tracks, &clips) == XTC_OK && tracks == 1 && clips == 1);
}
xtc_project_destroy(p); xtc_project_destroy(q);
assert(unlink(path) == 0 && unlink(copy) == 0 && rmdir(directory) == 0);
puts("MIDI notes/persistence/scheduling/trim/seek/panic/live offsets and polyphony passed.");
return 0;
}