feat(inserts): duplicate effect slots with independent state (phase 91)
This commit is contained in:
parent
e6cf7023fb
commit
062b33f55f
8 changed files with 69 additions and 5 deletions
12
Makefile
12
Makefile
|
|
@ -27,7 +27,7 @@ CXXFLAGS ?= -O2 -g
|
|||
CXXFLAGS += -std=c++17 -Wall -Wextra -Wpedantic -Iinclude
|
||||
LDFLAGS ?=
|
||||
|
||||
CORE_SOURCES := src/project/chain_preset.c src/mixer/track_fx.c src/audio/sample.c src/persistence/collect.c src/audio/normalize.c src/project/sections.c src/project/editing.c src/audio/export.c src/plugin/builtin.c src/settings/settings.c src/control/model.c src/audio/waveform.c src/plugin/ladspa.c src/plugin/host.c src/plugin/modern.c src/native/processor.c src/audio/recovery.c src/dsp/ramp.c src/native/native.c src/mixer/model.c src/mixer/render.c src/project/history.c src/midi/model.c src/midi/instrument.c src/audio/wave.c src/audio/arrangement.c src/audio/recorder.c src/core/xtc.c src/project/project.c src/persistence/project_file.c src/graph/graph.c src/dsp/gain.c src/engine/offline_engine.c src/engine/realtime.c src/backend/jack_backend.c
|
||||
CORE_SOURCES := src/project/chain_tools.c src/project/chain_preset.c src/mixer/track_fx.c src/audio/sample.c src/persistence/collect.c src/audio/normalize.c src/project/sections.c src/project/editing.c src/audio/export.c src/plugin/builtin.c src/settings/settings.c src/control/model.c src/audio/waveform.c src/plugin/ladspa.c src/plugin/host.c src/plugin/modern.c src/native/processor.c src/audio/recovery.c src/dsp/ramp.c src/native/native.c src/mixer/model.c src/mixer/render.c src/project/history.c src/midi/model.c src/midi/instrument.c src/audio/wave.c src/audio/arrangement.c src/audio/recorder.c src/core/xtc.c src/project/project.c src/persistence/project_file.c src/graph/graph.c src/dsp/gain.c src/engine/offline_engine.c src/engine/realtime.c src/backend/jack_backend.c
|
||||
JACK_AVAILABLE := $(shell $(PKG_CONFIG) --exists jack 2>/dev/null && echo yes)
|
||||
ifeq ($(JACK_AVAILABLE),yes)
|
||||
JACK_CFLAGS := $(shell $(PKG_CONFIG) --cflags jack)
|
||||
|
|
@ -586,3 +586,13 @@ test: test-track-fx
|
|||
.PHONY: test-gui-track-fx
|
||||
test-gui-track-fx: $(BUILD_DIR)/test_gui $(BUILD_DIR)/plugin-fixture.so $(BUILD_DIR)/clap-fixture.clap $(BUILD_DIR)/vst2-fixture.so $(BUILD_DIR)/vst3-fixture.so
|
||||
XTC_TEST_TRACK_FX_ONLY=1 XTC_TEST_PLUGIN="$(abspath $(BUILD_DIR))/plugin-fixture.so" XTC_TEST_CLAP="$(abspath $(BUILD_DIR))/clap-fixture.clap" XTC_TEST_VST2="$(abspath $(BUILD_DIR))/vst2-fixture.so" XTC_TEST_VST3="$(abspath $(BUILD_DIR))/vst3-fixture.so" xvfb-run -a -s "-screen 0 1280x900x24" ./$(BUILD_DIR)/test_gui
|
||||
|
||||
$(BUILD_DIR)/test_creative: $(BUILD_DIR)/tests/unit/test_creative.o $(BUILD_DIR)/libxtc.a
|
||||
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
|
||||
.PHONY: test-creative test-gui-creative
|
||||
test-creative: $(BUILD_DIR)/test_creative
|
||||
./$(BUILD_DIR)/test_creative
|
||||
test: test-creative
|
||||
test-gui-creative: $(BUILD_DIR)/test_gui
|
||||
XTC_TEST_CREATIVE_ONLY=1 xvfb-run -a -s "-screen 0 1280x900x24" ./$(BUILD_DIR)/test_gui
|
||||
-include $(BUILD_DIR)/tests/unit/test_creative.d
|
||||
|
|
|
|||
|
|
@ -10,3 +10,10 @@ or project-format change. Each phase is qualified separately in ROADMAP.md.
|
|||
mixer selection directly. Bus inserts are unsupported; no selection leaves the
|
||||
workspace unchanged. **View → Track inserts** still opens the rack independently.
|
||||
Ctrl+4 remains the piano-roll command, resolving the earlier shortcut collision.
|
||||
|
||||
## Duplicate an effect
|
||||
|
||||
Select a slot, then **Rack actions → Duplicate effect**. The copy appears directly
|
||||
after it, retaining controls, automation, wet amount and bypass. It is an
|
||||
independent instance with fresh processing history. Eight-slot capacity and
|
||||
plugin preparation errors preserve the old chain; Undo removes the copy.
|
||||
|
|
|
|||
|
|
@ -2073,12 +2073,12 @@ Implemented: selected sequencer/mixer track opens directly in the insert workspa
|
|||
Duplicate an effect adjacent to its slot with copied parameters, automation, wet amount and bypass; prepare transactionally, enforce the eight-slot limit and support undo.
|
||||
|
||||
### 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 metadata duplication and Rack actions/Duplicate effect create an adjacent independent slot, preserving parameters, automation, wet/bypass and gain. Warning-enabled core checks cover aliasing, independent edits and capacity rollback; GUI duplication, selection and undo checks passed.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ 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. */
|
||||
xtc_result xtc_track_chain_duplicate(const xtc_track_chain *chain,size_t index,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
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
class TrackInsertsPanel : public Fl_Group {
|
||||
MainWindow *owner_;
|
||||
Fl_Choice *tracks_;
|
||||
Fl_Menu_Button *actions_;
|
||||
Fl_Hold_Browser *slots_;
|
||||
Fl_Box *message_;
|
||||
Fl_Value_Input *wet_,*gain_;
|
||||
|
|
@ -49,6 +50,8 @@ class TrackInsertsPanel : public Fl_Group {
|
|||
wet_=new Fl_Value_Input(x+110,y+52,100,26,"Insert wet");wet_->bounds(0,1);wet_->step(.01);wet_->when(FL_WHEN_RELEASE);
|
||||
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("Duplicate effect",0,[](Fl_Widget *,void *c){auto *p=static_cast<TrackInsertsPanel *>(c);p->result(p->duplicate_slot());},this);
|
||||
message_=new Fl_Box(x+16,y+84,w-32,30,"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+126,230,h-142);slots_->format_char(0);slots_->callback([](Fl_Widget *,void *c){static_cast<TrackInsertsPanel *>(c)->update_mix();},this);
|
||||
scroll_=new Fl_Scroll(x+260,y+126,w-276,h-142);
|
||||
|
|
@ -61,6 +64,13 @@ 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 duplicate_slot() {
|
||||
xtc_track_chain chain{};auto r=xtc_project_get_chain(owner_->project_.get(),track_,&chain);size_t index=selected_slot();
|
||||
if(r==XTC_OK)r=xtc_track_chain_duplicate(&chain,index,&chain);
|
||||
if(r==XTC_OK)r=owner_->set_track_chain(track_,chain);
|
||||
if(r==XTC_OK){slots_->select(static_cast<int>(index+2));update_mix();}
|
||||
return r;
|
||||
}
|
||||
void update_mix() {
|
||||
xtc_track_chain chain{};if(xtc_project_get_chain(owner_->project_.get(),track_,&chain)!=XTC_OK){wet_->deactivate();gain_->deactivate();return;}
|
||||
gain_->activate();gain_->value(chain.output_gain);size_t index=selected_slot();
|
||||
|
|
|
|||
11
src/project/chain_tools.c
Normal file
11
src/project/chain_tools.c
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#include "xtc/xtc_plugin.h"
|
||||
/* Metadata transforms never load modules; aliases are safe and errors atomic. */
|
||||
xtc_result xtc_track_chain_duplicate(const xtc_track_chain *chain,size_t index,xtc_track_chain *out)
|
||||
{
|
||||
xtc_track_chain copy;size_t i;
|
||||
if(!out || xtc_track_chain_validate(chain)!=XTC_OK || index>=chain->count)return XTC_ERR_INVALID_ARGUMENT;
|
||||
if(chain->count==XTC_TRACK_INSERTS)return XTC_ERR_CAPACITY;
|
||||
copy=*chain;
|
||||
for(i=copy.count;i>index+1;--i)copy.slots[i]=copy.slots[i-1];
|
||||
copy.slots[index+1]=copy.slots[index];++copy.count;*out=copy;return XTC_OK;
|
||||
}
|
||||
|
|
@ -228,6 +228,10 @@ int creative_workflow_tests()
|
|||
CHECK(bar->find_item("View/Piano roll")->shortcut()==FL_CTRL+'4');
|
||||
window.mixer()->select_track(first_track);bar->picked(bar->find_item("View/Mixer"));
|
||||
CHECK(window.open_track_inserts()==XTC_OK && panel->selected_track()==first_track);
|
||||
CHECK(window.add_track_effect(first_track,XTC_BUILTIN_PATH,2)==XTC_OK);
|
||||
CHECK(panel->duplicate_slot()==XTC_OK && panel->selected_slot()==1);
|
||||
xtc_track_chain chain{};CHECK(xtc_project_get_chain(s->project(),first_track,&chain)==XTC_OK && chain.count==2);
|
||||
CHECK(window.undo()==XTC_OK);panel->refresh(true);CHECK(xtc_project_get_chain(s->project(),first_track,&chain)==XTC_OK && chain.count==1);
|
||||
window.do_callback();puts("Creative workflow GUI checks passed.");return failures ? 1 : 0;
|
||||
}
|
||||
int track_insert_tests()
|
||||
|
|
|
|||
19
tests/unit/test_creative.c
Normal file
19
tests/unit/test_creative.c
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#include "xtc/xtc_plugin.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
int main(void)
|
||||
{
|
||||
xtc_track_chain c={0},out={0};
|
||||
c.output_gain=.8f;c.count=1;c.slots[0].enabled=1;c.slots[0].wet=.4f;
|
||||
assert(xtc_plugin_probe_format(XTC_PLUGIN_BUILTIN,XTC_BUILTIN_PATH,2,48000,&c.slots[0].plugin)==XTC_OK);
|
||||
assert(xtc_track_chain_duplicate(&c,0,&out)==XTC_OK && out.count==2 && out.output_gain==.8f);
|
||||
assert(!memcmp(out.slots,out.slots+1,sizeof(out.slots[0])));
|
||||
out.slots[1].plugin.values[0]=10;assert(c.slots[0].plugin.values[0]!=10 && out.slots[0].plugin.values[0]!=10);
|
||||
assert(xtc_track_chain_duplicate(&out,1,&out)==XTC_OK && out.count==3 && out.slots[2].plugin.values[0]==10);
|
||||
c=out;assert(xtc_track_chain_duplicate(&out,3,&out)==XTC_ERR_INVALID_ARGUMENT && !memcmp(&c,&out,sizeof(c)));
|
||||
for(size_t i=3;i<XTC_TRACK_INSERTS;++i)out.slots[i]=out.slots[0];
|
||||
out.count=XTC_TRACK_INSERTS;c=out;
|
||||
assert(xtc_track_chain_duplicate(&out,0,&out)==XTC_ERR_CAPACITY && !memcmp(&c,&out,sizeof(c)));
|
||||
puts("Creative editing core checks passed.");return 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue