#define _POSIX_C_SOURCE 200809L #include "xtc/xtc_audio.h" #include "xtc/xtc_midi.h" #include "xtc/xtc_mixer.h" #include #include #include #include #include #include int main(void) { char directory[] = "/tmp/xtc-audio-edits-XXXXXX", path[256], song[256], missing[256]; unsigned char original[5000], final[5000]; size_t bytes; xtc_project *p = NULL, *q = NULL, *undo = NULL; xtc_history *history = NULL; xtc_audio_plan *a = NULL, *b = NULL; xtc_recorder *rec = NULL; xtc_waveform *wave = NULL; xtc_clip_info left, right, copy; uint64_t track, clip, split, duplicate, midi, unchanged = UINT64_MAX; size_t i, frames, bins, tracks, clips; unsigned int rate; float source[1003], before[1100], after[1100], blocks[1100], low, high; FILE *file; assert(mkdtemp(directory)); snprintf(path, sizeof(path), "%s/source.wav", directory); snprintf(song, sizeof(song), "%s/song.xtc", directory); snprintf(missing, sizeof(missing), "%s/missing.wav", directory); for (i = 0; i < 1003; ++i) source[i] = (float)(i % 31) / 32; assert(xtc_recorder_create(path, 48000, 1003, &rec) == XTC_OK); xtc_recorder_push(rec, source, 1003); assert(xtc_recorder_finish(rec) == XTC_OK); xtc_recorder_destroy(rec); file = fopen(path, "rb"); assert(file); bytes = fread(original, 1, sizeof(original), file); assert(fclose(file) == 0); assert(bytes == 4056); assert(xtc_waveform_create(path, &wave) == XTC_OK); assert(xtc_waveform_info(wave, &rate, &frames, &bins) == XTC_OK && rate == 48000 && frames == 1003 && bins == 1003); for (i = 0; i < bins; ++i) { assert(xtc_waveform_peak(wave, i, &low, &high) == XTC_OK && low == source[i] && high == source[i]); } low = 123; assert(xtc_waveform_peak(wave, bins, &low, &high) == XTC_ERR_INVALID_ARGUMENT && low == 123); assert(xtc_project_create(&p) == XTC_OK && xtc_project_create(&q) == XTC_OK); assert(xtc_project_add_track(p, "Audio", &track) == XTC_OK); assert(xtc_project_import_audio(p, track, path, 2, &clip) == XTC_OK); assert(xtc_project_find_clip(p, clip, &left) == XTC_OK && left.gain == 1 && left.length_tick == 41); assert(xtc_audio_plan_create(p, 48000, &a) == XTC_OK); assert(xtc_audio_plan_render(a, 0, before, 1100) == XTC_OK); assert(xtc_history_create(p, &history) == XTC_OK); assert(xtc_project_split_audio(p, clip, 2, &unchanged) == XTC_ERR_INVALID_ARGUMENT && unchanged == UINT64_MAX); assert(xtc_project_split_audio(p, clip, 43, &unchanged) == XTC_ERR_INVALID_ARGUMENT && unchanged == UINT64_MAX); assert(xtc_project_split_audio(p, clip, 22, &split) == XTC_OK); assert(xtc_project_find_clip(p, clip, &left) == XTC_OK && left.length_tick == 20 && left.offset_tick == 0); assert(xtc_project_find_clip(p, split, &right) == XTC_OK && right.offset_tick == 20 && right.length_tick == 21 && right.source_length_tick == 41); assert(!strcmp(left.asset_path, right.asset_path) && split != clip); assert(xtc_audio_plan_create(p, 48000, &b) == XTC_OK); assert(xtc_audio_plan_render(b, 0, after, 1100) == XTC_OK && !memcmp(before, after, sizeof(before))); xtc_audio_plan_destroy(b); assert(xtc_history_record(history, p) == XTC_OK && xtc_history_checkout(history, 0, &undo) == XTC_OK); assert(xtc_project_find_clip(undo, split, ©) == XTC_ERR_NOT_FOUND); assert(xtc_project_find_clip(undo, clip, ©) == XTC_OK && copy.length_tick == 41); xtc_project_destroy(undo); assert(xtc_project_set_clip_audio(p, split, 0.5f, 2, 3) == XTC_OK); assert(xtc_audio_plan_create(p, 48000, &b) == XTC_OK); assert(xtc_audio_plan_render(b, 0, after, 1100) == XTC_OK); /* Start 550, media end 1053; fade-in 50 frames, fade-out 75. */ assert(after[550] == 0 && after[1052] == 0 && after[1053] == 0); assert(after[600] == source[550] * 0.5f); assert(fabsf(after[551] - source[501]*0.5f/50) < 0.000001f); assert(fabsf(after[1051] - source[1001]*0.5f/75) < 0.000001f); for (i = 0; i < 1100;) { size_t n = i % 61 + 1; if (n > 1100-i) n = 1100-i; assert(xtc_audio_plan_render(b, i, blocks+i, n) == XTC_OK); i += n; } assert(!memcmp(after, blocks, sizeof(after))); assert(xtc_audio_plan_render(b, 551, blocks, 17) == XTC_OK && !memcmp(after+551, blocks, 17*sizeof(float))); xtc_audio_plan_destroy(b); assert(xtc_project_set_clip_audio(p, split, NAN, 0, 0) == XTC_ERR_INVALID_ARGUMENT); assert(xtc_project_set_clip_audio(p, split, 5, 0, 0) == XTC_ERR_INVALID_ARGUMENT); assert(xtc_project_set_clip_audio(p, split, 1, 42, 0) == XTC_ERR_INVALID_ARGUMENT); assert(xtc_project_duplicate_clip(p, split, track, 100, &duplicate) == XTC_OK); assert(xtc_project_find_clip(p, duplicate, ©) == XTC_OK && copy.gain == 0.5f && copy.fade_in_tick == 2 && copy.fade_out_tick == 3 && copy.offset_tick == 20); /* Splits retain outer fades and clear only inner explicit fades. */ assert(xtc_project_split_audio(p, duplicate, 110, &unchanged) == XTC_OK); assert(xtc_project_find_clip(p, duplicate, ©) == XTC_OK && copy.fade_in_tick == 2 && copy.fade_out_tick == 0); assert(xtc_project_find_clip(p, unchanged, ©) == XTC_OK && copy.fade_in_tick == 0 && copy.fade_out_tick == 3); assert(xtc_project_save(p, song) == XTC_OK && xtc_project_load(q, song) == XTC_OK); assert(xtc_project_find_clip(q, split, ©) == XTC_OK && copy.gain == 0.5f && copy.fade_in_tick == 2 && copy.fade_out_tick == 3); /* A bad v8 AUDIO record rejects the entire load. */ { char text[10000], *record; size_t length; file = fopen(song, "rb"); assert(file); length = fread(text, 1, sizeof(text)-1, file); assert(fclose(file) == 0); text[length] = 0; record = strstr(text, "AUDIO "); assert(record); memcpy(record+6, "7fc00000", 8); /* NaN */ file = fopen(song, "wb"); assert(file); assert(fwrite(text, 1, length, file) == length && fclose(file) == 0); assert(xtc_project_load(q, song) == XTC_ERR_FORMAT); assert(xtc_project_find_clip(q, split, ©) == XTC_OK && copy.gain == 0.5f); } /* Last source tick, very short audible tail and oversized overlapping fades. */ assert(xtc_project_edit_clip(q, split, track, 0, 1, 40) == XTC_OK); assert(xtc_project_set_clip_audio(q, split, 1, 41, 41) == XTC_OK); assert(xtc_audio_plan_create(q, 48000, &b) == XTC_OK); assert(xtc_audio_plan_render(b, 0, blocks, 25) == XTC_OK && blocks[0] == 0 && blocks[2] == 0 && blocks[3] == 0); xtc_audio_plan_destroy(b); assert(xtc_project_edit_clip(q, split, track, 0, 2, 40) == XTC_ERR_INVALID_ARGUMENT); assert(xtc_project_edit_clip(q, split, track, XTC_PROJECT_MAX_TICK, 1, 40) == XTC_ERR_INVALID_ARGUMENT); assert(xtc_project_add_midi_clip(p, track, 1000, 960, &midi) == XTC_OK); assert(xtc_project_split_audio(p, midi, 1001, &unchanged) == XTC_ERR_INVALID_STATE); assert(xtc_project_set_clip_audio(p, midi, 1, 0, 0) == XTC_ERR_INVALID_STATE); assert(xtc_project_get_counts(p, &tracks, &clips) == XTC_OK); for (i = clips; i < XTC_PROJECT_MAX_CLIPS; ++i) assert(xtc_project_add_clip(p, track, "Full", 0, 1, &midi) == XTC_OK); unchanged = UINT64_MAX; assert(xtc_project_split_audio(p, clip, 3, &unchanged) == XTC_ERR_CAPACITY && unchanged == UINT64_MAX); assert(xtc_project_find_clip(p, clip, ©) == XTC_OK && copy.length_tick == 20); file = fopen(path, "rb"); assert(file); assert(fread(final, 1, sizeof(final), file) == bytes && fclose(file) == 0); assert(!memcmp(original, final, bytes)); /* All editing leaves media byte-identical. */ assert(unlink(path) == 0); assert(xtc_waveform_peak(wave, 7, &low, &high) == XTC_OK && low == source[7]); assert(xtc_audio_plan_render(a, 0, blocks, 1100) == XTC_OK && !memcmp(before, blocks, sizeof(before))); xtc_waveform_destroy(wave); /* Grouped bins retain extrema, including the final odd-sized bin. */ { float long_source[2049]; for (i = 0; i < 2049; ++i) long_source[i] = (float)((int)(i % 17)-8) / 8; assert(xtc_recorder_create(path, 48000, 2049, &rec) == XTC_OK); xtc_recorder_push(rec, long_source, 2049); assert(xtc_recorder_finish(rec) == XTC_OK); xtc_recorder_destroy(rec); assert(xtc_waveform_create(path, &wave) == XTC_OK); assert(xtc_waveform_info(wave, &rate, &frames, &bins) == XTC_OK && bins == 1024); for (i = 0; i < bins; ++i) { size_t j, first = i*2049/bins, end = (i+1)*2049/bins; float expected_low = long_source[first], expected_high = expected_low; for (j = first+1; j < end; ++j) { if (long_source[j] < expected_low) expected_low = long_source[j]; if (long_source[j] > expected_high) expected_high = long_source[j]; } assert(xtc_waveform_peak(wave, i, &low, &high) == XTC_OK && low == expected_low && high == expected_high); } xtc_waveform_destroy(wave); assert(unlink(path) == 0); assert(xtc_recorder_create(path, 48000, 1, &rec) == XTC_OK); source[0] = 1; xtc_recorder_push(rec, source, 1); assert(xtc_recorder_finish(rec) == XTC_OK); xtc_recorder_destroy(rec); assert(xtc_waveform_create(path, &wave) == XTC_OK); assert(xtc_waveform_info(wave, &rate, &frames, &bins) == XTC_OK && bins == 1 && frames == 1); xtc_waveform_destroy(wave); assert(xtc_project_set_clip_asset(q, split, path) == XTC_OK); assert(xtc_project_edit_clip(q, split, track, 0, 1, 0) == XTC_OK); assert(xtc_audio_plan_create(q, 48000, &b) == XTC_OK); assert(xtc_audio_plan_render(b, 0, blocks, 25) == XTC_OK && blocks[0] == 0 && blocks[1] == 0); xtc_audio_plan_destroy(b); assert(unlink(path) == 0); } assert(xtc_waveform_create(missing, &wave) != XTC_OK && wave == NULL); xtc_audio_plan_destroy(a); xtc_history_destroy(history); xtc_project_destroy(p); xtc_project_destroy(q); assert(unlink(song) == 0 && rmdir(directory) == 0); puts("Non-destructive audio split/gain/fades/cache, bounds, persistence and undo passed."); return 0; }