Phase 24: add mix buses, coherent peak meters and master clipping indicators

This commit is contained in:
Johannes Findeisen 2026-10-27 16:15:34 +01:00
commit 2fd7724065
32 changed files with 632 additions and 61 deletions

View file

@ -144,6 +144,11 @@ xtc_result xtc_backend_submit(xtc_backend *backend, const xtc_graph *graph)
return XTC_OK;
}
xtc_result xtc_backend_panic(xtc_backend *) { xtc_midi_plan_panic(handle.midi, engine_status.sample_position); return XTC_OK; }
xtc_result xtc_backend_read_meters(xtc_backend *, xtc_meter_snapshot *out)
{
*out = {}; out->master_left = engine_status.peak_left; out->master_right = engine_status.peak_right;
return XTC_OK;
}
xtc_result xtc_backend_prepare_project(xtc_backend *backend, const xtc_project *project)
{
xtc_audio_plan *plan = nullptr;
@ -602,6 +607,27 @@ int main(int argc, char **argv)
CHECK((sx != gx || sy != gy) && (sx != ox || sy != oy) && (gx != ox || gy != oy));
window.do_callback();
}
{
reset(); MainWindow window(false);
auto *sequence = timeline(&window); auto *mixer = window.mixer();
CHECK(sequence->add_track() == XTC_OK);
CHECK(mixer->add_bus() == XTC_OK);
uint64_t bus = mixer->selected_track();
xtc_track_info track{};
CHECK(xtc_project_get_track(sequence->project(),1,&track) == XTC_OK && track.is_bus);
CHECK(mixer->mix(.5f,1,0,1) == XTC_OK);
CHECK(mixer->select_track(0) && mixer->route(bus) == XTC_OK);
CHECK(xtc_project_get_track(sequence->project(),0,&track) == XTC_OK && track.bus_id == bus);
CHECK(window.undo() == XTC_OK);
CHECK(xtc_project_get_track(sequence->project(),0,&track) == XTC_OK && track.bus_id == UINT64_MAX);
CHECK(window.undo(true) == XTC_OK);
CHECK(xtc_project_get_track(sequence->project(),0,&track) == XTC_OK && track.bus_id == bus);
mixer->peaks(1.25f,.5f); mixer->track_peaks(.5f,.25f);
CHECK(mixer->master_clipped() && contains(&window,"dBFS CLIP"));
mixer->peaks(0,0); CHECK(mixer->master_clipped());
mixer->reset_meters(); CHECK(!mixer->master_clipped());
window.do_callback();
}
if (const char *module = std::getenv("XTC_TEST_PLUGIN")) {
reset();
MainWindow window(false);

View file

@ -37,7 +37,7 @@ def main():
assert os.access(root / 'bin/xtc-check', os.X_OK)
run([str(root / 'bin/xtc-check')])
diagnose = subprocess.check_output([str(root / 'bin/xtc-check'), '--diagnose'], text=True)
assert 'Project formats: 1..8' in diagnose and 'JACK compiled:' in diagnose
assert 'Project formats: 1..9' in diagnose and 'JACK compiled:' in diagnose
# Upgrade/repair repeats the same owned-file install and keeps unrelated data.
(root / 'include/xtc/xtc.h').write_text('old installation marker\n')
run(make + ['install'] + variables, env=env)

View file

@ -18,12 +18,13 @@ int main(void)
assert(mkdtemp(root));
assert(xtc_project_create(&p) == XTC_OK);
snprintf(file,sizeof(file),"%s/migration.xtc",root);
for (version = 1; version <= 7; ++version) {
for (version = 1; version <= 8; ++version) {
xtc_track_info t; xtc_clip_info c; size_t notes, points;
snprintf(old,sizeof(old),"tests/fixtures/project-v%u.xtc",version);
assert(xtc_project_load(p,old) == XTC_OK);
assert(xtc_project_get_track(p,0,&t) == XTC_OK && !strcmp(t.name,"Keys") && t.gain == 1);
assert(xtc_project_get_clip(p,0,&c) == XTC_OK && c.midi == (version >= 3));
assert(!t.is_bus && t.bus_id == UINT64_MAX);
assert(c.gain == 1 && c.fade_in_tick == 0 && c.fade_out_tick == 0);
assert(xtc_project_note_count(p,&notes) == XTC_OK && notes == (size_t)(version >= 3));
assert(xtc_project_automation_count(p,&points) == XTC_OK && points == (size_t)(version >= 4));
@ -84,6 +85,6 @@ int main(void)
assert(xtc_project_load(p,file) == XTC_ERR_FORMAT);
{ xtc_clip_info c; assert(xtc_project_find_clip(p,clip,&c) == XTC_OK && c.asset_path[0]); } }
xtc_project_destroy(p); unlink(file); rmdir(moved);
puts("v1-v7 migration, current folder relocation, cwd independence and missing-media preservation passed.");
puts("v1-v8 migration, current folder relocation, cwd independence and missing-media preservation passed.");
return 0;
}

View file

@ -30,7 +30,7 @@ static Fl_Widget *button(Fl_Group &group, const std::string &label)
{
for (int i = 0; i < group.children(); ++i) {
auto *child = group.child(i);
if (child->label() && label == child->label()) return child;
if (child->label() && (label == child->label() || (label.compare(0, 7, "Master ") == 0 && std::string(child->label()).compare(0, label.size(), label) == 0))) return child;
if (auto *nested = dynamic_cast<Fl_Group *>(child))
if (auto *match = button(*nested, label)) return match;
}
@ -142,12 +142,13 @@ int main(int argc, char **argv)
press(window, "Play"); pump();
CHECK(!button(window, "Play")->active() && button(window, "Pause")->active());
CHECK(timeline->position() > 1920);
auto *left_meter = dynamic_cast<Fl_Progress *>(button(window, "Output L"));
auto *right_meter = dynamic_cast<Fl_Progress *>(button(window, "Output R"));
CHECK(left_meter && right_meter && left_meter->value() > 0 && right_meter->value() > 0);
auto *left_meter = dynamic_cast<Fl_Progress *>(button(window, "Master L"));
auto *right_meter = dynamic_cast<Fl_Progress *>(button(window, "Master R"));
CHECK(left_meter && right_meter && left_meter->value() > -90 && right_meter->value() > -90);
const double playing_left = left_meter->value(), playing_right = right_meter->value();
CHECK(window.pause_playback() == XTC_OK);
pump(); // Fade completion is acknowledged by the GUI timer.
CHECK(left_meter->value() == 0 && right_meter->value() == 0);
CHECK(left_meter->value() < playing_left && right_meter->value() < playing_right);
uint64_t paused = timeline->position(); pump();
CHECK(timeline->position() == paused);
press(window, "Play"); pump();