feat(inserts): add bulk enable, bypass and clear actions (phase 95)

This commit is contained in:
Johannes Findeisen 2026-11-06 02:07:19 +01:00
commit 924821738d
7 changed files with 48 additions and 6 deletions

View file

@ -44,3 +44,11 @@ transport when recalled, following the existing safe insert-edit workflow.
no external plugins. All controls remain editable. Times are milliseconds;
feedback and wet levels are deliberate starting points, without automatic
loudness matching or a claim that the presets suit every source.
## Bulk rack actions
**Enable all effects**, **Bypass all effects**, and **Clear chain** each prepare
one undoable edit. Enabling all intentionally enables every slot, including
previously bypassed effects; Undo restores the previous mixed bypass state.
Clear removes slots while preserving chain output gain. No plugin resources
are released in the audio callback. Empty racks return a no-result error.

View file

@ -2133,12 +2133,12 @@ Implemented: Slapback, Dub Echo and Ambient Space templates use original interna
Enable all inserts, bypass all inserts or clear a chain as single undoable prepared edits, retaining output gain and respecting recording/error restrictions.
### 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: bounded C99 enable-all, bypass-all and clear transforms preserve output gain and support in-place metadata use. Rack actions prepare one undoable replacement; failed enable preparation preserves the old chain. Core metadata/invalid-action checks and GUI bypass/enable/clear/undo/rollback checks passed.
---

View file

@ -36,14 +36,18 @@ typedef struct {
#define XTC_TRACK_INSERTS 8U
typedef struct { xtc_plugin_spec plugin; unsigned int enabled; float wet; } xtc_track_insert;
typedef struct { size_t count; float output_gain; xtc_track_insert slots[XTC_TRACK_INSERTS]; } xtc_track_chain;
/* Copies the selected slot immediately after itself, preserving all metadata.
* Input/output may alias; errors preserve output. */
/* Original internal-device starting points: 0 Slapback, 1 Dub Echo,
* 2 Ambient Space. Name returns NULL for an unknown index. No modules opened;
* template errors preserve out. Delay times are milliseconds, not beat sync. */
const char *xtc_track_chain_template_name(unsigned int index);
xtc_result xtc_track_chain_template(unsigned int index,unsigned int tempo,xtc_track_chain *out);
/* Copies the selected slot immediately after itself, preserving all metadata.
* Input/output may alias; errors preserve output. */
xtc_result xtc_track_chain_duplicate(const xtc_track_chain *chain,size_t index,xtc_track_chain *out);
/* Pure metadata operations, preserving output gain; aliases accepted and errors
* preserve out. Enabling missing plugins can fail later at plan preparation. */
typedef enum {XTC_CHAIN_ENABLE_ALL,XTC_CHAIN_BYPASS_ALL,XTC_CHAIN_CLEAR} xtc_chain_action;
xtc_result xtc_track_chain_bulk(const xtc_track_chain *chain,xtc_chain_action action,xtc_track_chain *out);
xtc_result xtc_track_chain_preset_save(const xtc_track_chain *chain,const char *path);
xtc_result xtc_track_chain_preset_load(const char *path,xtc_track_chain *out);
/* Metadata only; control thread. Empty chain has unity output. No module is

View file

@ -58,6 +58,9 @@ 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("Enable all effects",0,[](Fl_Widget *,void *c){auto *p=static_cast<TrackInsertsPanel *>(c);p->result(p->bulk(XTC_CHAIN_ENABLE_ALL));},this);
actions_->add("Bypass all effects",0,[](Fl_Widget *,void *c){auto *p=static_cast<TrackInsertsPanel *>(c);p->result(p->bulk(XTC_CHAIN_BYPASS_ALL));},this);
actions_->add("Clear chain",0,[](Fl_Widget *,void *c){auto *p=static_cast<TrackInsertsPanel *>(c);p->result(p->bulk(XTC_CHAIN_CLEAR));},this);
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);
@ -77,6 +80,12 @@ 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)); }
xtc_result bulk(xtc_chain_action action) {
xtc_track_chain chain{};auto r=xtc_project_get_chain(owner_->project_.get(),track_,&chain);
if(r==XTC_OK && !chain.count)return XTC_ERR_NOT_FOUND;
if(r==XTC_OK)r=xtc_track_chain_bulk(&chain,action,&chain);
return r==XTC_OK ? owner_->set_track_chain(track_,chain) : r;
}
xtc_result apply_template(unsigned int index) {
xtc_track_chain chain{};auto r=xtc_track_chain_template(index,xtc_project_tempo(owner_->project_.get()),&chain);
if(r==XTC_OK)r=owner_->set_track_chain(track_,chain);

View file

@ -31,3 +31,13 @@ xtc_result xtc_track_chain_template(unsigned int index,unsigned int tempo,xtc_tr
if(index==2){c.slots[0].plugin.values[0]=.88f;c.slots[0].plugin.values[1]=.6f;c.slots[0].plugin.values[2]=.6f;c.slots[1].plugin.values[0]=500;c.slots[1].plugin.values[1]=.35f;c.slots[1].plugin.values[2]=.25f;}
r=xtc_track_chain_validate(&c);if(r==XTC_OK)*out=c;return r;
}
xtc_result xtc_track_chain_bulk(const xtc_track_chain *chain,xtc_chain_action action,xtc_track_chain *out)
{
xtc_track_chain copy;size_t i;
if(!out || xtc_track_chain_validate(chain)!=XTC_OK || action<XTC_CHAIN_ENABLE_ALL || action>XTC_CHAIN_CLEAR)return XTC_ERR_INVALID_ARGUMENT;
copy=*chain;
if(action==XTC_CHAIN_CLEAR){copy.count=0;for(i=0;i<XTC_TRACK_INSERTS;++i)copy.slots[i]=(xtc_track_insert){0};}
else for(i=0;i<copy.count;++i)copy.slots[i].enabled=action==XTC_CHAIN_ENABLE_ALL;
*out=copy;return XTC_OK;
}

View file

@ -250,6 +250,12 @@ int creative_workflow_tests()
CHECK(panel->apply_template(1)==XTC_OK);
CHECK(xtc_project_get_chain(s->project(),second_track,&chain)==XTC_OK && chain.count==2 && chain.slots[0].plugin.values[0]==375);
CHECK(window.undo()==XTC_OK);panel->refresh(true);CHECK(xtc_project_get_chain(s->project(),second_track,&chain)==XTC_OK && chain.count==0);
CHECK(panel->apply_template(1)==XTC_OK && panel->bulk(XTC_CHAIN_BYPASS_ALL)==XTC_OK);
CHECK(xtc_project_get_chain(s->project(),second_track,&chain)==XTC_OK && !chain.slots[0].enabled && !chain.slots[1].enabled);
submit_result=XTC_ERR_INVALID_STATE;unchanged=s->project();CHECK(panel->bulk(XTC_CHAIN_ENABLE_ALL)==XTC_ERR_INVALID_STATE && s->project()==unchanged);submit_result=XTC_OK;
CHECK(panel->bulk(XTC_CHAIN_ENABLE_ALL)==XTC_OK && panel->bulk(XTC_CHAIN_CLEAR)==XTC_OK);
CHECK(xtc_project_get_chain(s->project(),second_track,&chain)==XTC_OK && chain.count==0 && chain.output_gain==.8f);
CHECK(window.undo()==XTC_OK);panel->refresh(true);CHECK(xtc_project_get_chain(s->project(),second_track,&chain)==XTC_OK && chain.count==2);
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;

View file

@ -25,5 +25,10 @@ int main(void)
xtc_plan_destroy(plan);xtc_graph_destroy(g);
}
out=c;assert(!xtc_track_chain_template_name(3) && xtc_track_chain_template(3,120000,&c)==XTC_ERR_INVALID_ARGUMENT && !memcmp(&out,&c,sizeof(c)));
assert(xtc_track_chain_template(1,120000,&c)==XTC_OK);
assert(xtc_track_chain_bulk(&c,XTC_CHAIN_BYPASS_ALL,&c)==XTC_OK && !c.slots[0].enabled && !c.slots[1].enabled && c.output_gain==.8f);
assert(xtc_track_chain_bulk(&c,XTC_CHAIN_ENABLE_ALL,&c)==XTC_OK && c.slots[0].enabled && c.slots[1].enabled);
out=c;assert(xtc_track_chain_bulk(&c,(xtc_chain_action)99,&c)==XTC_ERR_INVALID_ARGUMENT && !memcmp(&c,&out,sizeof(c)));
assert(xtc_track_chain_bulk(&c,XTC_CHAIN_CLEAR,&c)==XTC_OK && c.count==0 && c.output_gain==.8f && !c.slots[0].plugin.path[0]);
puts("Creative editing core checks passed.");return 0;
}