#define _POSIX_C_SOURCE 200809L #include "xtc/xtc_dsp.h" #include "xtc/xtc_audio.h" #include "xtc/xtc_midi.h" #include #include #include #include #include #include #include static void timing(void) { unsigned int rates[] = {1, 8000, 44100, 48000, 96000, 192000, 768000}; unsigned int tempos[] = {20000, 120000, 123457, 300000}; xtc_project *p = NULL; size_t r, t; assert(xtc_project_create(&p) == XTC_OK); for (r = 0; r < sizeof(rates)/sizeof(*rates); ++r) for (t = 0; t < sizeof(tempos)/sizeof(*tempos); ++t) { uint64_t tick, sample, back, expected, previous = 0; assert(xtc_project_set_tempo(p, tempos[t]) == XTC_OK); for (tick = 0; tick <= XTC_PROJECT_MAX_TICK; tick += 9973) { expected = tick * rates[r] * UINT64_C(60000) / ((uint64_t)tempos[t] * 960); assert(xtc_project_tick_to_sample(p, tick, rates[r], &sample) == XTC_OK); assert(sample == expected && sample >= previous); previous = sample; assert(xtc_project_sample_to_tick(p, sample, rates[r], &back) == XTC_OK); assert(back == sample * tempos[t] * 960 / ((uint64_t)rates[r] * 60000)); } assert(xtc_project_tick_to_sample(p, XTC_PROJECT_MAX_TICK, rates[r], &sample) == XTC_OK); } { uint64_t unchanged = 123; assert(xtc_project_sample_to_tick(p, UINT64_MAX, 1, &unchanged) == XTC_ERR_CAPACITY && unchanged == 123); assert(xtc_project_set_tempo(p, 0) == XTC_ERR_INVALID_ARGUMENT); } xtc_project_destroy(p); } static void ramps(void) { xtc_gain_ramp a, b; float whole[257], split[257], right[257]; size_t i; assert(xtc_gain_ramp_reset(&a, 0) == XTC_OK); b = a; assert(xtc_gain_ramp_target(&a, 2, 17) == XTC_OK); assert(xtc_gain_ramp_target(&b, 2, 17) == XTC_OK); for (i = 0; i < 257; ++i) whole[i] = split[i] = right[i] = 1; assert(xtc_gain_ramp_process(&a, whole, right, 257) == XTC_OK); for (i = 0; i < 257; ++i) assert(xtc_gain_ramp_process(&b, split + i, NULL, 1) == XTC_OK); assert(memcmp(whole, split, sizeof(whole)) == 0 && memcmp(whole, right, sizeof(whole)) == 0); assert(whole[16] == 2 && whole[256] == 2); /* Headroom, no implicit clipping. */ assert(xtc_gain_ramp_target(&a, 0, 3) == XTC_OK); whole[0] = whole[1] = whole[2] = 1; assert(xtc_gain_ramp_process(&a, whole, NULL, 3) == XTC_OK && whole[2] == 0); assert(xtc_gain_ramp_target(&a, NAN, 1) == XTC_ERR_INVALID_ARGUMENT && a.target == 0); assert(xtc_dsp_finite(NAN) == 0 && xtc_dsp_finite(INFINITY) == 0); assert(xtc_dsp_finite(nextafterf(0, 1)) == 0 && xtc_dsp_finite(FLT_MIN) == FLT_MIN); assert(xtc_dsp_finite(4) == 4); assert(xtc_gain_ramp_reset(&a, 0) == XTC_OK); assert(xtc_gain_ramp_target(&a, 1, 4) == XTC_OK); whole[0] = 1; assert(xtc_gain_ramp_process(&a, whole, NULL, 1) == XTC_OK && whole[0] == 0.25f); assert(xtc_gain_ramp_target(&a, -0.25f, 2) == XTC_OK); whole[0] = whole[1] = 1; assert(xtc_gain_ramp_process(&a, whole, NULL, 2) == XTC_OK && whole[0] == 0 && whole[1] == -0.25f); assert(xtc_gain_ramp_target(&a, 1, 0) == XTC_OK && a.current == 1); whole[0] = 9; assert(xtc_gain_ramp_process(&a, whole, whole, 1) == XTC_ERR_INVALID_ARGUMENT && whole[0] == 9); } static void events(void) { xtc_project *p = NULL; xtc_midi_plan *a = NULL, *b = NULL; xtc_midi_event events[] = {{7, XTC_MIDI_NOTE_ON, 0, 69, 127}, {33, XTC_MIDI_NOTE_OFF, 0, 69, 0}}; float whole[257], split[257], right[257]; size_t i; assert(xtc_project_create(&p) == XTC_OK); assert(xtc_midi_plan_create(p, 48000, &a) == XTC_OK && xtc_midi_plan_create(p, 48000, &b) == XTC_OK); assert(xtc_midi_plan_render_stereo(a, 0, events, 2, whole, right, 257) == XTC_OK); for (i = 0; i < 257; ++i) { xtc_midi_event event; const xtc_midi_event *e = NULL; if (i == 7 || i == 33) { event = events[i == 7 ? 0 : 1]; event.offset = 0; e = &event; } assert(xtc_midi_plan_render(b, i, e, e ? 1U : 0U, split + i, 1) == XTC_OK); } assert(memcmp(whole, split, sizeof(whole)) == 0 && memcmp(whole, right, sizeof(whole)) == 0); for (i = 0; i <= 7; ++i) assert(whole[i] == 0); assert(whole[8] > 0); for (i = 33; i < 257; ++i) assert(whole[i] == 0); /* Equal-offset input events retain caller order: off then on starts a voice. */ events[0].offset = events[1].offset = 0; xtc_midi_plan_reset(a); assert(xtc_midi_plan_render(a, 0, events, 2, whole, 257) == XTC_OK && whole[1] == 0); { xtc_midi_event temp = events[0]; events[0] = events[1]; events[1] = temp; } xtc_midi_plan_reset(a); assert(xtc_midi_plan_render(a, 0, events, 2, whole, 257) == XTC_OK && whole[1] > 0); xtc_midi_plan_destroy(a); xtc_midi_plan_destroy(b); xtc_project_destroy(p); } static void clips(void) { xtc_project *p = NULL; xtc_audio_plan *a = NULL; xtc_midi_plan *old = NULL, *fresh = NULL; xtc_recorder *rec = NULL; uint64_t track, clip, note; float input[1003], whole[4097], split[4097], midi[4097]; size_t i, offset; char dir[] = "/tmp/xtc-quality-XXXXXX", path[256]; assert(mkdtemp(dir)); snprintf(path, sizeof(path), "%s/source.wav", dir); for (i = 0; i < 1003; ++i) input[i] = 1; assert(xtc_recorder_create(path, 48000, 1003, &rec) == XTC_OK); xtc_recorder_push(rec, input, 1003); assert(xtc_recorder_finish(rec) == XTC_OK); xtc_recorder_destroy(rec); assert(xtc_project_create(&p) == XTC_OK && xtc_project_add_track(p, "Test", &track) == XTC_OK); assert(xtc_project_import_audio(p, track, path, 1, &clip) == XTC_OK); assert(xtc_audio_plan_create(p, 48000, &a) == XTC_OK); assert(xtc_audio_plan_set_fade_frames(a, 8) == XTC_OK); assert(xtc_audio_plan_render(a, 0, whole, 4097) == XTC_OK); for (offset = 0; offset < 4097;) { size_t n = offset % 131 + 1; if (n > 4097 - offset) n = 4097 - offset; assert(xtc_audio_plan_render(a, offset, split + offset, n) == XTC_OK); offset += n; } assert(memcmp(whole, split, sizeof(whole)) == 0); assert(whole[24] == 0 && whole[25] == 0 && whole[26] == 0.125f && whole[33] == 1); /* Fade at the true media end, before the imported clip's rounded-up tick end. */ assert(whole[1026] == 0.125f && whole[1027] == 0 && whole[1028] == 0); assert(xtc_audio_plan_render(a, 26, split, 1) == XTC_OK && split[0] == whole[26]); { xtc_audio_plan *resampled = NULL; assert(xtc_audio_plan_create(p, 8000, &resampled) == XTC_OK); assert(xtc_audio_plan_set_fade_frames(resampled, 8) == XTC_OK); assert(xtc_audio_plan_render(resampled, 0, split, 200) == XTC_OK); assert(split[4] == 0 && split[5] == 0.125f && split[170] == 0.125f && split[171] == 0 && split[172] == 0); xtc_audio_plan_destroy(resampled); } assert(xtc_project_add_midi_clip(p, track, 1, 100, &clip) == XTC_OK); assert(xtc_project_add_note(p, clip, 0, 80, 69, 127, 0, ¬e) == XTC_OK); assert(xtc_midi_plan_create(p, 48000, &old) == XTC_OK); assert(xtc_midi_plan_set_fade_frames(old, 8) == XTC_OK); assert(xtc_midi_plan_render(old, 0, NULL, 0, whole, 4097) == XTC_OK); xtc_midi_plan_reset(old); for (offset = 0; offset < 4097;) { size_t n = offset % 131 + 1; if (n > 4097 - offset) n = 4097 - offset; assert(xtc_midi_plan_render(old, offset, NULL, 0, split + offset, n) == XTC_OK); offset += n; } assert(memcmp(whole, split, sizeof(whole)) == 0); assert(whole[25] == 0 && whole[26] > 0 && whole[2024] == 0 && whole[2025] == 0); assert(xtc_project_set_tempo(p, 60000) == XTC_OK); { xtc_audio_plan *new_audio = NULL; assert(xtc_audio_plan_create(p, 48000, &new_audio) == XTC_OK); assert(xtc_audio_plan_set_fade_frames(new_audio, 8) == XTC_OK); assert(xtc_audio_plan_render(new_audio, 0, midi, 4097) == XTC_OK); assert(midi[26] == 0 && midi[50] == 0 && midi[51] == 0.125f); assert(xtc_audio_plan_render(a, 26, split, 1) == XTC_OK && split[0] == 0.125f); xtc_audio_plan_destroy(new_audio); } assert(xtc_midi_plan_create(p, 48000, &fresh) == XTC_OK); assert(xtc_midi_plan_render(fresh, 0, NULL, 0, midi, 4097) == XTC_OK); assert(midi[26] == 0 && midi[51] > 0); /* Newly prepared plan follows new tempo. */ xtc_midi_plan_reset(old); assert(xtc_midi_plan_render(old, 0, NULL, 0, split, 4097) == XTC_OK); assert(memcmp(whole, split, sizeof(whole)) == 0); /* Existing snapshot unchanged. */ xtc_audio_plan_destroy(a); xtc_midi_plan_destroy(old); xtc_midi_plan_destroy(fresh); xtc_project_destroy(p); assert(unlink(path) == 0 && rmdir(dir) == 0); } static void layouts(void) { size_t sizes[] = {1, 2, 3, 17, 257, 4097, 65535, 65536}, i, j, node; xtc_graph *g = NULL; xtc_plan *plan = NULL; float *l = malloc(65536 * sizeof(float)), *r = malloc(65536 * sizeof(float)); assert(l && r); assert(xtc_graph_create(1, &g) == XTC_OK && xtc_graph_add_gain(g, 2, &node) == XTC_OK); assert(xtc_graph_compile(g, 48000, 65536, &plan) == XTC_OK); for (i = 0; i < sizeof(sizes)/sizeof(*sizes); ++i) { for (j = 0; j < sizes[i]; ++j) l[j] = j % 3 == 0 ? nextafterf(0, 1) : j % 3 == 1 ? 2 : INFINITY; assert(xtc_plan_process_stereo(plan, l, NULL, NULL, NULL, NULL, l, r, sizes[i]) == XTC_OK); for (j = 0; j < sizes[i]; ++j) assert(l[j] == (j % 3 == 1 ? 4 : 0) && r[j] == l[j]); assert(xtc_plan_process_stereo(plan, NULL, NULL, NULL, NULL, NULL, l, r, sizes[i]) == XTC_OK); for (j = 0; j < sizes[i]; ++j) assert(l[j] == 0 && r[j] == 0); } assert(xtc_plan_process_stereo(plan, NULL, NULL, NULL, NULL, NULL, l, l, 1) == XTC_ERR_INVALID_ARGUMENT); free(l); free(r); xtc_plan_destroy(plan); xtc_graph_destroy(g); } int main(void) { timing(); ramps(); events(); clips(); layouts(); puts("Timing, sample-offset events, tempo snapshots, fades, ramps and block invariance passed."); return 0; }