137 lines
7.7 KiB
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;
|
|
}
|