diff --git a/docs/CREATIVE_WORKFLOWS.md b/docs/CREATIVE_WORKFLOWS.md index 216d79d..04dfd5f 100644 --- a/docs/CREATIVE_WORKFLOWS.md +++ b/docs/CREATIVE_WORKFLOWS.md @@ -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. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index d3acd0c..cca9b14 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -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. --- diff --git a/src/gui/track_inserts.hpp b/src/gui/track_inserts.hpp index f3d6dcd..1215366 100644 --- a/src/gui/track_inserts.hpp +++ b/src/gui/track_inserts.hpp @@ -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 ids_; std::vector 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(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(c);p->result(p->comparison(0,true));},this); + actions_->add("Compare/Recall A",0,[](Fl_Widget *,void *c){auto *p=static_cast(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(c);p->result(p->comparison(1,true));},this); + actions_->add("Compare/Recall B",0,[](Fl_Widget *,void *c){auto *p=static_cast(c);p->result(p->comparison(1,false));},this,FL_MENU_INACTIVE); actions_->add("Copy chain",0,[](Fl_Widget *,void *c){auto *p=static_cast(c);p->result(p->copy_chain());},this); actions_->add("Paste chain (replace)",0,[](Fl_Widget *,void *c){auto *p=static_cast(c);p->result(p->paste_chain());},this,FL_MENU_INACTIVE); actions_->add("Duplicate effect",0,[](Fl_Widget *,void *c){auto *p=static_cast(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;ivalue(static_cast(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; diff --git a/tests/integration/test_gui.cpp b/tests/integration/test_gui.cpp index d0a0c28..c27c4c5 100644 --- a/tests/integration/test_gui.cpp +++ b/tests/integration/test_gui.cpp @@ -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()