feat(gui): reorder, bypass and remove track inserts (phase 85)

This commit is contained in:
Johannes Findeisen 2026-11-06 01:38:55 +01:00
commit ccb1d64b9f
4 changed files with 40 additions and 4 deletions

View file

@ -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.
---

View file

@ -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.

View file

@ -29,6 +29,12 @@ class TrackInsertsPanel : public Fl_Group {
add->add("Internal Delay",0,[](Fl_Widget *,void *c){static_cast<TrackInsertsPanel *>(c)->add(XTC_BUILTIN_PATH,2);},this);
add->add("Internal Reverb",0,[](Fl_Widget *,void *c){static_cast<TrackInsertsPanel *>(c)->add(XTC_BUILTIN_PATH,3);},this);
add->add("Plugin library...",0,[](Fl_Widget *,void *c){auto *p=static_cast<TrackInsertsPanel *>(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<TrackInsertsPanel *>(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;i<chain.count;++i)chain.slots[i-1]=chain.slots[i];chain.slots[--chain.count]={};}
else return XTC_ERR_INVALID_ARGUMENT;
r=owner_->set_track_chain(track_,chain);
if(r==XTC_OK && chain.count)slots_->select(static_cast<int>(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<uint64_t> ids;std::vector<std::string> names;

View file

@ -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;