diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index ebb8e1c..2895600 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -1955,12 +1955,12 @@ Implemented: case-insensitive path/name search and All/LADSPA/VST2/VST3/CLAP fil Expose move up/down, remove and bypass with one undo step per action and safe prepared-plan adoption. ### Tasks and acceptance criteria -- [ ] Implement the described workflow with validated errors and safe ownership. -- [ ] Pass relevant core and/or GUI tests. -- [ ] Document behavior and limitations; commit this phase separately. +- [x] Implement the described workflow with validated errors and safe ownership. +- [x] Pass relevant core and/or GUI tests. +- [x] Document behavior and limitations; commit this phase separately. ### Implementation status -Planned. +Implemented: selected insert Up/Down, bypass and removal, preserving effect metadata and selection on reorder. Every successful edit prepares a candidate and records one undo step. Focused GUI checks cover boundary rejection, order, bypass, removal/undo and saved chain reload; warning-enabled build passed. --- diff --git a/docs/TRACK_INSERTS.md b/docs/TRACK_INSERTS.md index 3b3df4b..4d47fe3 100644 --- a/docs/TRACK_INSERTS.md +++ b/docs/TRACK_INSERTS.md @@ -39,3 +39,8 @@ The library searches configured Preferences paths recursively (including VST3 bundle contents), with case-insensitive search and format filters. LADSPA and VST2 both use `.so`, so those filters share candidates until inspection identifies the ABI. Listing files executes no plugin code. Only inspect trusted binaries. + +Select a slot in the rack, then use **Up**, **Down**, **Bypass** or **Remove**. +The selection follows reordered effects. Bypassed slots retain their settings +and appear marked in the rack; their nodes are omitted from the compiled graph. +Each successful operation creates one project undo step. diff --git a/src/gui/track_inserts.hpp b/src/gui/track_inserts.hpp index f1ce764..987d924 100644 --- a/src/gui/track_inserts.hpp +++ b/src/gui/track_inserts.hpp @@ -29,6 +29,12 @@ class TrackInsertsPanel : public Fl_Group { add->add("Internal Delay",0,[](Fl_Widget *,void *c){static_cast(c)->add(XTC_BUILTIN_PATH,2);},this); add->add("Internal Reverb",0,[](Fl_Widget *,void *c){static_cast(c)->add(XTC_BUILTIN_PATH,3);},this); add->add("Plugin library...",0,[](Fl_Widget *,void *c){auto *p=static_cast(c);std::string file;if(!xtc_choose_plugin_file(p->owner_->settings(),file))return;unsigned int index=0;if(xtc_choose_plugin(file.c_str(),p->owner_->sample_rate(),index))p->add(file.c_str(),index);},this); + struct Action {const char *name;int command;}; + int left=x+486; + for(const auto &action:{Action{"Up",-1},Action{"Down",1},Action{"Bypass",2},Action{"Remove",3}}) { + auto *button=new Fl_Button(left,y+12,82,28,action.name);left+=90; + button->callback([](Fl_Widget *w,void *c){auto *p=static_cast(c);int command=!strcmp(w->label(),"Up") ? -1 : !strcmp(w->label(),"Down") ? 1 : !strcmp(w->label(),"Bypass") ? 2 : 3;p->result(p->edit_slot(command));},this); + } message_=new Fl_Box(x+16,y+50,w-32,32,"Track input > inserts > output gain > track fader > bus/master");message_->align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE); slots_=new Fl_Hold_Browser(x+16,y+94,230,h-110);slots_->format_char(0); graph_=new GraphWidget(x+260,y+94,w-276,h-110,nullptr,[](void *,const xtc_graph_edit &,size_t *){return XTC_ERR_UNSUPPORTED;},nullptr,message_); @@ -40,6 +46,22 @@ 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)); } + /* Commands: -1 up, 1 down, 2 toggle bypass, 3 remove. */ + xtc_result edit_slot(int command) { + xtc_track_chain chain{};auto r=xtc_project_get_chain(owner_->project_.get(),track_,&chain);size_t index=selected_slot(); + if(r!=XTC_OK)return r; + if(index>=chain.count)return XTC_ERR_INVALID_STATE; + size_t selected=index; + if(command==-1 || command==1) { + if((command==-1 && !index) || (command==1 && index+1>=chain.count))return XTC_ERR_INVALID_ARGUMENT; + selected=command==-1 ? index-1 : index+1;std::swap(chain.slots[index],chain.slots[selected]); + }else if(command==2)chain.slots[index].enabled=!chain.slots[index].enabled; + else if(command==3) {for(size_t i=index+1;iset_track_chain(track_,chain); + if(r==XTC_OK && chain.count)slots_->select(static_cast(std::min(selected,chain.count-1)+1)); + return r; + } void refresh(bool force=false) { const xtc_project *project=owner_->project_.get();size_t count=0,clips=0;xtc_project_get_counts(project,&count,&clips); std::vector ids;std::vector names; diff --git a/tests/integration/test_gui.cpp b/tests/integration/test_gui.cpp index 1f12388..b9de45a 100644 --- a/tests/integration/test_gui.cpp +++ b/tests/integration/test_gui.cpp @@ -233,6 +233,15 @@ int track_insert_tests() auto *before=s->project();submit_result=XTC_ERR_INVALID_STATE; CHECK(window.add_track_effect(track,XTC_BUILTIN_PATH,3)==XTC_ERR_INVALID_STATE && s->project()==before);submit_result=XTC_OK; CHECK(window.add_track_effect(track,"/missing/plugin.so",0)!=XTC_OK && s->project()==before); + CHECK(window.add_track_effect(track,XTC_BUILTIN_PATH,3)==XTC_OK); + CHECK(panel->edit_slot(-1)==XTC_ERR_INVALID_ARGUMENT); + CHECK(panel->edit_slot(1)==XTC_OK && panel->selected_slot()==1); + CHECK(xtc_project_get_chain(s->project(),track,&chain)==XTC_OK && chain.slots[1].plugin.index==2); + CHECK(panel->edit_slot(2)==XTC_OK); + CHECK(xtc_project_get_chain(s->project(),track,&chain)==XTC_OK && !chain.slots[1].enabled); + CHECK(panel->edit_slot(3)==XTC_OK && window.undo()==XTC_OK);panel->refresh(true); + CHECK(xtc_project_get_chain(s->project(),track,&chain)==XTC_OK && chain.count==2); + CHECK(panel->edit_slot(3)==XTC_OK); CHECK(window.save_project("/tmp/xtc-inserts-gui.xtc")==XTC_OK && window.new_project()==XTC_OK && window.open_project("/tmp/xtc-inserts-gui.xtc")==XTC_OK); CHECK(xtc_project_get_chain(s->project(),track,&chain)==XTC_OK && chain.count==1);unlink("/tmp/xtc-inserts-gui.xtc"); window.do_callback();puts("Track inserts GUI adoption/undo/rollback/reload passed.");return failures ? 1 : 0;