xtc/tests/unit/test_audio.c

169 lines
9.2 KiB
C
Raw Normal View History

#define _POSIX_C_SOURCE 200809L
#include "xtc/xtc_audio.h"
#include <assert.h>
#include <math.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static void le(unsigned char *p, uint32_t value, size_t count)
{ size_t i; for (i = 0; i < count; ++i) { p[i] = (unsigned char)(value & 255U); value >>= 8U; } }
static void fixture(const char *path, unsigned int bits, unsigned int channels, unsigned int rate)
{
unsigned char header[44] = {0}, sample[8] = {0};
size_t i; FILE *f = fopen(path, "wb");
unsigned int align = channels * bits / 8;
assert(f);
memcpy(header, "RIFF", 4); le(header + 4, 36 + 480 * align, 4);
memcpy(header + 8, "WAVEfmt ", 8); le(header + 16, 16, 4);
le(header + 20, 1, 2); le(header + 22, channels, 2);
le(header + 24, rate, 4); le(header + 28, rate * align, 4);
le(header + 32, align, 2); le(header + 34, bits, 2);
memcpy(header + 36, "data", 4); le(header + 40, 480 * align, 4);
assert(fwrite(header, 1, 44, f) == 44);
le(sample, UINT32_C(1) << (bits - 3), bits / 8); /* +0.25 */
if (channels == 2) le(sample + bits / 8, UINT32_C(3) << (bits - 3), bits / 8); /* +0.75 */
for (i = 0; i < 480; ++i) assert(fwrite(sample, 1, align, f) == align);
assert(fclose(f) == 0);
}
typedef struct { xtc_recorder *r; unsigned int complete; } stress;
static void *producer(void *context)
{
stress *s = context;
float samples[32]; size_t i, b;
for (i = 0; i < 32; ++i) samples[i] = (float)i / 32;
for (b = 0; b < 10000; ++b) xtc_recorder_push(s->r, samples, 32);
__atomic_store_n(&s->complete, 1, __ATOMIC_RELEASE);
return NULL;
}
int main(void)
{
char directory[] = "/tmp/xtc-audio-XXXXXX", wave[256], take[256], project[256], moved[256];
xtc_project *p = NULL, *q = NULL;
xtc_audio_plan *plan = NULL;
xtc_recorder *rec = NULL;
uint64_t track, clip, frames;
xtc_clip_info info;
float output[64], input[64], timeline[64];
xtc_plan *graph_plan = NULL;
unsigned int bits;
size_t i;
assert(mkdtemp(directory));
snprintf(wave, sizeof(wave), "%s/input.wav", directory);
snprintf(take, sizeof(take), "%s/take.wav", directory);
snprintf(project, sizeof(project), "%s/song.xtc", directory);
snprintf(moved, sizeof(moved), "%s/moved.wav", directory);
for (bits = 16; bits <= 32; bits += 8) {
fixture(wave, bits, 2, 48000);
assert(xtc_project_create(&p) == XTC_OK);
assert(xtc_project_add_track(p, "Audio", &track) == XTC_OK);
assert(xtc_project_import_audio(p, track, wave, 960, &clip) == XTC_OK);
assert(xtc_project_find_clip(p, clip, &info) == XTC_OK && info.length_tick == 20);
assert(xtc_audio_plan_create(p, 48000, &plan) == XTC_OK && xtc_audio_plan_issues(plan, NULL) == 0);
assert(xtc_audio_plan_render(plan, 23998, output, 8) == XTC_OK);
assert(output[0] == 0 && output[1] == 0 && output[2] == 0.5f && output[7] == 0.5f);
assert(xtc_graph_compile(xtc_project_graph(p), 48000, 64, &graph_plan) == XTC_OK);
assert(xtc_plan_process_audio(graph_plan, NULL, output, timeline, 8) == XTC_OK && timeline[2] == 0.5f);
xtc_plan_destroy(graph_plan);
xtc_audio_plan_destroy(plan);
assert(xtc_audio_plan_create(p, 24000, &plan) == XTC_OK);
assert(xtc_audio_plan_render(plan, 11999, output, 8) == XTC_OK && output[0] == 0 && output[1] == 0.5f);
xtc_audio_plan_destroy(plan);
/* Trim offset and overlap summing. */
assert(xtc_project_edit_clip(p, clip, track, 0, 10, 10) == XTC_OK);
assert(xtc_project_import_audio(p, track, wave, 0, &clip) == XTC_OK);
assert(xtc_audio_plan_create(p, 48000, &plan) == XTC_OK);
assert(xtc_audio_plan_render(plan, 0, output, 64) == XTC_OK && output[0] == 1.0f);
assert(rename(wave, moved) == 0);
assert(xtc_audio_plan_render(plan, 0, output, 64) == XTC_OK && output[0] == 1.0f); /* no callback file access */
xtc_audio_plan_destroy(plan);
assert(xtc_audio_plan_create(p, 48000, &plan) == XTC_OK && xtc_audio_plan_issues(plan, NULL) == 1);
assert(xtc_audio_plan_render(plan, 0, output, 64) == XTC_OK && output[0] == 0);
xtc_audio_plan_destroy(plan);
assert(xtc_project_set_clip_asset(p, clip, moved) == XTC_OK);
assert(xtc_audio_plan_create(p, 48000, &plan) == XTC_OK && xtc_audio_plan_issues(plan, NULL) == 1);
assert(xtc_audio_plan_render(plan, 0, output, 64) == XTC_OK && output[0] == 0.5f);
xtc_audio_plan_destroy(plan);
assert(unlink(moved) == 0);
xtc_project_destroy(p);
}
/* Recording ring wraps, exact samples survive WAV -> project -> reopen. */
for (i = 0; i < 64; ++i) input[i] = i % 2 ? -0.5f : 0.25f;
assert(xtc_recorder_create_replace(take, 48000, 128, 1, &rec) == XTC_OK);
for (i = 0; i < 10; ++i) {
xtc_recorder_push(rec, input, 64);
assert(xtc_recorder_drain(rec) == XTC_OK);
}
assert(xtc_recorder_status(rec, &frames) == XTC_OK && frames == 640);
assert(xtc_recorder_finish(rec) == XTC_OK); xtc_recorder_destroy(rec);
assert(xtc_project_create(&p) == XTC_OK && xtc_project_create(&q) == XTC_OK);
assert(xtc_project_add_track(p, "Take", &track) == XTC_OK);
assert(xtc_project_import_audio(p, track, take, 0, &clip) == XTC_OK);
assert(xtc_project_save(p, project) == XTC_OK && xtc_project_load(q, project) == XTC_OK);
assert(xtc_audio_plan_create(q, 48000, &plan) == XTC_OK);
assert(xtc_audio_plan_render(plan, 0, output, 64) == XTC_OK && memcmp(input, output, sizeof(input)) == 0);
assert(xtc_audio_plan_render(plan, 639, output, 8) == XTC_OK && output[0] == -0.5f && output[1] == 0);
xtc_audio_plan_destroy(plan);
/* Truncated/unsupported imports do not change project IDs/clip counts. */
{
FILE *f = fopen(wave, "wb"); size_t tracks, clips; assert(f); fputs("RIFF", f); assert(fclose(f) == 0);
assert(xtc_project_import_audio(p, track, wave, 0, &clip) == XTC_ERR_FORMAT);
assert(xtc_project_get_counts(p, &tracks, &clips) == XTC_OK && clips == 1);
}
{
unsigned char bytes[2048];
FILE *f;
size_t count, n, tracks, clips;
fixture(wave, 16, 2, 48000);
f = fopen(wave, "rb"); assert(f);
count = fread(bytes, 1, sizeof(bytes), f); assert(fclose(f) == 0);
for (n = 0; n < count; ++n) {
f = fopen(wave, "wb"); assert(f);
assert(fwrite(bytes, 1, n, f) == n && fclose(f) == 0);
assert(xtc_project_import_audio(p, track, wave, 0, &clip) != XTC_OK);
}
assert(xtc_project_get_counts(p, &tracks, &clips) == XTC_OK && clips == 1);
bytes[20] = 6; /* Unsupported codec. */
f = fopen(wave, "wb"); assert(f);
assert(fwrite(bytes, 1, count, f) == count && fclose(f) == 0);
assert(xtc_project_import_audio(p, track, wave, 0, &clip) == XTC_ERR_UNSUPPORTED);
fixture(wave, 32, 1, 48000);
f = fopen(wave, "r+b"); assert(f);
assert(fseek(f, 20, SEEK_SET) == 0 && fputc(3, f) != EOF);
le(bytes, UINT32_C(0x7fc00000), 4);
assert(fseek(f, 44, SEEK_SET) == 0 && fwrite(bytes, 1, 4, f) == 4 && fclose(f) == 0);
assert(xtc_project_import_audio(p, track, wave, 0, &clip) == XTC_ERR_FORMAT);
}
xtc_project_destroy(p); xtc_project_destroy(q);
/* Overflow is sticky, partial file recoverable, destination unchanged until finish. */
assert(xtc_recorder_create_replace(take, 48000, 64, 1, &rec) == XTC_OK);
xtc_recorder_push(rec, input, 64); xtc_recorder_push(rec, input, 1);
assert(xtc_recorder_status(rec, &frames) == XTC_ERR_CAPACITY && frames == 64);
assert(xtc_recorder_finish(rec) == XTC_ERR_CAPACITY); xtc_recorder_destroy(rec);
assert(xtc_recorder_create_replace(take, 48000, 64, 1, &rec) == XTC_OK);
xtc_recorder_push(rec, NULL, 1);
assert(xtc_recorder_status(rec, &frames) == XTC_ERR_IO);
xtc_recorder_destroy(rec);
/* Actual concurrent producer/control consumer; no GUI or JACK required. */
{
stress s = {0}; pthread_t thread;
assert(xtc_recorder_create_replace(take, 48000, 524288, 1, &rec) == XTC_OK); s.r = rec;
assert(pthread_create(&thread, NULL, producer, &s) == 0);
while (!__atomic_load_n(&s.complete, __ATOMIC_ACQUIRE)) assert(xtc_recorder_drain(rec) == XTC_OK);
assert(pthread_join(thread, NULL) == 0);
assert(xtc_recorder_finish(rec) == XTC_OK);
assert(xtc_recorder_status(rec, &frames) == XTC_OK && frames == 320000);
xtc_recorder_destroy(rec);
assert(xtc_project_create(&p) == XTC_OK);
assert(xtc_project_add_track(p, "Concurrent take", &track) == XTC_OK);
assert(xtc_project_import_audio(p, track, take, 0, &clip) == XTC_OK);
assert(xtc_audio_plan_create(p, 48000, &plan) == XTC_OK);
assert(xtc_audio_plan_render(plan, 319936, output, 64) == XTC_OK);
for (i = 0; i < 64; ++i) assert(output[i] == (float)(i % 32) / 32);
xtc_audio_plan_destroy(plan); xtc_project_destroy(p);
}
assert(unlink(wave) == 0 && unlink(take) == 0 && unlink(project) == 0 && rmdir(directory) == 0);
puts("Audio import/render/trim/resample/missing assets and recording roundtrip/overflow/concurrency passed.");
return 0;
}