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

137 lines
7.7 KiB
C

#include "xtc/xtc_midi.h"
#include "xtc/xtc_mixer.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
static void editing(void)
{
xtc_project *p = NULL, *snapshot = NULL;
xtc_history *history = NULL;
uint64_t track, clip, second, id, next;
xtc_note_info n, original, untouched, foreign;
xtc_note_filter f;
xtc_note_edit e = {XTC_NOTES_EDIT, 160, 12, -1, -1, 0, 0};
size_t count = 999, total;
assert(xtc_project_create(&p) == XTC_OK);
assert(xtc_project_add_track(p, "Keys", &track) == XTC_OK);
assert(xtc_project_add_midi_clip(p, track, 0, 3840, &clip) == XTC_OK);
assert(xtc_project_add_midi_clip(p, track, 3840, 3840, &second) == XTC_OK);
assert(xtc_project_add_note(p, clip, 80, 100, 60, 73, 2, &id) == XTC_OK);
assert(xtc_project_get_note(p, 0, &original) == XTC_OK);
assert(xtc_project_add_note(p, clip, 90, 101, 61, 91, 3, &next) == XTC_OK);
assert(xtc_project_get_note(p, 1, &untouched) == XTC_OK);
assert(xtc_project_add_note(p, second, 80, 100, 60, 73, 2, &next) == XTC_OK);
assert(xtc_project_get_note(p, 2, &foreign) == XTC_OK);
f = (xtc_note_filter){clip, UINT64_MAX, 2, 60, 60};
assert(xtc_history_create(p, &history) == XTC_OK);
assert(xtc_project_edit_notes(p, &f, &e, &count) == XTC_OK && count == 1);
assert(xtc_project_get_note(p, 0, &n) == XTC_OK);
assert(n.id == id && n.start_tick == 160 && n.length_tick == 100 && n.pitch == 72 && n.velocity == 73 && n.channel == 2);
assert(xtc_project_get_note(p, 1, &n) == XTC_OK && !memcmp(&n, &untouched, sizeof(n)));
assert(xtc_project_get_note(p, 2, &n) == XTC_OK && !memcmp(&n, &foreign, sizeof(n)));
assert(xtc_history_record(history, p) == XTC_OK);
assert(xtc_history_checkout(history, 0, &snapshot) == XTC_OK);
assert(xtc_project_get_note(snapshot, 0, &n) == XTC_OK && !memcmp(&n, &original, sizeof(n)));
xtc_project_destroy(snapshot);
assert(xtc_history_accept(history, 0) == XTC_OK);
assert(xtc_history_checkout(history, 1, &snapshot) == XTC_OK);
assert(xtc_project_get_note(snapshot, 0, &n) == XTC_OK && n.start_tick == 160 && n.pitch == 72);
xtc_project_destroy(snapshot);
f.pitch_min = 0; f.pitch_max = 127;
e = (xtc_note_edit){XTC_NOTES_REPEAT, 0, -12, 110, 4, 2, 320};
assert(xtc_project_edit_notes(p, &f, &e, &count) == XTC_OK && count == 1);
assert(xtc_project_get_note(p, 3, &n) == XTC_OK && n.id > foreign.id && n.start_tick == 480 && n.pitch == 60 && n.velocity == 110 && n.channel == 4);
next = n.id;
assert(xtc_project_get_note(p, 4, &n) == XTC_OK && n.id > next && n.start_tick == 800 && n.length_tick == 100);
next = n.id;
/* Reject entire batch if a later match overflows pitch/range/capacity. */
f.channel = -1;
e = (xtc_note_edit){XTC_NOTES_EDIT, 0, 67, -1, -1, 0, 0};
count = 999;
assert(xtc_project_get_note(p, 0, &original) == XTC_OK);
assert(xtc_project_edit_notes(p, &f, &e, &count) == XTC_ERR_INVALID_ARGUMENT && count == 999);
assert(xtc_project_get_note(p, 0, &n) == XTC_OK && !memcmp(&n, &original, sizeof(n)));
e = (xtc_note_edit){XTC_NOTES_REPEAT, 0, 0, -1, -1, 2, UINT64_MAX};
assert(xtc_project_edit_notes(p, &f, &e, &count) == XTC_ERR_INVALID_ARGUMENT && count == 999);
e.copies = XTC_PROJECT_MAX_NOTES;
assert(xtc_project_edit_notes(p, &f, &e, &count) == XTC_ERR_CAPACITY && count == 999);
e = (xtc_note_edit){XTC_NOTES_EDIT, UINT64_MAX, 0, 0, -1, 0, 0};
assert(xtc_project_edit_notes(p, &f, &e, &count) == XTC_ERR_INVALID_ARGUMENT);
e.velocity = -1; e.grid_tick = 3840;
f.note_id = id;
/* Nearest grid preserves duration, including rounding down near zero. */
assert(xtc_project_edit_notes(p, &f, &e, &count) == XTC_OK);
assert(xtc_project_get_note(p, 0, &n) == XTC_OK && n.start_tick == 0 && n.length_tick == 100);
assert(xtc_project_add_note(p, clip, 3800, 40, 50, 50, 0, &id) == XTC_OK && id == next + 1);
f.note_id = id;
assert(xtc_project_edit_notes(p, &f, &e, &count) == XTC_ERR_INVALID_ARGUMENT);
f.note_id = UINT64_MAX; f.channel = 4;
e.action = XTC_NOTES_DELETE;
assert(xtc_project_edit_notes(p, &f, &e, &count) == XTC_OK && count == 2);
assert(xtc_project_note_count(p, &total) == XTC_OK && total == 4);
assert(xtc_project_get_note(p, 1, &n) == XTC_OK && !memcmp(&n, &untouched, sizeof(n)));
assert(xtc_project_edit_notes(p, &f, &e, &count) == XTC_ERR_NOT_FOUND);
f.channel = 16;
assert(xtc_project_edit_notes(p, &f, &e, &count) == XTC_ERR_INVALID_ARGUMENT);
xtc_history_destroy(history);
xtc_project_destroy(p);
}
static void scheduling(void)
{
xtc_project *p = NULL;
xtc_midi_plan *a = NULL, *b = NULL;
uint64_t track, clip, id;
float whole[512], split[512], reference[512];
xtc_midi_event events[] = {{0, XTC_MIDI_NOTE_ON, 0, 69, 127},
{17, XTC_MIDI_NOTE_OFF, 0, 69, 0},
{17, XTC_MIDI_NOTE_ON, 0, 69, 127}};
assert(xtc_project_create(&p) == XTC_OK);
assert(xtc_midi_plan_create(p, 48000, &a) == XTC_OK);
assert(xtc_midi_plan_create(p, 48000, &b) == XTC_OK);
assert(xtc_midi_plan_render(a, 0, events, 3, whole, 64) == XTC_OK);
assert(xtc_midi_plan_render(b, 0, events, 1, split, 17) == XTC_OK);
events[1].offset = events[2].offset = 0;
assert(xtc_midi_plan_render(b, 17, events+1, 2, split+17, 47) == XTC_OK);
assert(!memcmp(whole, split, 64*sizeof(float)) && whole[17] == 0 && whole[18] > 0);
/* Equal offsets preserve caller order: on then off must end silent. */
events[0].type = XTC_MIDI_NOTE_ON; events[1].type = XTC_MIDI_NOTE_OFF;
assert(xtc_midi_plan_render(a, 100, events, 2, whole, 64) == XTC_OK);
for (size_t i = 0; i < 64; ++i) assert(whole[i] == 0);
events[0].channel = 16;
whole[0] = 123;
assert(xtc_midi_plan_render(a, 164, events, 1, whole, 64) == XTC_ERR_INVALID_ARGUMENT && whole[0] == 123);
xtc_midi_plan_destroy(a); xtc_midi_plan_destroy(b);
assert(xtc_project_add_track(p, "Keys", &track) == XTC_OK);
assert(xtc_project_add_midi_clip(p, track, 0, 960, &clip) == XTC_OK);
/* Adjacent repeated pitch and an overlapping same-pitch note remain independent. */
assert(xtc_project_add_note(p, clip, 0, 8, 69, 127, 0, &id) == XTC_OK);
assert(xtc_project_add_note(p, clip, 8, 8, 69, 127, 0, &id) == XTC_OK);
assert(xtc_project_add_note(p, clip, 4, 8, 69, 64, 0, &id) == XTC_OK);
assert(xtc_midi_plan_create(p, 48000, &a) == XTC_OK);
assert(xtc_midi_plan_create(p, 48000, &b) == XTC_OK);
assert(xtc_midi_plan_render(a, 0, NULL, 0, whole, 512) == XTC_OK);
assert(xtc_midi_plan_render(b, 0, NULL, 0, split, 200) == XTC_OK);
assert(xtc_midi_plan_render(b, 200, NULL, 0, split+200, 200) == XTC_OK);
assert(xtc_midi_plan_render(b, 400, NULL, 0, split+400, 112) == XTC_OK);
assert(!memcmp(whole, split, sizeof(whole)));
assert(xtc_midi_plan_render(b, 199, NULL, 0, reference, 201) == XTC_OK);
assert(!memcmp(whole+199, reference, 201*sizeof(float))); /* seek chase */
xtc_midi_plan_destroy(a); xtc_midi_plan_destroy(b);
/* 32 ends before 32 starts at the same timestamp fits voice bound. */
assert(xtc_project_remove_clip(p, clip) == XTC_OK);
assert(xtc_project_add_midi_clip(p, track, 0, 960, &clip) == XTC_OK);
for (unsigned int i = 0; i < 32; ++i) {
assert(xtc_project_add_note(p, clip, 0, 8, 60, 100, 0, &id) == XTC_OK);
assert(xtc_project_add_note(p, clip, 8, 8, 60, 100, 0, &id) == XTC_OK);
}
assert(xtc_midi_plan_create(p, 48000, &a) == XTC_OK);
xtc_midi_plan_destroy(a); xtc_project_destroy(p);
}
int main(void)
{
editing(); scheduling();
puts("Filtered transactional MIDI edits, triplet quantize, repeats, undo and deterministic ordering passed.");
return 0;
}