feat(presets): save and adopt reusable effect chains (phase 87)
This commit is contained in:
parent
eb71ca765f
commit
b6d0771b4f
8 changed files with 67 additions and 5 deletions
2
Makefile
2
Makefile
|
|
@ -27,7 +27,7 @@ CXXFLAGS ?= -O2 -g
|
|||
CXXFLAGS += -std=c++17 -Wall -Wextra -Wpedantic -Iinclude
|
||||
LDFLAGS ?=
|
||||
|
||||
CORE_SOURCES := 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_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)
|
||||
|
|
|
|||
|
|
@ -1985,12 +1985,12 @@ Implemented: selected insert Inspect/edit verifies identity and shows generic co
|
|||
Save and load portable chain metadata with bounded validation, transactional replacement and explicit missing-plugin errors at preparation.
|
||||
|
||||
### 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: atomic chain preset save/load reuses the versioned project container with exactly one non-bus track and no clips/instrument. Metadata load never opens modules and preserves output on failure; GUI adoption prepares all effects before replacement. Core roundtrip/malformed tests, full core suite and GUI replacement/missing-file/reload checks passed. Presets reference installed binaries; opaque plugin states are not captured.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -50,3 +50,11 @@ controls. Reset defaults clears parameter automation on Apply. Cancel leaves the
|
|||
project unchanged. Internal effects and LADSPA expose controls; modern adapters
|
||||
currently expose none and run default configurations. Native plugin editor
|
||||
windows and opaque plugin-state chunks are not implemented.
|
||||
|
||||
**Preset → Save chain / Load chain** reuses the atomic versioned project file
|
||||
container with one track. Order, bypass, wet amounts, output gain and exposed
|
||||
controls/automation are retained. Loading metadata executes no plugin code;
|
||||
applying it verifies all enabled plugins before replacing the current chain.
|
||||
Preset files reference absolute installed binary paths, so another machine must
|
||||
provide compatible binaries at those paths. No plugin binaries or opaque state
|
||||
chunks are embedded.
|
||||
|
|
|
|||
|
|
@ -31,9 +31,13 @@ typedef struct {
|
|||
unsigned int tempo, point_count; /* milli-BPM, 20000..300000 */
|
||||
xtc_plugin_point points[XTC_PLUGIN_POINTS];
|
||||
} xtc_plugin_spec;
|
||||
/* Atomic versioned metadata presets; load never executes plugin code and
|
||||
* preserves out on error. Modules are verified only when adopting playback. */
|
||||
#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;
|
||||
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
|
||||
* opened here. Nonempty chains are mono effects on non-bus tracks. */
|
||||
/* Allocates the exact serial/parallel graph used by playback. Caller owns it. */
|
||||
|
|
|
|||
|
|
@ -37,6 +37,9 @@ class TrackInsertsPanel : public Fl_Group {
|
|||
}
|
||||
auto *inspect=new Fl_Button(x+850,y+12,120,28,"Inspect / edit");
|
||||
inspect->callback([](Fl_Widget *,void *c){auto *p=static_cast<TrackInsertsPanel *>(c);p->result(p->inspect_slot());},this);
|
||||
auto *presets=new Fl_Menu_Button(x+978,y+12,68,28,"Preset");
|
||||
presets->add("Save chain...",0,[](Fl_Widget *,void *c){static_cast<TrackInsertsPanel *>(c)->preset(true);},this);
|
||||
presets->add("Load chain...",0,[](Fl_Widget *,void *c){static_cast<TrackInsertsPanel *>(c)->preset(false);},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_);
|
||||
|
|
@ -48,6 +51,21 @@ 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 save_preset(const char *file) {
|
||||
xtc_track_chain chain{};auto r=xtc_project_get_chain(owner_->project_.get(),track_,&chain);
|
||||
return r==XTC_OK ? xtc_track_chain_preset_save(&chain,file) : r;
|
||||
}
|
||||
xtc_result load_preset(const char *file) {
|
||||
xtc_track_chain chain{};auto r=xtc_track_chain_preset_load(file,&chain);
|
||||
return r==XTC_OK ? owner_->set_track_chain(track_,chain) : r;
|
||||
}
|
||||
void preset(bool save) {
|
||||
Fl_Native_File_Chooser chooser;chooser.title(save ? "Save effect chain" : "Load effect chain");
|
||||
chooser.type(save ? Fl_Native_File_Chooser::BROWSE_SAVE_FILE : Fl_Native_File_Chooser::BROWSE_FILE);
|
||||
chooser.filter("Effect chain project\t*.xtc");if(save)chooser.options(Fl_Native_File_Chooser::SAVEAS_CONFIRM);
|
||||
if(chooser.show()!=0 || !chooser.filename())return;
|
||||
result(save ? save_preset(chooser.filename()) : load_preset(chooser.filename()));
|
||||
}
|
||||
xtc_result inspect_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)return r;
|
||||
|
|
|
|||
23
src/project/chain_preset.c
Normal file
23
src/project/chain_preset.c
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#include "project_internal.h"
|
||||
/* Reuse the versioned atomic project container. No module loading occurs here. */
|
||||
xtc_result xtc_track_chain_preset_save(const xtc_track_chain *chain,const char *path)
|
||||
{
|
||||
xtc_project *p=NULL;uint64_t id;xtc_result r;
|
||||
if(!path || xtc_track_chain_validate(chain)!=XTC_OK)return XTC_ERR_INVALID_ARGUMENT;
|
||||
r=xtc_project_create(&p);
|
||||
if(r==XTC_OK)r=xtc_project_add_track(p,"Effect chain preset",&id);
|
||||
if(r==XTC_OK)r=xtc_project_set_chain(p,id,chain);
|
||||
if(r==XTC_OK)r=xtc_project_save(p,path);
|
||||
xtc_project_destroy(p);return r;
|
||||
}
|
||||
xtc_result xtc_track_chain_preset_load(const char *path,xtc_track_chain *out)
|
||||
{
|
||||
xtc_project *p=NULL;xtc_track_chain chain;xtc_result r;
|
||||
if(!path || !out)return XTC_ERR_INVALID_ARGUMENT;
|
||||
r=xtc_project_create(&p);
|
||||
if(r==XTC_OK)r=xtc_project_load(p,path);
|
||||
if(r==XTC_OK && (p->track_count!=1 || p->clip_count || p->tracks[0].is_bus || p->instruments[0].instrument))r=XTC_ERR_FORMAT;
|
||||
if(r==XTC_OK)r=xtc_project_get_chain(p,p->tracks[0].id,&chain);
|
||||
if(r==XTC_OK)*out=chain;
|
||||
xtc_project_destroy(p);return r;
|
||||
}
|
||||
|
|
@ -249,6 +249,11 @@ int track_insert_tests()
|
|||
CHECK(xtc_project_get_chain(s->project(),track,&chain)==XTC_OK && chain.slots[0].plugin.values[0]!=10);
|
||||
const auto *cancel_before=s->project();
|
||||
Fl::add_timeout(.05,[](void *){press(find(Fl::modal(),"Cancel"));});CHECK(panel->inspect_slot()==XTC_OK && s->project()==cancel_before);
|
||||
CHECK(panel->save_preset("/tmp/xtc-inserts-preset.xtc")==XTC_OK);
|
||||
CHECK(window.add_track_effect(track,XTC_BUILTIN_PATH,3)==XTC_OK && panel->load_preset("/tmp/xtc-inserts-preset.xtc")==XTC_OK);
|
||||
CHECK(xtc_project_get_chain(s->project(),track,&chain)==XTC_OK && chain.count==1);
|
||||
auto *preset_before=s->project();CHECK(panel->load_preset("/missing/preset.xtc")!=XTC_OK && s->project()==preset_before);
|
||||
unlink("/tmp/xtc-inserts-preset.xtc");
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -59,5 +59,9 @@ int main(void)
|
|||
assert(xtc_project_get_chain(q,a,&got)==XTC_OK && got.output_gain==.75f);
|
||||
c.output_gain=1;c.slots[0].wet=2;assert(xtc_project_set_chain(q,a,&c)==XTC_ERR_INVALID_ARGUMENT);
|
||||
c.slots[0].wet=1;c.count=XTC_TRACK_INSERTS+1;assert(xtc_track_chain_validate(&c)==XTC_ERR_INVALID_ARGUMENT);
|
||||
assert(xtc_track_chain_preset_save(&got,file)==XTC_OK);
|
||||
memset(&c,0,sizeof(c));assert(xtc_track_chain_preset_load(file,&c)==XTC_OK && c.count==1 && c.output_gain==.75f);
|
||||
{FILE *broken=fopen(file,"w");assert(broken);fputs("bad preset\n",broken);fclose(broken);}
|
||||
assert(xtc_track_chain_preset_load(file,&c)==XTC_ERR_FORMAT && c.count==1 && c.output_gain==.75f);
|
||||
xtc_project_destroy(p);xtc_project_destroy(q);unlink(file);puts("Track insert metadata/persistence passed.");return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue