201 lines
10 KiB
C
201 lines
10 KiB
C
#define _POSIX_C_SOURCE 200809L
|
|
#include "xtc/xtc_dsp.h"
|
|
#include "xtc/xtc_audio.h"
|
|
#include "xtc/xtc_midi.h"
|
|
#include <assert.h>
|
|
#include <float.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
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;
|
|
}
|