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 pasting prepares the candidate and is undoable. The clipboard is internal to
this session and clears on successful New/Open. Preparation errors keep both this session and clears on successful New/Open. Preparation errors keep both
the destination and clipboard intact. 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. Capture and recall two track-bound effect configurations, with clear readiness indicators, safe preparation, undo and cleanup on document replacement.
### Tasks and acceptance criteria ### Tasks and acceptance criteria
- [ ] Implement the workflow with bounded validation, safe preparation and documented limits. - [x] Implement the workflow with bounded validation, safe preparation and documented limits.
- [ ] Pass meaningful core and/or GUI checks, including error and undo behavior. - [x] Pass meaningful core and/or GUI checks, including error and undo behavior.
- [ ] Document results and commit this phase separately. - [x] Document results and commit this phase separately.
### Implementation status ### 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 cached_{};
xtc_track_chain clipboard_{}; xtc_track_chain clipboard_{};
bool clipboard_ready_=false; 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<uint64_t> ids_;
std::vector<std::string> names_; std::vector<std::string> names_;
void result(xtc_result r) { if(r!=XTC_OK)owner_->report_error("Track inserts",r);refresh(true); } 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); 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); 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_=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("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("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); 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);} void select_track(uint64_t id) {track_=id;refresh(true);}
const xtc_graph *graph() const {return model_;} const xtc_graph *graph() const {return model_;}
void add(const char *file,unsigned int index) { result(owner_->add_track_effect(track_,file,index)); } 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 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() { 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)"); int index=actions_->find_index("Paste chain (replace)");
if(index>=0)actions_->mode(index,clipboard_ready_ ? 0 : FL_MENU_INACTIVE); 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(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]; 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)); 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; xtc_track_chain chain{};chain.output_gain=1;
if(track_!=UINT64_MAX)xtc_project_get_chain(project,track_,&chain); if(track_!=UINT64_MAX)xtc_project_get_chain(project,track_,&chain);
if(!rebuild && !std::memcmp(&cached_,&chain,sizeof(chain)))return; 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(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); 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; 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(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; window.do_callback();puts("Creative workflow GUI checks passed.");return failures ? 1 : 0;
} }
int track_insert_tests() int track_insert_tests()