feat(gui): add the per-track modular insert workspace (phase 83)
This commit is contained in:
parent
62cee7a7b0
commit
e3c27d6aae
6 changed files with 121 additions and 4 deletions
|
|
@ -1925,12 +1925,12 @@ Implemented: generated serial graphs process summed pre-fader audio or MIDI per
|
|||
Offer a selected-track insert view alongside the global graph, with clear signal order and transactional Add effect actions.
|
||||
|
||||
### 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: Ctrl+4 opens the track insert rack and actual generated modular graph, alongside the global graph. Track selection and Add effect provide internal devices and the existing four-format plugin library; candidate preparation and undo are transactional. Warning-enabled GUI build and focused adoption/undo/failed preparation/missing module/save-reload tests passed. Generated wiring is read-only.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -27,3 +27,10 @@ allocation or I/O. Mono effect adapters require zero reported latency. External
|
|||
plugin state on seeks follows the existing host contract. Bus inserts and
|
||||
tracks containing both audio assets and MIDI reject preparation explicitly.
|
||||
Live unassigned sine monitoring remains a separate master input.
|
||||
|
||||
Open **View → Track inserts** (Ctrl+4) and choose a track. **Add effect** offers
|
||||
internal Delay/Reverb and the plugin library. The left rack shows signal order;
|
||||
the graph displays the actual generated graph compiled by playback. Wiring in
|
||||
this view is read-only: edit the rack to preserve the serial chain. The global
|
||||
Modular Graph remains independent. A failed plugin probe or playback preparation
|
||||
preserves the current project; successful additions are undoable.
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include "command_palette.hpp"
|
||||
#include <FL/Fl_Value_Input.H>
|
||||
#include "plugin_library.hpp"
|
||||
#include "track_inserts.hpp"
|
||||
#include <FL/Fl_Check_Button.H>
|
||||
#include <FL/Fl_Choice.H>
|
||||
#include <FL/Fl_Value_Slider.H>
|
||||
|
|
@ -496,8 +497,10 @@ MainWindow::MainWindow(bool prompt_unsaved,const char *configuration)
|
|||
dock->callback([](Fl_Widget *,void *c){auto *p=static_cast<EditorPanel *>(c);p->owner->detach_editor(p->index,false);},&panel);
|
||||
panel.placeholder->end();panel.placeholder->hide();panel.page->end();
|
||||
}
|
||||
track_inserts_=new TrackInsertsPanel(216,158,1056,546,this);
|
||||
tabs_->end();
|
||||
tabs_->value(sequencer);
|
||||
menu->add("View/Track inserts",FL_CTRL+'4',view_callback,track_inserts_);
|
||||
menu->add("View/Sequencer", FL_CTRL + '1', view_callback, sequencer);
|
||||
menu->add("View/Mixer", FL_CTRL + '2', view_callback, mixer);
|
||||
menu->add("View/Modular Graph", FL_CTRL + '3', view_callback, graph);
|
||||
|
|
@ -1948,6 +1951,7 @@ xtc_result MainWindow::create_track(bool midi)
|
|||
}
|
||||
void MainWindow::refresh_workspace()
|
||||
{
|
||||
if(track_inserts_)track_inserts_->refresh();
|
||||
if (loop_button_) {
|
||||
loop_button_->value(settings_.loop_enabled != 0);
|
||||
if (recorder_)
|
||||
|
|
@ -2704,3 +2708,23 @@ void MainWindow::follow_playhead()
|
|||
if (screen > timeline_scroll_->x() + timeline_scroll_->w() - 48 || screen < timeline_scroll_->x() + sequencer_->header_width())
|
||||
timeline_scroll_->scroll_to(std::max(0, pixel - 160), timeline_scroll_->yposition());
|
||||
}
|
||||
|
||||
xtc_result MainWindow::set_track_chain(uint64_t track,const xtc_track_chain &chain)
|
||||
{
|
||||
auto r=before_arrangement_edit(this);if(r!=XTC_OK)return r;
|
||||
xtc_project *candidate=nullptr;r=xtc_project_clone(project_.get(),&candidate);
|
||||
if(r==XTC_OK)r=xtc_project_set_chain(candidate,track,&chain);
|
||||
if(r==XTC_OK)r=install_project(candidate,false);
|
||||
if(r!=XTC_OK){xtc_project_destroy(candidate);return r;}
|
||||
mark_dirty();track_inserts_->refresh(true);return XTC_OK;
|
||||
}
|
||||
xtc_result MainWindow::add_track_effect(uint64_t track,const char *file,unsigned int index)
|
||||
{
|
||||
xtc_track_chain chain{};auto r=xtc_project_get_chain(project_.get(),track,&chain);
|
||||
if(r!=XTC_OK)return r;
|
||||
if(chain.count==XTC_TRACK_INSERTS)return XTC_ERR_CAPACITY;
|
||||
xtc_track_insert slot{};r=xtc_plugin_probe(file,index,sample_rate(),&slot.plugin);
|
||||
if(r!=XTC_OK)return r;
|
||||
slot.enabled=1;slot.wet=1;chain.slots[chain.count++]=slot;
|
||||
return set_track_chain(track,chain);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,8 +29,12 @@ class Fl_Value_Input;
|
|||
class Fl_Scroll;
|
||||
class Fl_Check_Button;
|
||||
|
||||
class TrackInsertsPanel;
|
||||
class MainWindow : public Fl_Double_Window {
|
||||
public:
|
||||
xtc_result set_track_chain(uint64_t track,const xtc_track_chain &chain);
|
||||
xtc_result add_track_effect(uint64_t track,const char *file,unsigned int index);
|
||||
TrackInsertsPanel *track_inserts() const {return track_inserts_;}
|
||||
void show_track_inspector(bool visible);
|
||||
/* One undoable candidate edit. MIDI also adds a one-bar clip at the cursor.
|
||||
* Preparation stops/resets transport; recording and failed candidates retain
|
||||
|
|
@ -218,6 +222,8 @@ class MainWindow : public Fl_Double_Window {
|
|||
xtc_result return_to_start();
|
||||
|
||||
private:
|
||||
friend class TrackInsertsPanel;
|
||||
TrackInsertsPanel *track_inserts_=nullptr;
|
||||
using MixSnapshot=std::unique_ptr<xtc_mix_snapshot,decltype(&xtc_mix_snapshot_destroy)>;
|
||||
MixSnapshot mix_snapshots_[2]{{nullptr,xtc_mix_snapshot_destroy},{nullptr,xtc_mix_snapshot_destroy}};
|
||||
struct EditorPanel {
|
||||
|
|
|
|||
62
src/gui/track_inserts.hpp
Normal file
62
src/gui/track_inserts.hpp
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#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 <cstring>
|
||||
#include <algorithm>
|
||||
class TrackInsertsPanel : public Fl_Group {
|
||||
MainWindow *owner_;
|
||||
Fl_Choice *tracks_;
|
||||
Fl_Hold_Browser *slots_;
|
||||
Fl_Box *message_;
|
||||
GraphWidget *graph_;
|
||||
xtc_graph *model_=nullptr;
|
||||
uint64_t track_=UINT64_MAX;
|
||||
xtc_track_chain cached_{};
|
||||
std::vector<uint64_t> ids_;
|
||||
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);
|
||||
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_);
|
||||
resizable(graph_);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)); }
|
||||
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_ || force;
|
||||
if(rebuild){ids_=ids;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();}
|
||||
redraw();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
#include "../../src/gui/command_palette.hpp"
|
||||
#include <FL/Fl_Value_Input.H>
|
||||
#include "../../src/gui/plugin_library.hpp"
|
||||
#include "../../src/gui/track_inserts.hpp"
|
||||
#include <FL/Fl_Hold_Browser.H>
|
||||
#include <FL/Fl_Choice.H>
|
||||
#include <FL/Fl_Value_Slider.H>
|
||||
|
|
@ -215,8 +216,25 @@ static int sample_editor_tests()
|
|||
CHECK(window.undo()==XTC_OK);CHECK(xtc_project_get_counts(s->project(),&tracks,&clips)==XTC_OK && clips==1);
|
||||
window.do_callback();std::puts("Sample editor GUI selection/processing/adoption/slicing/undo passed.");return failures ? 1 : 0;
|
||||
}
|
||||
int track_insert_tests()
|
||||
{
|
||||
reset();MainWindow window(false,"");window.show();pump();auto *s=timeline(&window);
|
||||
CHECK(window.create_track(false)==XTC_OK);auto track=s->selected_track();auto *panel=window.track_inserts();CHECK(panel);
|
||||
panel->select_track(track);CHECK(panel->selected_track()==track && panel->graph());
|
||||
CHECK(window.add_track_effect(track,XTC_BUILTIN_PATH,2)==XTC_OK);
|
||||
xtc_track_chain chain{};CHECK(xtc_project_get_chain(s->project(),track,&chain)==XTC_OK && chain.count==1 && chain.slots[0].enabled);
|
||||
CHECK(window.undo()==XTC_OK);panel->refresh(true);CHECK(xtc_project_get_chain(s->project(),track,&chain)==XTC_OK && chain.count==0);
|
||||
CHECK(window.undo(true)==XTC_OK);panel->refresh(true);CHECK(xtc_project_get_chain(s->project(),track,&chain)==XTC_OK && chain.count==1);
|
||||
auto *before=s->project();submit_result=XTC_ERR_INVALID_STATE;
|
||||
CHECK(window.add_track_effect(track,XTC_BUILTIN_PATH,3)==XTC_ERR_INVALID_STATE && s->project()==before);submit_result=XTC_OK;
|
||||
CHECK(window.add_track_effect(track,"/missing/plugin.so",0)!=XTC_OK && s->project()==before);
|
||||
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;
|
||||
}
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if(std::getenv("XTC_TEST_TRACK_FX_ONLY"))return track_insert_tests();
|
||||
if(std::getenv("XTC_TEST_SAMPLE_EDITOR_ONLY"))return sample_editor_tests();
|
||||
if(!std::getenv("XTC_TEST_MODERN_EDITING_ONLY")) {
|
||||
reset();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue