#include "xtc/xtc_midi.h" #include "xtc/xtc_mixer.h" #include #include #include 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; }