Phase 12: Improve audio transitions and verify sample-accurate timing

This commit is contained in:
Johannes Findeisen 2026-10-27 03:37:19 +01:00
commit 05140c2e93
29 changed files with 795 additions and 33 deletions

View file

@ -202,3 +202,13 @@ window, reopens in another window, compares stereo rendering exactly, verifies
playhead/meters and pause/resume/seek/home, and continues editing/saving.
Mock GUI tests inject failed stop/seek/project preparation and recording guards.
See [WORKFLOW.md](../../docs/WORKFLOW.md) for acceptance details and limitations.
## Phase 12 transitions
Backend fault tests verify start/stop ramp samples, repeated requests, callback
completion, silent/held blocks before collection and deactivation error/retry.
GUI tests keep immediate mocked stop semantics; real workflow/live GUI tests
wait for asynchronous fade acknowledgement. `test_quality` in the unit suite
checks exact source envelopes/timing and variable blocks, including tempo
snapshot changes. See [AUDIO_QUALITY.md](../../docs/AUDIO_QUALITY.md) for commands,
latency limitations and the pending listening checklist.

View file

@ -106,6 +106,10 @@ xtc_result xtc_backend_close(xtc_backend *backend)
}
xtc_result xtc_backend_start(xtc_backend *backend)
{ CHECK(backend == &handle); ++starts; if (start_result == XTC_OK) { handle.running = 1; engine_status.active_revision = engine_status.submitted_revision; } return start_result; }
xtc_result xtc_backend_set_transition_frames(xtc_backend *, size_t frames)
{ CHECK(frames == 240); return XTC_OK; }
xtc_result xtc_backend_request_stop(xtc_backend *backend)
{ return xtc_backend_stop(backend); }
xtc_result xtc_backend_stop(xtc_backend *backend)
{ CHECK(backend == &handle); ++stops; if (stop_result == XTC_OK) handle.running = 0; return stop_result; }
xtc_result xtc_backend_get_status(const xtc_backend *backend, xtc_backend_status *status)

View file

@ -83,7 +83,7 @@ int main(int argc, char **argv)
unsigned int rate = 0U, frames = 0U, blocks = 0U;
CHECK(std::sscanf(status->label(), "JACK | %u Hz | %u frames | stereo | %u blocks", &rate, &frames, &blocks) == 3);
CHECK(rate > 0U && frames > 0U && blocks > 0U);
stop->do_callback();
stop->do_callback(); pump();
CHECK(find(window, "ready / stopped") != nullptr);
uint64_t paused = timeline->position(); pump();
CHECK(timeline->position() == paused);
@ -93,7 +93,7 @@ int main(int argc, char **argv)
play->do_callback();
pump();
CHECK(find(window, "running — modular graph") != nullptr);
stop->do_callback();
stop->do_callback(); pump();
char directory[] = "/tmp/xtc-live-record-XXXXXX"; CHECK(mkdtemp(directory) != nullptr);
std::string take = std::string(directory) + "/take.wav";
std::string song = std::string(directory) + "/song.xtc";

View file

@ -66,6 +66,8 @@ static std::vector<float> render(const xtc_project *project)
CHECK(xtc_graph_compile(xtc_project_graph(project), 48000, 256, &graph) == XTC_OK);
CHECK(xtc_audio_plan_create(project, 48000, &audio) == XTC_OK);
CHECK(xtc_midi_plan_create(project, 48000, &midi) == XTC_OK);
CHECK(xtc_audio_plan_set_fade_frames(audio, 240) == XTC_OK);
CHECK(xtc_midi_plan_set_fade_frames(midi, 240) == XTC_OK);
std::vector<float> result;
for (uint64_t pos = 0; pos < 96000; pos += 256) {
float al[256], ar[256], ml[256], mr[256], left[256], right[256];
@ -113,7 +115,7 @@ int main(int argc, char **argv)
CHECK(arranged.start_tick == 1920 && arranged.length_tick == 3840);
CHECK(window.import_audio(wave.c_str()) == XTC_OK);
expected = render(timeline->project());
CHECK(expected[0] > 0); // Imported audio is routed without graph-file edits.
CHECK(expected[0] == 0 && expected[2] > 0); // Imported audio is routed without graph-file edits.
CHECK(expected[2 * 48001] != 0); // Arranged notes also reach the output.
CHECK(xtc_graph_get_counts(xtc_project_graph(timeline->project()), &nodes, &edges) == XTC_OK);
CHECK(nodes == 3 && edges == 2); // Audio and instrument -> output.
@ -144,12 +146,13 @@ int main(int argc, char **argv)
auto *right_meter = dynamic_cast<Fl_Progress *>(button(window, "Output R"));
CHECK(left_meter && right_meter && left_meter->value() > 0 && right_meter->value() > 0);
CHECK(window.pause_playback() == XTC_OK);
pump(); // Fade completion is acknowledged by the GUI timer.
CHECK(left_meter->value() == 0 && right_meter->value() == 0);
uint64_t paused = timeline->position(); pump();
CHECK(timeline->position() == paused);
press(window, "Play"); pump();
CHECK(timeline->position() > paused);
press(window, "Stop");
press(window, "Stop"); pump();
paused = timeline->position(); pump(); CHECK(timeline->position() == paused);
press(window, "To start"); CHECK(timeline->position() == 0);
press(window, "Add MIDI clip");