From 924821738dba4b059b4ffe5704274003f8124d33 Mon Sep 17 00:00:00 2001 From: hanez Date: Fri, 6 Nov 2026 02:07:19 +0100 Subject: [PATCH] feat(inserts): add bulk enable, bypass and clear actions (phase 95) --- docs/CREATIVE_WORKFLOWS.md | 8 ++++++++ docs/ROADMAP.md | 8 ++++---- include/xtc/xtc_plugin.h | 8 ++++++-- src/gui/track_inserts.hpp | 9 +++++++++ src/project/chain_tools.c | 10 ++++++++++ tests/integration/test_gui.cpp | 6 ++++++ tests/unit/test_creative.c | 5 +++++ 7 files changed, 48 insertions(+), 6 deletions(-) diff --git a/docs/CREATIVE_WORKFLOWS.md b/docs/CREATIVE_WORKFLOWS.md index 4f79cf4..232954e 100644 --- a/docs/CREATIVE_WORKFLOWS.md +++ b/docs/CREATIVE_WORKFLOWS.md @@ -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. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index b88121e..1144c78 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -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. --- diff --git a/include/xtc/xtc_plugin.h b/include/xtc/xtc_plugin.h index 92bed02..96616cb 100644 --- a/include/xtc/xtc_plugin.h +++ b/include/xtc/xtc_plugin.h @@ -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 diff --git a/src/gui/track_inserts.hpp b/src/gui/track_inserts.hpp index f486bcc..c6e2c5a 100644 --- a/src/gui/track_inserts.hpp +++ b/src/gui/track_inserts.hpp @@ -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(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(c);p->result(p->bulk(XTC_CHAIN_ENABLE_ALL));},this); + actions_->add("Bypass all effects",0,[](Fl_Widget *,void *c){auto *p=static_cast(c);p->result(p->bulk(XTC_CHAIN_BYPASS_ALL));},this); + actions_->add("Clear chain",0,[](Fl_Widget *,void *c){auto *p=static_cast(c);p->result(p->bulk(XTC_CHAIN_CLEAR));},this); 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); @@ -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); diff --git a/src/project/chain_tools.c b/src/project/chain_tools.c index b47e9e9..f5849e7 100644 --- a/src/project/chain_tools.c +++ b/src/project/chain_tools.c @@ -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 || actionXTC_CHAIN_CLEAR)return XTC_ERR_INVALID_ARGUMENT; + copy=*chain; + if(action==XTC_CHAIN_CLEAR){copy.count=0;for(i=0;iapply_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; diff --git a/tests/unit/test_creative.c b/tests/unit/test_creative.c index 95ca751..00befbb 100644 --- a/tests/unit/test_creative.c +++ b/tests/unit/test_creative.c @@ -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; }