feat(mixer): control insert wet mix and chain output gain (phase 88)
This commit is contained in:
parent
b6d0771b4f
commit
a3858b1c82
6 changed files with 49 additions and 11 deletions
|
|
@ -2000,12 +2000,12 @@ Implemented: atomic chain preset save/load reuses the versioned project containe
|
|||
Expose per-slot wet/dry and chain output gain with finite bounded values and render tests for endpoints and ordering.
|
||||
|
||||
### 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 wet amount (0–1) and chain output gain (0–4) controls; parallel dry/wet gain branches and pre-fader output scaling. Track-chain tempo follows project tempo during assignment and changes. Exact dry/wet endpoint and fractional impulse tests, full core/reliability suite and GUI mix/invalid-value/tempo/preset tests passed. Mono mix controls use linear amplitude blending.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -58,3 +58,10 @@ 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.
|
||||
|
||||
**Insert wet** blends the selected effect with its input (0 dry, 1 fully wet).
|
||||
**Chain gain** scales the complete chain before the track fader (0–4 linear).
|
||||
Edits are prepared and undoable, with finite range validation. Internal sync
|
||||
parameters follow project tempo. This is mono amplitude blending; stereo track
|
||||
insert layouts, latency compensation and gapless live insert edits are future
|
||||
work. Editing stops transport before preparing the replacement plan.
|
||||
|
|
|
|||
|
|
@ -9,13 +9,16 @@
|
|||
#include <FL/Fl_Menu_Button.H>
|
||||
#include <FL/Fl_Hold_Browser.H>
|
||||
#include <FL/Fl_Box.H>
|
||||
#include <FL/Fl_Value_Input.H>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
class TrackInsertsPanel : public Fl_Group {
|
||||
MainWindow *owner_;
|
||||
Fl_Choice *tracks_;
|
||||
Fl_Hold_Browser *slots_;
|
||||
Fl_Box *message_;
|
||||
Fl_Value_Input *wet_,*gain_;
|
||||
GraphWidget *graph_;
|
||||
xtc_graph *model_=nullptr;
|
||||
uint64_t track_=UINT64_MAX;
|
||||
|
|
@ -40,9 +43,12 @@ class TrackInsertsPanel : public Fl_Group {
|
|||
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_);
|
||||
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);
|
||||
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);
|
||||
graph_=new GraphWidget(x+260,y+126,w-276,h-142,nullptr,[](void *,const xtc_graph_edit &,size_t *){return XTC_ERR_UNSUPPORTED;},nullptr,message_);
|
||||
resizable(graph_);end();
|
||||
}
|
||||
~TrackInsertsPanel() override { graph_->set_graph(nullptr);xtc_graph_destroy(model_); }
|
||||
|
|
@ -51,6 +57,17 @@ 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)); }
|
||||
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();
|
||||
if(index<chain.count){wet_->activate();wet_->value(chain.slots[index].wet);}else wet_->deactivate();
|
||||
}
|
||||
xtc_result set_mix(double wet,double gain) {
|
||||
if(!std::isfinite(wet) || wet<0 || wet>1 || !std::isfinite(gain) || gain<0 || gain>4)return XTC_ERR_INVALID_ARGUMENT;
|
||||
xtc_track_chain chain{};auto r=xtc_project_get_chain(owner_->project_.get(),track_,&chain);if(r!=XTC_OK)return r;
|
||||
size_t index=selected_slot();if(index<chain.count)chain.slots[index].wet=static_cast<float>(wet);
|
||||
chain.output_gain=static_cast<float>(gain);return owner_->set_track_chain(track_,chain);
|
||||
}
|
||||
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;
|
||||
|
|
@ -89,7 +106,7 @@ class TrackInsertsPanel : public Fl_Group {
|
|||
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;
|
||||
update_mix();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);
|
||||
|
|
@ -107,7 +124,7 @@ class TrackInsertsPanel : public Fl_Group {
|
|||
if(chain.count)slots_->select(selected>0 && static_cast<size_t>(selected)<=chain.count ? selected : 1);
|
||||
xtc_graph *next=nullptr;auto r=xtc_track_chain_graph(&chain,&next);
|
||||
if(r==XTC_OK){graph_->set_graph(next);xtc_graph_destroy(model_);model_=next;graph_->auto_layout();}
|
||||
redraw();
|
||||
update_mix();redraw();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -471,6 +471,7 @@ xtc_result xtc_project_set_tempo(xtc_project *project, unsigned int tempo)
|
|||
if (project == NULL || tempo < 20000U || tempo > 300000U)
|
||||
return XTC_ERR_INVALID_ARGUMENT;
|
||||
project->tempo = tempo;
|
||||
{size_t i,j;for(i=0;i<project->track_count;++i)for(j=0;j<project->chains[i].count;++j)project->chains[i].slots[j].plugin.tempo=tempo;}
|
||||
{size_t i;for(i=0;i<project->track_count;++i)if(project->instruments[i].instrument)project->instruments[i].tempo=tempo;}
|
||||
{ size_t i; for (i = 0; i < project->graph->node_count; ++i)
|
||||
if (project->graph->nodes[i].type == XTC_NODE_PLUGIN) project->graph->nodes[i].plugin.tempo = tempo; }
|
||||
|
|
@ -806,5 +807,7 @@ xtc_result xtc_project_set_chain(xtc_project *p,uint64_t track,const xtc_track_c
|
|||
size_t i;if(!p || xtc_track_chain_validate(c)!=XTC_OK)return XTC_ERR_INVALID_ARGUMENT;
|
||||
i=track_index(p,track);if(i==SIZE_MAX)return XTC_ERR_NOT_FOUND;
|
||||
if(p->tracks[i].is_bus && c->count)return XTC_ERR_UNSUPPORTED;
|
||||
p->chains[i]=*c;return XTC_OK;
|
||||
p->chains[i]=*c;
|
||||
{size_t j;for(j=0;j<c->count;++j)p->chains[i].slots[j].plugin.tempo=p->tempo;}
|
||||
return XTC_OK;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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->set_mix(.3,.8)==XTC_OK);
|
||||
CHECK(xtc_project_get_chain(s->project(),track,&chain)==XTC_OK && chain.slots[0].wet==.3f && chain.output_gain==.8f);
|
||||
auto *mix_before=s->project();CHECK(panel->set_mix(NAN,1)==XTC_ERR_INVALID_ARGUMENT && s->project()==mix_before);
|
||||
CHECK(window.set_tempo(135000)==XTC_OK);
|
||||
CHECK(xtc_project_get_chain(s->project(),track,&chain)==XTC_OK && chain.slots[0].plugin.tempo==135000);
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -30,13 +30,19 @@ static void rendering(void)
|
|||
c.slots[0].enabled=0;assert(xtc_project_set_chain(p,track,&c)==XTC_OK && xtc_audio_plan_create(p,8000,&audio)==XTC_OK);
|
||||
assert(xtc_audio_plan_set_fade_frames(audio,0)==XTC_OK && xtc_audio_plan_render(audio,0,out,32)==XTC_OK && out[0]==1 && out[8]==0);
|
||||
xtc_audio_plan_destroy(audio);audio=NULL;
|
||||
c.slots[0].enabled=1;c.slots[0].wet=.25f;
|
||||
assert(xtc_project_set_chain(p,track,&c)==XTC_OK && xtc_audio_plan_create(p,8000,&audio)==XTC_OK);
|
||||
assert(xtc_audio_plan_set_fade_frames(audio,0)==XTC_OK && xtc_audio_plan_render(audio,0,out,32)==XTC_OK);
|
||||
assert(fabsf(out[0]-.75f)<.00001f && fabsf(out[8]-.25f)<.00001f);xtc_audio_plan_destroy(audio);audio=NULL;
|
||||
c.slots[0].wet=0;assert(xtc_project_set_chain(p,track,&c)==XTC_OK && xtc_audio_plan_create(p,8000,&audio)==XTC_OK);
|
||||
assert(xtc_audio_plan_set_fade_frames(audio,0)==XTC_OK && xtc_audio_plan_render(audio,0,out,32)==XTC_OK && out[0]==1 && out[8]==0);xtc_audio_plan_destroy(audio);audio=NULL;
|
||||
/* Reject split ownership of a nonlinear chain on a hybrid track. */
|
||||
assert(xtc_project_add_midi_clip(p,track,0,960,&clip)==XTC_OK);
|
||||
assert(xtc_audio_plan_create(p,8000,&audio)==XTC_ERR_UNSUPPORTED && audio==NULL);
|
||||
xtc_project_destroy(p);p=NULL;
|
||||
assert(xtc_project_create(&p)==XTC_OK && xtc_project_add_track(p,"MIDI",&track)==XTC_OK && xtc_project_add_midi_clip(p,track,0,960,&clip)==XTC_OK);
|
||||
assert(xtc_project_add_note(p,clip,0,960,69,127,0,¬e)==XTC_OK);
|
||||
c.slots[0].enabled=1;assert(xtc_project_set_chain(p,track,&c)==XTC_OK && xtc_midi_plan_create(p,8000,&midi)==XTC_OK);
|
||||
c.slots[0].enabled=1;c.slots[0].wet=1;assert(xtc_project_set_chain(p,track,&c)==XTC_OK && xtc_midi_plan_create(p,8000,&midi)==XTC_OK);
|
||||
assert(xtc_midi_plan_set_fade_frames(midi,0)==XTC_OK && xtc_midi_plan_render(midi,0,NULL,0,out,32)==XTC_OK);
|
||||
for(size_t j=0;j<32;++j)assert(fabsf(out[j]-(j<8 ? 0.f : .05f*sinf(6.28318530718f*440.f*(float)(j-8)/8000.f)))<.00001f);
|
||||
xtc_midi_plan_destroy(midi);xtc_project_destroy(p);unlink(wave);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue