feat(inserts): compare track effect configurations with A and B (phase 93)

This commit is contained in:
Johannes Findeisen 2026-11-06 02:05:41 +01:00
commit c6f384a623
4 changed files with 41 additions and 5 deletions

View file

@ -26,3 +26,12 @@ including output gain. Copying neither loads modules nor changes undo history;
pasting prepares the candidate and is undoable. The clipboard is internal to
this session and clears on successful New/Open. Preparation errors keep both
the destination and clipboard intact.
## Compare effect configurations
**Rack actions → Compare** captures or recalls A and B. Each slot is bound to the
track where it was captured; Recall is available only on that track. Capturing
copies metadata without affecting undo or loading modules. Recall prepares a
fresh chain and is undoable; histories and opaque plugin states are excluded.
The two comparison slots clear on successful New/Open. Comparisons stop
transport when recalled, following the existing safe insert-edit workflow.

View file

@ -2103,12 +2103,12 @@ Implemented: session-local independent chain copy/paste with explicit destinatio
Capture and recall two track-bound effect configurations, with clear readiness indicators, safe preparation, undo and cleanup on document replacement.
### Tasks and acceptance criteria
- [ ] Implement the workflow with bounded validation, safe preparation and documented limits.
- [ ] Pass meaningful core and/or GUI checks, including error and undo behavior.
- [ ] Document results and commit this phase separately.
- [x] Implement the workflow with bounded validation, safe preparation and documented limits.
- [x] Pass meaningful core and/or GUI checks, including error and undo behavior.
- [x] Document results and commit this phase separately.
### Implementation status
Planned.
Implemented: two independent track-bound chain comparison slots, with disabled Recall until captured on the current track. Capture is metadata-only; recall prepares fresh instances and records one undo edit. GUI capture/recall, A/B values, undo, cross-track rejection and New cleanup checks passed with a warning-enabled build.
---

View file

@ -28,6 +28,7 @@ class TrackInsertsPanel : public Fl_Group {
xtc_track_chain cached_{};
xtc_track_chain clipboard_{};
bool clipboard_ready_=false;
struct Comparison {xtc_track_chain chain{};uint64_t track=UINT64_MAX;bool ready=false;} comparisons_[2];
std::vector<uint64_t> ids_;
std::vector<std::string> names_;
void result(xtc_result r) { if(r!=XTC_OK)owner_->report_error("Track inserts",r);refresh(true); }
@ -53,6 +54,10 @@ class TrackInsertsPanel : public Fl_Group {
gain_=new Fl_Value_Input(x+360,y+52,100,26,"Chain gain");gain_->bounds(0,4);gain_->step(.01);gain_->when(FL_WHEN_RELEASE);
for(auto *input:{wet_,gain_})input->callback([](Fl_Widget *,void *c){auto *p=static_cast<TrackInsertsPanel *>(c);p->result(p->set_mix(p->wet_->value(),p->gain_->value()));},this);
actions_=new Fl_Menu_Button(x+486,y+52,150,26,"Rack actions");
actions_->add("Compare/Capture A",0,[](Fl_Widget *,void *c){auto *p=static_cast<TrackInsertsPanel *>(c);p->result(p->comparison(0,true));},this);
actions_->add("Compare/Recall A",0,[](Fl_Widget *,void *c){auto *p=static_cast<TrackInsertsPanel *>(c);p->result(p->comparison(0,false));},this,FL_MENU_INACTIVE);
actions_->add("Compare/Capture B",0,[](Fl_Widget *,void *c){auto *p=static_cast<TrackInsertsPanel *>(c);p->result(p->comparison(1,true));},this);
actions_->add("Compare/Recall B",0,[](Fl_Widget *,void *c){auto *p=static_cast<TrackInsertsPanel *>(c);p->result(p->comparison(1,false));},this,FL_MENU_INACTIVE);
actions_->add("Copy chain",0,[](Fl_Widget *,void *c){auto *p=static_cast<TrackInsertsPanel *>(c);p->result(p->copy_chain());},this);
actions_->add("Paste chain (replace)",0,[](Fl_Widget *,void *c){auto *p=static_cast<TrackInsertsPanel *>(c);p->result(p->paste_chain());},this,FL_MENU_INACTIVE);
actions_->add("Duplicate effect",0,[](Fl_Widget *,void *c){auto *p=static_cast<TrackInsertsPanel *>(c);p->result(p->duplicate_slot());},this);
@ -68,9 +73,21 @@ class TrackInsertsPanel : public Fl_Group {
void select_track(uint64_t id) {track_=id;refresh(true);}
const xtc_graph *graph() const {return model_;}
void add(const char *file,unsigned int index) { result(owner_->add_track_effect(track_,file,index)); }
void clear_session() {clipboard_={};clipboard_ready_=false;update_actions();}
void clear_session() {clipboard_={};clipboard_ready_=false;for(auto &slot:comparisons_)slot=Comparison{};update_actions();}
bool clipboard_ready() const {return clipboard_ready_;}
bool comparison_ready(unsigned int slot) const {return slot<2 && comparisons_[slot].ready && comparisons_[slot].track==track_;}
xtc_result comparison(unsigned int slot,bool capture) {
if(slot>=2)return XTC_ERR_INVALID_ARGUMENT;
if(capture) {
xtc_track_chain chain{};auto r=xtc_project_get_chain(owner_->project_.get(),track_,&chain);
if(r==XTC_OK){comparisons_[slot].chain=chain;comparisons_[slot].track=track_;comparisons_[slot].ready=true;update_actions();}
return r;
}
if(!comparison_ready(slot))return XTC_ERR_NOT_FOUND;
return owner_->set_track_chain(track_,comparisons_[slot].chain);
}
void update_actions() {
for(unsigned int slot=0;slot<2;++slot){int item=actions_->find_index(slot ? "Compare/Recall B" : "Compare/Recall A");if(item>=0)actions_->mode(item,comparison_ready(slot) ? 0 : FL_MENU_INACTIVE);}
int index=actions_->find_index("Paste chain (replace)");
if(index>=0)actions_->mode(index,clipboard_ready_ ? 0 : FL_MENU_INACTIVE);
}
@ -148,6 +165,7 @@ class TrackInsertsPanel : public Fl_Group {
if(rebuild){ids_=ids;names_=names;tracks_->clear();for(const auto &name:names) {tracks_->add("track");tracks_->replace(tracks_->size()-2,name.c_str());}}
if(std::find(ids_.begin(),ids_.end(),track_)==ids_.end())track_=ids_.empty() ? UINT64_MAX : ids_[0];
for(size_t i=0;i<ids_.size();++i)if(ids_[i]==track_)tracks_->value(static_cast<int>(i));
update_actions();
xtc_track_chain chain{};chain.output_gain=1;
if(track_!=UINT64_MAX)xtc_project_get_chain(project,track_,&chain);
if(!rebuild && !std::memcmp(&cached_,&chain,sizeof(chain)))return;

View file

@ -239,7 +239,16 @@ int creative_workflow_tests()
CHECK(xtc_project_get_chain(s->project(),second_track,&chain)==XTC_OK && chain.count==1 && chain.output_gain==1 && chain.slots[0].wet==1);
CHECK(window.undo()==XTC_OK);panel->refresh(true);CHECK(xtc_project_get_chain(s->project(),second_track,&chain)==XTC_OK && chain.count==0);
submit_result=XTC_ERR_INVALID_STATE;auto *unchanged=s->project();CHECK(panel->paste_chain()==XTC_ERR_INVALID_STATE && s->project()==unchanged);submit_result=XTC_OK;
panel->select_track(first_track);CHECK(panel->comparison(0,false)==XTC_ERR_NOT_FOUND);
CHECK(panel->comparison(0,true)==XTC_OK && panel->comparison_ready(0));
CHECK(panel->set_mix(.7,.9)==XTC_OK && panel->comparison(1,true)==XTC_OK);
CHECK(panel->comparison(0,false)==XTC_OK && xtc_project_get_chain(s->project(),first_track,&chain)==XTC_OK && chain.output_gain==.6f);
CHECK(panel->comparison(1,false)==XTC_OK && xtc_project_get_chain(s->project(),first_track,&chain)==XTC_OK && chain.output_gain==.9f);
CHECK(window.undo()==XTC_OK);panel->refresh(true);
CHECK(xtc_project_get_chain(s->project(),first_track,&chain)==XTC_OK && chain.output_gain==.6f);
panel->select_track(second_track);CHECK(!panel->comparison_ready(0) && panel->comparison(0,false)==XTC_ERR_NOT_FOUND);
CHECK(window.new_project()==XTC_OK && !panel->clipboard_ready() && panel->paste_chain()==XTC_ERR_NOT_FOUND);
CHECK(!panel->comparison_ready(0) && !panel->comparison_ready(1));
window.do_callback();puts("Creative workflow GUI checks passed.");return failures ? 1 : 0;
}
int track_insert_tests()