151 lines
11 KiB
C++
151 lines
11 KiB
C++
#ifndef XTC_TRACK_INSERTS_HPP
|
|
#define XTC_TRACK_INSERTS_HPP
|
|
#include "main_window.hpp"
|
|
#include "graph_widget.hpp"
|
|
#include "plugin_library.hpp"
|
|
#include "plugin_dialog.hpp"
|
|
#include "sequencer_widget.hpp"
|
|
#include <FL/Fl_Choice.H>
|
|
#include <FL/Fl_Menu_Button.H>
|
|
#include <FL/Fl_Hold_Browser.H>
|
|
#include <FL/Fl_Box.H>
|
|
#include <FL/Fl_Value_Input.H>
|
|
#include <FL/Fl_Scroll.H>
|
|
#include <cstring>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
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_;
|
|
GraphWidget *graph_;
|
|
Fl_Scroll *scroll_;
|
|
xtc_graph *model_=nullptr;
|
|
uint64_t track_=UINT64_MAX;
|
|
xtc_track_chain cached_{};
|
|
std::vector<uint64_t> ids_;
|
|
std::vector<std::string> names_;
|
|
void result(xtc_result r) { if(r!=XTC_OK)owner_->report_error("Track inserts",r);refresh(true); }
|
|
public:
|
|
TrackInsertsPanel(int x,int y,int w,int h,MainWindow *owner):Fl_Group(x,y,w,h,"Track inserts"),owner_(owner) {
|
|
tracks_=new Fl_Choice(x+64,y+12,240,28,"Track");tracks_->callback([](Fl_Widget *,void *c){auto *p=static_cast<TrackInsertsPanel *>(c);int i=p->tracks_->value();if(i>=0 && static_cast<size_t>(i)<p->ids_.size()){p->track_=p->ids_[static_cast<size_t>(i)];p->refresh(true);}},this);
|
|
auto *add=new Fl_Menu_Button(x+320,y+12,150,28,"Add effect");
|
|
add->add("Internal Delay",0,[](Fl_Widget *,void *c){static_cast<TrackInsertsPanel *>(c)->add(XTC_BUILTIN_PATH,2);},this);
|
|
add->add("Internal Reverb",0,[](Fl_Widget *,void *c){static_cast<TrackInsertsPanel *>(c)->add(XTC_BUILTIN_PATH,3);},this);
|
|
add->add("Plugin library...",0,[](Fl_Widget *,void *c){auto *p=static_cast<TrackInsertsPanel *>(c);std::string file;if(!xtc_choose_plugin_file(p->owner_->settings(),file))return;unsigned int index=0;if(xtc_choose_plugin(file.c_str(),p->owner_->sample_rate(),index))p->add(file.c_str(),index);},this);
|
|
struct Action {const char *name;int command;};
|
|
int left=x+486;
|
|
for(const auto &action:{Action{"Up",-1},Action{"Down",1},Action{"Bypass",2},Action{"Remove",3}}) {
|
|
auto *button=new Fl_Button(left,y+12,82,28,action.name);left+=90;
|
|
button->callback([](Fl_Widget *w,void *c){auto *p=static_cast<TrackInsertsPanel *>(c);int command=!strcmp(w->label(),"Up") ? -1 : !strcmp(w->label(),"Down") ? 1 : !strcmp(w->label(),"Bypass") ? 2 : 3;p->result(p->edit_slot(command));},this);
|
|
}
|
|
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);
|
|
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);
|
|
graph_=new GraphWidget(x+260,y+126,2000,4000,nullptr,[](void *,const xtc_graph_edit &,size_t *){return XTC_ERR_UNSUPPORTED;},nullptr,message_);
|
|
scroll_->end();resizable(scroll_);end();
|
|
}
|
|
~TrackInsertsPanel() override { graph_->set_graph(nullptr);xtc_graph_destroy(model_); }
|
|
uint64_t selected_track() const {return track_;}
|
|
size_t selected_slot() const {return slots_->value()>0 ? static_cast<size_t>(slots_->value()-1) : SIZE_MAX;}
|
|
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();
|
|
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;
|
|
}
|
|
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;
|
|
if(index>=chain.count)return XTC_ERR_INVALID_STATE;
|
|
xtc_plugin_details details{};xtc_plugin_spec edited{};
|
|
r=xtc_plugin_describe(&chain.slots[index].plugin,owner_->sample_rate(),&details);if(r!=XTC_OK)return r;
|
|
if(!xtc_edit_plugin(chain.slots[index].plugin,details,edited))return XTC_OK;
|
|
chain.slots[index].plugin=edited;return owner_->set_track_chain(track_,chain);
|
|
}
|
|
/* Commands: -1 up, 1 down, 2 toggle bypass, 3 remove. */
|
|
xtc_result edit_slot(int command) {
|
|
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;
|
|
if(index>=chain.count)return XTC_ERR_INVALID_STATE;
|
|
size_t selected=index;
|
|
if(command==-1 || command==1) {
|
|
if((command==-1 && !index) || (command==1 && index+1>=chain.count))return XTC_ERR_INVALID_ARGUMENT;
|
|
selected=command==-1 ? index-1 : index+1;std::swap(chain.slots[index],chain.slots[selected]);
|
|
}else if(command==2)chain.slots[index].enabled=!chain.slots[index].enabled;
|
|
else if(command==3) {for(size_t i=index+1;i<chain.count;++i)chain.slots[i-1]=chain.slots[i];chain.slots[--chain.count]={};}
|
|
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));
|
|
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);
|
|
std::vector<uint64_t> ids;std::vector<std::string> names;
|
|
for(size_t i=0;i<count;++i){xtc_track_info t{};xtc_project_get_track(project,i,&t);if(!t.is_bus){ids.push_back(t.id);names.emplace_back(t.name);}}
|
|
bool rebuild=ids!=ids_ || names!=names_ || force;
|
|
if(rebuild){ids_=ids;names_=names;tracks_->clear();for(const auto &name:names) {tracks_->add("track");tracks_->replace(tracks_->size()-2,name.c_str());}}
|
|
if(std::find(ids_.begin(),ids_.end(),track_)==ids_.end())track_=ids_.empty() ? UINT64_MAX : ids_[0];
|
|
for(size_t i=0;i<ids_.size();++i)if(ids_[i]==track_)tracks_->value(static_cast<int>(i));
|
|
xtc_track_chain chain{};chain.output_gain=1;
|
|
if(track_!=UINT64_MAX)xtc_project_get_chain(project,track_,&chain);
|
|
if(!rebuild && !std::memcmp(&cached_,&chain,sizeof(chain)))return;
|
|
cached_=chain;int selected=slots_->value();slots_->clear();
|
|
for(size_t i=0;i<chain.count;++i){std::string label=std::to_string(i+1)+" "+chain.slots[i].plugin.label+(chain.slots[i].enabled ? "" : " [bypass]");slots_->add(label.c_str());}
|
|
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();
|
|
size_t nodes=0,edges=0,max_level=0,max_row=0;std::array<size_t,256> rows{};
|
|
xtc_graph_get_counts(model_,&nodes,&edges);
|
|
for(size_t i=0;i<nodes;++i){xtc_node_info node{};xtc_routing_info route{};xtc_graph_get_node(model_,i,&node);if(graph_->routing(node.id,route))max_level=std::max(max_level,route.level);}
|
|
graph_->size(std::max(scroll_->w()-20,static_cast<int>(max_level+1)*174+40),4000);
|
|
for(size_t i=0;i<nodes;++i){xtc_node_info node{};xtc_routing_info route{};xtc_graph_get_node(model_,i,&node);if(graph_->routing(node.id,route) && route.level<rows.size()) {size_t row=rows[route.level]++;max_row=std::max(max_row,row);graph_->move_node(node.id,20+static_cast<int>(route.level)*174,24+static_cast<int>(row)*104);}}
|
|
graph_->size(graph_->w(),std::max(scroll_->h()-20,static_cast<int>(max_row+1)*104+48));scroll_->scroll_to(0,0);
|
|
message_->copy_label("Track input > inserts > output gain > track fader > bus/master. Scroll to inspect long chains.");}
|
|
update_mix();redraw();
|
|
}
|
|
};
|
|
#endif
|