feat(gui): compact track creation and make the inspector optional (phase 80)
This commit is contained in:
parent
9e380d4dd0
commit
e9d783146f
4 changed files with 198 additions and 41 deletions
150
docs/ROADMAP.md
150
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.
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
#include <FL/Fl_Group.H>
|
||||
#include <FL/Fl_Scroll.H>
|
||||
#include <FL/Fl_Menu_Bar.H>
|
||||
#include <FL/Fl_Menu_Button.H>
|
||||
#include <FL/Fl_Input_.H>
|
||||
#include <FL/Fl_Tabs.H>
|
||||
#include <FL/fl_ask.H>
|
||||
|
|
@ -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<MainWindow *>(c)->mark_dirty();
|
||||
},
|
||||
this);
|
||||
add_track->callback(
|
||||
[](Fl_Widget *, void *c) {
|
||||
static_cast<SequencerWidget *>(c)->add_track();
|
||||
},
|
||||
sequencer_);
|
||||
add_midi->callback(
|
||||
[](Fl_Widget *, void *c) {
|
||||
static_cast<SequencerWidget *>(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<MainWindow *>(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<MainWindow *>(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<MainWindow *>(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<MainWindow *>(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<SequencerWidget *>(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<MainWindow *>(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<Fl_Widget *>(inspector_gain_), static_cast<Fl_Widget *>(inspector_pan_),
|
||||
static_cast<Fl_Widget *>(inspector_mute_), static_cast<Fl_Widget *>(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<double>(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<Fl_Menu_Bar *>(child(0));
|
||||
if(menu) { auto *item=const_cast<Fl_Menu_Item *>(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);
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#include <FL/Fl_Button.H>
|
||||
#include <FL/Fl_Group.H>
|
||||
#include <FL/Fl_Menu_Bar.H>
|
||||
#include <FL/Fl_Menu_Button.H>
|
||||
#include <FL/Fl_Tabs.H>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
|
|
@ -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<Fl_Menu_Button *>(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<Fl_Menu_Button *>(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<int,int>{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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue