diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 988fde5..ff91f96 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -1874,6 +1874,156 @@ See [AUDIO_EDITOR.md](AUDIO_EDITOR.md) for precise workflows and limits. --- +## Phase 80 — Compact Creation and Optional Track Inspector + +### Goal +Replace the large creation sidebar with a sequencer Create dropdown. Keep all creation actions and shortcuts, and provide an optional track inspector to reclaim horizontal editing space. + +### Tasks and acceptance criteria +- [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 +Implemented: sequencer Create menu retains empty/audio/MIDI track creation, WAV import, instrument loading and MIDI editing. The inspector is hidden by default and available under View/Track inspector, reclaiming 204 pixels. Core baseline and full Xvfb GUI suite passed, including menu callbacks, creation/undo, layout sizes and floating panels. Inspector visibility is session-local. + +--- + +## Phase 81 — Persistent Track Insert Chains + +### Goal +Add a bounded C99 per-track insert model with validation, clone/history/duplicate support and backward-compatible project persistence. Loading metadata must not load plugin binaries. + +### 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. + +### Implementation status +Planned. + +--- + +## Phase 82 — Prepared Track Insert Processing + +### Goal +Compile track inserts into serial modular graphs before track gain/pan. Cover audio and MIDI rendering, export, bypass, cleanup and callback safety; explicitly reject unsupported routing. + +### 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. + +### Implementation status +Planned. + +--- + +## Phase 83 — Track Inserts in the Modular Workspace + +### Goal +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. + +### Implementation status +Planned. + +--- + +## Phase 84 — Searchable Effect Discovery + +### Goal +Make LADSPA, VST2, VST3 and CLAP effects easy to find and add, including Linux VST3 bundles, format filters and clear compatibility errors. + +### 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. + +### Implementation status +Planned. + +--- + +## Phase 85 — Insert Chain Editing + +### Goal +Expose move up/down, remove and bypass with one undo step per action and safe prepared-plan adoption. + +### 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. + +### Implementation status +Planned. + +--- + +## Phase 86 — Insert Inspection and Controls + +### Goal +Show selected effect identity and supported generic controls. Provide reset where supported and clearly identify unavailable native editors/parameters. + +### 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. + +### Implementation status +Planned. + +--- + +## Phase 87 — Reusable Effect Chain Presets + +### Goal +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. + +### Implementation status +Planned. + +--- + +## Phase 88 — Parallel Insert Mix and Chain Output + +### Goal +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. + +### Implementation status +Planned. + +--- + +## Phase 89 — Host VST3 Gallery and Integrated Qualification + +### Goal +Load compatible installed host VST3 effects in real track chains, capture screenshots, update usage documentation and qualify integration, persistence and real-time boundaries. + +### 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. + +### Implementation status +Planned. + +--- + ## Recommended Execution Order Reorder the phases when the audit reveals a blocking dependency. Do not mark a phase complete merely because code exists: its acceptance criteria must be demonstrated by builds, tests, or documented manual verification. diff --git a/src/gui/main_window.cpp b/src/gui/main_window.cpp index 79fd00d..face465 100644 --- a/src/gui/main_window.cpp +++ b/src/gui/main_window.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -291,7 +292,7 @@ MainWindow::MainWindow(bool prompt_unsaved,const char *configuration) editors_[0].owner=this;editors_[0].index=0;editors_[0].page=sequencer; editors_[0].content=new Fl_Group(216,158,1056,546); editors_[0].content->begin(); - auto *add_track = new Fl_Button(228, 168, 100, 26, "Add track"); + auto *add_track = new Fl_Menu_Button(228, 168, 100, 26, "Create"); auto *add_region = new Fl_Button(336, 168, 110, 26, "Add region"); auto *delete_region = new Fl_Button(454, 168, 130, 26, "Delete region/track"); auto *zoom_in = new Fl_Button(592, 168, 80, 26, "Zoom +"); @@ -373,11 +374,6 @@ MainWindow::MainWindow(bool prompt_unsaved,const char *configuration) static_cast(c)->mark_dirty(); }, this); - add_track->callback( - [](Fl_Widget *, void *c) { - static_cast(c)->add_track(); - }, - sequencer_); add_midi->callback( [](Fl_Widget *, void *c) { static_cast(c)->add_midi_clip(); @@ -510,36 +506,33 @@ MainWindow::MainWindow(bool prompt_unsaved,const char *configuration) auto *sidebar = new Fl_Group(12,126,192,578); sidebar_=sidebar; sidebar->box(FL_BORDER_BOX); - auto *create_title = new Fl_Box(24,140,168,24,"CREATE TRACKS"); - create_title->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE); - auto *midi_track = new Fl_Button(24,176,168,34,"New MIDI track"); - midi_track->tooltip("Create a track and one-bar MIDI clip at the cursor; undo as one edit."); - midi_track->callback([](Fl_Widget *, void *c) { auto *w = static_cast(c); - xtc_result r = w->create_track(true); if (r != XTC_OK) w->report_error("Create MIDI track",r); },this); - auto *audio_track = new Fl_Button(24,218,168,34,"New audio track"); - audio_track->tooltip("Create an empty audio track, ready for WAV import or recording."); - audio_track->callback([](Fl_Widget *, void *c) { auto *w = static_cast(c); - xtc_result r = w->create_track(false); if (r != XTC_OK) w->report_error("Create audio track",r); },this); - menu->add("Track/New MIDI track",FL_CTRL+FL_SHIFT+'m',midi_track->callback(),this); - menu->add("Track/New audio track",FL_CTRL+FL_SHIFT+'a',audio_track->callback(),this); - auto *import = new Fl_Button(24,260,168,34,"Import WAV"); - import->callback(audio_file_callback,this); - auto *load = new Fl_Button(24,302,168,34,"Load instrument plugin"); - load->callback(instrument_callback,this); - auto *editor = new Fl_Button(24,344,168,34,"Edit MIDI notes"); - editor->callback(piano_callback,this); - auto *selection_title = new Fl_Box(24,404,168,24,"TRACK INSPECTOR"); + auto midi_create = [](Fl_Widget *, void *c) { auto *w=static_cast(c); + xtc_result r=w->create_track(true); if(r!=XTC_OK)w->report_error("Create MIDI track",r); }; + auto audio_create = [](Fl_Widget *, void *c) { auto *w=static_cast(c); + xtc_result r=w->create_track(false); if(r!=XTC_OK)w->report_error("Create audio track",r); }; + add_track->add("Empty track",0,[](Fl_Widget *,void *c){static_cast(c)->add_track();},sequencer_); + add_track->add("New MIDI track",0,midi_create,this); + add_track->add("New audio track",0,audio_create,this); + add_track->add("Import WAV",0,audio_file_callback,this); + add_track->add("Load instrument plugin",0,instrument_callback,this); + add_track->add("Edit MIDI notes",0,piano_callback,this); + menu->add("Track/New MIDI track",FL_CTRL+FL_SHIFT+'m',midi_create,this); + menu->add("Track/New audio track",FL_CTRL+FL_SHIFT+'a',audio_create,this); + menu->add("View/Track inspector",0,[](Fl_Widget *,void *c){ + auto *w=static_cast(c);w->show_track_inspector(!w->sidebar_->visible()); + },this,FL_MENU_TOGGLE); + auto *selection_title = new Fl_Box(24,140,168,24,"TRACK INSPECTOR"); selection_title->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE); - selection_info_ = new Fl_Box(24,438,168,82,"Select a track to inspect it."); + selection_info_ = new Fl_Box(24,174,168,82,"Select a track to inspect it."); selection_info_->align(FL_ALIGN_TOP_LEFT | FL_ALIGN_INSIDE | FL_ALIGN_WRAP); - inspector_gain_ = new Fl_Value_Input(78, 528, 114, 26, "Gain"); + inspector_gain_ = new Fl_Value_Input(78, 264, 114, 26, "Gain"); inspector_gain_->bounds(0, 4); inspector_gain_->step(.01); - inspector_pan_ = new Fl_Value_Input(78, 562, 114, 26, "Pan"); + inspector_pan_ = new Fl_Value_Input(78, 298, 114, 26, "Pan"); inspector_pan_->bounds(-1, 1); inspector_pan_->step(.01); - inspector_mute_ = new Fl_Check_Button(24, 596, 78, 24, "Mute"); - inspector_solo_ = new Fl_Check_Button(108, 596, 84, 24, "Solo"); + inspector_mute_ = new Fl_Check_Button(24, 332, 78, 24, "Mute"); + inspector_solo_ = new Fl_Check_Button(108, 332, 84, 24, "Solo"); for (auto *control : {static_cast(inspector_gain_), static_cast(inspector_pan_), static_cast(inspector_mute_), static_cast(inspector_solo_)}) { control->when(FL_WHEN_RELEASE); @@ -548,7 +541,7 @@ MainWindow::MainWindow(bool prompt_unsaved,const char *configuration) inspector_gain_->tooltip( "Selected track linear gain: 0 silence, 1 unity, 4 maximum. Automation overrides static gain."); inspector_pan_->tooltip("Selected track pan: -1 left, 0 center, +1 right."); - auto *tip = new Fl_Box(24, 632, 168, 52, "Ctrl+P Commands\nCtrl+1/2/3 Views\nCtrl+0 Fit song"); + auto *tip = new Fl_Box(24, 368, 168, 52, "Ctrl+P Commands\nCtrl+1/2/3 Views\nCtrl+0 Fit song"); tip->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE); sidebar->end(); status_ = new Fl_Box(12,714,1256,26,"JACK: not connected"); @@ -557,12 +550,11 @@ MainWindow::MainWindow(bool prompt_unsaved,const char *configuration) error_->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE | FL_ALIGN_WRAP); xtc_ui::style(this); color(xtc_ui::background()); - create_title->labelcolor(xtc_ui::muted()); selection_title->labelcolor(xtc_ui::muted()); + selection_title->labelcolor(xtc_ui::muted()); tip->labelcolor(xtc_ui::muted()); hint->labelcolor(xtc_ui::muted()); transport_position_->box(xtc_ui::flat); transport_position_->color(xtc_ui::background()); transport_position_->labelfont(FL_COURIER_BOLD); transport_position_->labelsize(18); tempo_display_->labelcolor(xtc_ui::muted()); - midi_track->color(fl_rgb_color(45,91,86)); play_->labelcolor(xtc_ui::accent()); record_->labelcolor(fl_rgb_color(245,132,142)); error_->labelcolor(fl_rgb_color(255,166,139)); end(); @@ -570,6 +562,7 @@ MainWindow::MainWindow(bool prompt_unsaved,const char *configuration) size_range(1220, 720); callback(close_callback, this); apply_ui_preferences(); + show_track_inspector(false); open_backend(); if(config_result!=XTC_OK && config_result!=XTC_ERR_NOT_FOUND) report_error("Preferences file invalid; using defaults",config_result); Fl::add_timeout(static_cast(settings_.refresh_ms)/1000,timer_callback,this); @@ -636,6 +629,16 @@ void MainWindow::reset_layout() settings_.piano_width=900;settings_.piano_height=620;settings_.track_header_width=180; sequencer_->header_width(180);tabs_->value(editors_[0].page);apply_ui_preferences(); } +void MainWindow::show_track_inspector(bool visible) +{ + const int left=visible ? 216 : 12; + if(visible)sidebar_->show();else sidebar_->hide(); + tabs_->resize(left,tabs_->y(),w()-left-8,tabs_->h()); + auto *menu=dynamic_cast(child(0)); + if(menu) { auto *item=const_cast(menu->find_item("View/Track inspector")); + if(item) { if(visible)item->set();else item->clear(); } } + init_sizes();redraw(); +} void MainWindow::resize(int x,int y,int width,int height) { Fl_Double_Window::resize(x,y,width,height); diff --git a/src/gui/main_window.hpp b/src/gui/main_window.hpp index 52b2ae3..b0ac464 100644 --- a/src/gui/main_window.hpp +++ b/src/gui/main_window.hpp @@ -31,6 +31,7 @@ class Fl_Check_Button; class MainWindow : public Fl_Double_Window { public: + 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 * the current document. No engine/model changes happen in audio callbacks. */ diff --git a/tests/integration/test_gui.cpp b/tests/integration/test_gui.cpp index 5a76a80..d2d6f5b 100644 --- a/tests/integration/test_gui.cpp +++ b/tests/integration/test_gui.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -342,7 +343,7 @@ int main(int argc, char **argv) CHECK(sequence != nullptr); char directory[] = "/tmp/xtc-gui-XXXXXX"; CHECK(mkdtemp(directory) != nullptr); std::string filename = std::string(directory) + "/song.xtc"; - press(find(&window, "Add track")); press(find(&window, "Add region")); + auto *creation=dynamic_cast(find(&window,"Create")); CHECK(creation); if(creation)creation->picked(creation->find_item("Empty track")); press(find(&window, "Add region")); size_t track_count, clip_count; CHECK(xtc_project_get_counts(sequence->project(), &track_count, &clip_count) == XTC_OK && track_count == 1 && clip_count == 1); xtc_clip_info region{}; @@ -957,10 +958,12 @@ int main(int argc, char **argv) MainWindow window(false,""); window.show(); auto *sequence = timeline(&window); CHECK(sequence != nullptr); - auto *midi = find(&window,"New MIDI track"); - auto *audio = find(&window,"New audio track"); - CHECK(midi && audio && find(&window,"Import WAV") && find(&window,"Edit MIDI notes")); - press(midi); + auto *create=dynamic_cast(find(&window,"Create")); + CHECK(create && create->find_item("New MIDI track") && create->find_item("Import WAV")); + CHECK(sequence->x()<100); + window.show_track_inspector(true);CHECK(sequence->x()>200); + window.show_track_inspector(false);CHECK(sequence->x()<100); + create->picked(create->find_item("New MIDI track")); size_t tracks = 0, clips = 0; xtc_project_get_counts(sequence->project(),&tracks,&clips); CHECK(tracks == 1 && clips == 1); @@ -973,7 +976,7 @@ int main(int argc, char **argv) CHECK(window.undo(true) == XTC_OK); CHECK(sequence->select(track,clip)); pump(); CHECK(contains(&window,"MIDI track")); - press(audio); + create->picked(create->find_item("New audio track")); xtc_project_get_counts(sequence->project(),&tracks,&clips); CHECK(tracks == 2 && clips == 1); const xtc_project *before = sequence->project(); reserve_result = XTC_ERR_OUT_OF_MEMORY; @@ -984,9 +987,9 @@ int main(int argc, char **argv) // Exercise the supported minimum and a larger workspace with real layout. for (auto size : {std::pair{1220,720},{1600,1000}}) { window.resize(window.x(),window.y(),size.first,size.second); pump(); - CHECK(midi->x() >= 0 && midi->x()+midi->w() <= sequence->parent()->x()); + CHECK(create->x() >= 0 && create->x()+create->w() <= window.w()); CHECK(sequence->parent()->x()+sequence->parent()->w() <= window.w()); - CHECK(find(&window,"Play")->visible_r() && midi->visible_r()); + CHECK(find(&window,"Play")->visible_r() && create->visible_r()); } CHECK(window.save_project("/tmp/xtc-workspace.xtc") == XTC_OK); CHECK(window.new_project() == XTC_OK && window.open_project("/tmp/xtc-workspace.xtc") == XTC_OK);