feat(plugins): qualify host VST3 chains and refresh the DAW gallery (phase 89)
4
Makefile
|
|
@ -582,3 +582,7 @@ test-track-fx: $(BUILD_DIR)/test_track_fx
|
|||
./$(BUILD_DIR)/test_track_fx
|
||||
test: test-track-fx
|
||||
-include $(BUILD_DIR)/tests/unit/test_track_fx.d
|
||||
|
||||
.PHONY: test-gui-track-fx
|
||||
test-gui-track-fx: $(BUILD_DIR)/test_gui $(BUILD_DIR)/plugin-fixture.so $(BUILD_DIR)/clap-fixture.clap $(BUILD_DIR)/vst2-fixture.so $(BUILD_DIR)/vst3-fixture.so
|
||||
XTC_TEST_TRACK_FX_ONLY=1 XTC_TEST_PLUGIN="$(abspath $(BUILD_DIR))/plugin-fixture.so" XTC_TEST_CLAP="$(abspath $(BUILD_DIR))/clap-fixture.clap" XTC_TEST_VST2="$(abspath $(BUILD_DIR))/vst2-fixture.so" XTC_TEST_VST3="$(abspath $(BUILD_DIR))/vst3-fixture.so" xvfb-run -a -s "-screen 0 1280x900x24" ./$(BUILD_DIR)/test_gui
|
||||
|
|
|
|||
|
|
@ -170,3 +170,12 @@ and output, float32 and zero latency; they use default configuration. Instrument
|
|||
modern parameter editing/automation, opaque state and custom editors are not
|
||||
implemented. Projects now save v11 and read v1–v11. See PLUGIN_HOSTING.md for the
|
||||
full contract and test/qualification limits.
|
||||
|
||||
|
||||
Phases 80–89 add a compact sequencer Create menu, optional track inspector and
|
||||
per-track modular effect chains. Open **View → Track inserts** (Ctrl+4) to add,
|
||||
search, reorder, bypass, inspect and mix mono LADSPA/VST2/VST3/CLAP effects or
|
||||
internal devices. Chains support undo, presets, audio/MIDI rendering and export.
|
||||
Projects save format 17 and read formats 1–17. See
|
||||
[track insert workflows and limits](docs/TRACK_INSERTS.md) and the
|
||||
[screenshot gallery](screenshots/README.md).
|
||||
|
|
|
|||
|
|
@ -112,3 +112,20 @@ Do not unload/replace modules during playback; candidate plans are prepared off
|
|||
the callback and old instances are reclaimed on the control thread. Missing
|
||||
references remain inspectable without loading; failed GUI preparation retains
|
||||
the current project. Save a backup for applications limited to v11 or earlier.
|
||||
|
||||
|
||||
### Track insert chains (Phases 80–89)
|
||||
|
||||
Track chains contain at most eight mono effects. Modern adapters require zero
|
||||
reported latency, use default configuration and do not provide native plugin
|
||||
windows, parameter editing or opaque state persistence. A bounded VST3 output
|
||||
parameter sink retains the final notification per parameter per block (64
|
||||
parameters); these notifications are not recorded or exposed as controls.
|
||||
Track edits stop transport before candidate preparation. Bus inserts and tracks
|
||||
with both audio assets and MIDI reject preparation. Unassigned live sine
|
||||
monitoring stays on the master input. Stereo insert layouts, latency
|
||||
compensation, sandboxed scanning and gapless live insert edits remain future
|
||||
work. Internal history resets on discontinuities; third-party history follows
|
||||
the existing host contract. Presets retain absolute plugin references, so
|
||||
compatible binaries must be installed at the same paths on another machine.
|
||||
The smaller optional inspector's visibility is session-local.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Project format v16 (reads v1–v15)
|
||||
# Project format v17 (reads v1–v17)
|
||||
|
||||
Phases 5–31 use a bounded text format implemented in C99 without a parser library.
|
||||
The extension is `.xtc`. File operations run exclusively on the control thread.
|
||||
|
|
@ -426,3 +426,39 @@ followed by `MARKER tick hex_name` records. Count is 0..128; ticks must be stric
|
|||
increasing and at most MAX_TICK. Names use the existing bounded UTF-8 hex encoding.
|
||||
Versions 1–15 migrate to an empty marker list. Older builds reject version 16.
|
||||
Markers are copied by snapshots and history; the tick is their stable key.
|
||||
|
||||
|
||||
## Version 17: per-track insert chains
|
||||
|
||||
After MARKERS/MARKER and before END, the writer emits a chain for every track,
|
||||
in dense track order. Earlier versions migrate to empty chains with unity gain.
|
||||
Floats use the existing eight-digit IEEE binary32 hex encoding; strings use the
|
||||
existing bounded UTF-8/byte hex encoding, with `-` for an empty identity.
|
||||
|
||||
```text
|
||||
CHAINS track_count
|
||||
CHAIN track_id slot_count output_gain_bits
|
||||
INSERT enabled wet_bits format descriptor_index unique_id inputs tempo hex_label
|
||||
MODULE hex_absolute_binary_path
|
||||
IDENTITY hex_stable_identifier_or_dash
|
||||
CONTROLS control_count
|
||||
CONTROL port value_bits
|
||||
PARAMETERS point_count
|
||||
PARAM tick port step value_bits
|
||||
```
|
||||
|
||||
Each CHAIN has exactly slot_count INSERT blocks. CONTROLS/PARAMETERS determine
|
||||
the subsequent CONTROL/PARAM record counts, including zero. Slots are ordered,
|
||||
at most eight, and accept mono non-instrument effects only. Enabled is 0/1,
|
||||
wet is finite 0–1, output gain finite 0–4; inputs must be 1. The format values
|
||||
are LADSPA=0, CLAP=1, VST2=2, VST3=3 and Internal=4. Existing plugin specification
|
||||
validation applies, including descriptor/identity/port/automation bounds and
|
||||
20,000–300,000 milli-BPM. Bus chains must be empty. No sample reference is
|
||||
permitted on an effect. Duplicate, missing, reordered or excessive chain
|
||||
records, malformed strings and nonfinite values reject the whole load.
|
||||
|
||||
Loading and dependency inspection only validate metadata; no plugin module
|
||||
is opened. Enabled plugins are verified when a render plan is prepared. Chain
|
||||
preset files reuse this schema with exactly one non-bus track and no clips or
|
||||
instrument. Track insert APIs are declared in `xtc_plugin.h`; see
|
||||
[TRACK_INSERTS.md](TRACK_INSERTS.md) for playback restrictions and workflows.
|
||||
|
|
|
|||
|
|
@ -2015,12 +2015,40 @@ Implemented: selected insert wet amount (0–1) and chain output gain (0–4) co
|
|||
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.
|
||||
- [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: installed host ZamEQ2, ZamAutoSat and ZamDelay VST3 effects load together, render finite nonzero output and export a non-silent WAV. A bounded VST3 output-parameter sink fixes host assertion logging; the fixture verifies notification/reset/capacity behavior. Actual format labels and scrollable compact insert graphs are verified. All 52 prior screenshots were refreshed and five real compact/VST3 captures added. Full core/reliability, four-format insert GUI, full effect/instrument GUI matrix, JACK lifecycle, ASan/UBSan core and focused/full GUI, static analysis and both staged install layouts passed. See TRACK_INSERTS.md and screenshots/README.md for exact limits and reproduction.
|
||||
|
||||
---
|
||||
|
||||
### Integrated validation for Phases 80–89
|
||||
|
||||
All ten phases have separate tested implementation commits. Verification used
|
||||
`make BUILD_DIR=build/audio-editor all test test-reliability gui`,
|
||||
`test-gui-track-fx`, the full `test-gui`, `test-gui-modern-plugins`,
|
||||
`test-gui-instruments`, `test-gui-plugins` and `test-gui-jack` targets,
|
||||
`analyze`, `test-install` and `test-install-layout`. Clang ASan/UBSan core,
|
||||
allocation guards and focused/full GUI checks passed using only the existing
|
||||
system Fontconfig leak suppression. The normal GCC build is warning-free;
|
||||
Clang/FLTK emits the existing redundant-instrumentation warning.
|
||||
|
||||
The opt-in `XTC_TEST_HOST_VST3=1` core check loaded the installed Zam VST3
|
||||
binaries, rendered their serial MIDI-track chain and validated non-silent WAV
|
||||
export. The screenshot tool refreshed the complete prior gallery; its
|
||||
`XTC_CAPTURE_HOST_VST3=1` mode captured the real loaded chain, identity view,
|
||||
parallel branches, compact workspace and creation menu. Metadata-only CLI
|
||||
inspection tests cover insert dependencies, malformed/overflow records,
|
||||
nonfinite values, missing modules and v16 migration without executing modules.
|
||||
|
||||
No dependency was added. Writer format is 17, reads 1–17. Inserts remain bounded
|
||||
mono effects with zero reported latency, control-thread edits and no native
|
||||
plugin editors or modern parameter/state persistence. Bus/hybrid inserts reject
|
||||
preparation. These tests qualify host ownership and the named configurations;
|
||||
physical hardware, arbitrary third-party plugins and feature parity with mature
|
||||
DAWs are not claimed. See TRACK_INSERTS.md and KNOWN_LIMITATIONS.md.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -65,3 +65,30 @@ 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.
|
||||
|
||||
The CLI `xtc-check --inspect project.xtc` lists insert format, slot order,
|
||||
bypass/wet settings and missing binary references without loading plugins.
|
||||
VST3 processing supplies a fixed output-parameter sink (64 IDs, final point per
|
||||
ID/block) for plugins that emit meter/parameter notifications; notifications are
|
||||
not recorded or exposed as controls.
|
||||
|
||||
Host qualification on 2026-11-06 used the installed Linux x86_64 ZamEQ2,
|
||||
ZamAutoSat and ZamDelay VST3 binaries under `/usr/lib/vst3`. All three were
|
||||
prepared together, rendered with nonzero finite output and exported to WAV.
|
||||
This verifies those default mono configurations, not arbitrary plugins or
|
||||
physical hardware. The repository fixtures cover LADSPA, CLAP, VST2 and VST3
|
||||
insert adoption and VST3 output notification bounds. Internal-plan allocation
|
||||
failure and callback guards, sanitizers, GUI/JACK lifecycle and install checks
|
||||
cover host ownership and cleanup.
|
||||
|
||||
Reproduce focused verification with:
|
||||
|
||||
```sh
|
||||
make BUILD_DIR=build/audio-editor test-track-fx test-gui-track-fx
|
||||
XTC_TEST_HOST_VST3=1 ./build/audio-editor/test_track_fx
|
||||
```
|
||||
|
||||
The host opt-in requires the three named Zam bundles; the normal tests use
|
||||
internal devices and controlled fixtures. The screenshot tool's
|
||||
`XTC_CAPTURE_HOST_VST3=1` mode captures those real host plugins; see
|
||||
[screenshots/README.md](../screenshots/README.md).
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# xtc screenshots
|
||||
|
||||
The main gallery was refreshed after Phase 68 on 2026-11-06. Two additional
|
||||
Phase 79 captures show the new sample editor and transient markers.
|
||||
The complete gallery was refreshed after Phase 89 on 2026-11-06, including
|
||||
the sample editor, compact workspace and real installed VST3 insert chains.
|
||||
These are actual rendered application windows and menus on a 1440 × 960 virtual
|
||||
X11 screen, without mockups, compositing or image edits.
|
||||
|
||||
|
|
@ -14,6 +14,21 @@ APIs and GUI history/preparation paths. Original projects, media and user
|
|||
configuration are not overwritten. Black margins are the virtual desktop;
|
||||
floating views show the actual detached windows together.
|
||||
|
||||
## Compact workspace and installed VST3 effects (Phases 80–89)
|
||||
|
||||
- [Compact sequencer workspace](compact-workspace.png)
|
||||
- [Create dropdown over the sequencer](create-track-menu.png)
|
||||
- [ZamEQ2 → ZamAutoSat → ZamDelay on the Keys track](track-inserts-vst3.png)
|
||||
- [Verified VST3 identity and adapter capabilities](track-insert-vst3-identity.png)
|
||||
- [Parallel wet/dry branches in the actual insert graph](track-inserts-parallel.png)
|
||||
|
||||
The three VST3 effects are installed on this host under `/usr/lib/vst3`, loaded
|
||||
through the real host and prepared in the track's chain. These are host graph
|
||||
and identity views; native plugin editor windows are not available. The
|
||||
identity view explicitly reports default configuration and absent parameter
|
||||
controls. Long graphs scroll horizontally. See
|
||||
[TRACK_INSERTS.md](../docs/TRACK_INSERTS.md) for precise compatibility limits.
|
||||
|
||||
## New arrangement and composition workflows
|
||||
|
||||
| Phase | Feature | Captures |
|
||||
|
|
@ -50,8 +65,8 @@ See [AUDIO_EDITOR.md](../docs/AUDIO_EDITOR.md) for limits and frame/tick roundin
|
|||
All pre-existing images below were recaptured. Mixer playback meters come from
|
||||
real JACK callbacks. The minimum-size capture uses a 1220 × 720 main window;
|
||||
the scrolled view adds twelve tracks. Audio views import the repository's
|
||||
`original-pluck.wav` and apply clip gain/fades. Plugin views use repository test
|
||||
fixtures, not commercial plugins. Internal instrument menus and delay/reverb
|
||||
`original-pluck.wav` and apply clip gain/fades. The older descriptor/control views use repository test
|
||||
fixtures. The track-insert views below use real installed host VST3 binaries. Internal instrument menus and delay/reverb
|
||||
editors show the real available devices and controls. The command palette uses
|
||||
the current application action list.
|
||||
|
||||
|
|
@ -115,3 +130,15 @@ nor starts/reconfigures the audio server.
|
|||
|
||||
Set `XTC_CAPTURE_SAMPLE_ONLY=1` for the capture tool (or the make target) to write
|
||||
only the two sample-editor images while retaining the existing gallery.
|
||||
|
||||
|
||||
To reproduce the host captures where those Zam bundles are installed:
|
||||
|
||||
```sh
|
||||
XTC_CAPTURE_HOST_VST3=1 xvfb-run -a -s '-screen 0 1440x960x24' \
|
||||
./build/audio-editor/capture_screenshots "$PWD/screenshots"
|
||||
```
|
||||
|
||||
Run `make BUILD_DIR=build/audio-editor screenshots` first to build the tool and
|
||||
refresh the full fixture/demo gallery. All captures use temporary projects and
|
||||
preserve user settings and original media.
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 65 KiB |
BIN
screenshots/compact-workspace.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
screenshots/create-track-menu.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 62 KiB |
BIN
screenshots/track-insert-vst3-identity.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
screenshots/track-inserts-parallel.png
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
screenshots/track-inserts-vst3.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 67 KiB |
|
|
@ -29,7 +29,7 @@ const char *node_name(xtc_node_type type)
|
|||
case XTC_NODE_METER:
|
||||
return "Meter";
|
||||
case XTC_NODE_PLUGIN:
|
||||
return "LADSPA plugin";
|
||||
return "Effect plugin";
|
||||
case XTC_NODE_NATIVE:
|
||||
return "Native processor";
|
||||
case XTC_NODE_INSTRUMENT:
|
||||
|
|
@ -394,7 +394,10 @@ void GraphWidget::draw()
|
|||
fl_color(FL_WHITE);
|
||||
fl_font(FL_HELVETICA_BOLD, 12);
|
||||
char text[96];
|
||||
std::snprintf(text, sizeof(text), "%s #%zu", node_name(node.type), node.id);
|
||||
xtc_plugin_spec title_plugin{};
|
||||
if(node.type==XTC_NODE_PLUGIN && xtc_graph_get_plugin(graph_,node.id,&title_plugin)==XTC_OK)
|
||||
std::snprintf(text,sizeof(text),"%s effect #%zu",xtc_plugin_format_name(title_plugin.format),node.id);
|
||||
else std::snprintf(text, sizeof(text), "%s #%zu", node_name(node.type), node.id);
|
||||
fl_push_clip(left+6, top+2, node_width-12, 40);
|
||||
fl_draw(text, left + 10, top + 20);
|
||||
fl_font(FL_HELVETICA, 12);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#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>
|
||||
|
|
@ -20,10 +21,12 @@ class TrackInsertsPanel : public Fl_Group {
|
|||
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) {
|
||||
|
|
@ -48,8 +51,9 @@ class TrackInsertsPanel : public Fl_Group {
|
|||
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();
|
||||
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_;}
|
||||
|
|
@ -112,8 +116,8 @@ class TrackInsertsPanel : public Fl_Group {
|
|||
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());}}
|
||||
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;
|
||||
|
|
@ -123,7 +127,14 @@ class TrackInsertsPanel : public Fl_Group {
|
|||
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();}
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
|
|
|||
13
src/main.c
|
|
@ -78,6 +78,19 @@ static int inspect(const xtc_project *project, uint64_t version)
|
|||
}
|
||||
}
|
||||
}
|
||||
for(i=0;i<tracks;++i) {
|
||||
xtc_track_info track;xtc_track_chain chain;size_t slot;
|
||||
xtc_project_get_track(project,i,&track);xtc_project_get_chain(project,track.id,&chain);
|
||||
if(!chain.count)continue;
|
||||
printf("Track %" PRIu64 " insert chain: %zu slots, output gain %.6g\n",track.id,chain.count,(double)chain.output_gain);
|
||||
for(slot=0;slot<chain.count;++slot) {
|
||||
const xtc_track_insert *insert=chain.slots+slot;const xtc_plugin_spec *spec=&insert->plugin;
|
||||
int available=spec->format==XTC_PLUGIN_BUILTIN || readable_file(spec->path);
|
||||
printf("%s insert track %" PRIu64 " slot %zu %s wet %.6g ",xtc_plugin_format_name(spec->format),track.id,slot+1,insert->enabled ? "enabled" : "bypassed",(double)insert->wet);
|
||||
quoted(spec->label);printf(" %s: ",available ? "available" : "MISSING");quoted(spec->path);putchar('\n');
|
||||
if(!available)++missing;
|
||||
}
|
||||
}
|
||||
printf("Dependency references missing/unreadable: %zu.\n",missing);
|
||||
puts("File availability only: WAV contents, plugin identity/initialization, JACK and hardware not checked; no plugin code executed.");
|
||||
return missing ? 2 : EXIT_SUCCESS;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,17 @@ typedef struct module {
|
|||
static module *modules;
|
||||
typedef union { clap_event_header_t header; clap_event_note_t note; clap_event_midi_t midi; } clap_note;
|
||||
typedef struct { Steinberg_Vst_IEventList api; struct xtc_modern *owner; } vst_events;
|
||||
/* Bounded output-notification sink. Keep the final point per parameter/block;
|
||||
* this host has no parameter automation recorder or native editor consumer. */
|
||||
#define VST_OUTPUT_PARAMETERS 64U
|
||||
typedef struct {
|
||||
Steinberg_Vst_IParamValueQueue api;Steinberg_Vst_ParamID id;
|
||||
Steinberg_int32 offset;Steinberg_Vst_ParamValue value;int present;
|
||||
} vst_parameter_queue;
|
||||
typedef struct {
|
||||
Steinberg_Vst_IParameterChanges api;size_t count;
|
||||
vst_parameter_queue queues[VST_OUTPUT_PARAMETERS];
|
||||
} vst_parameter_changes;
|
||||
struct xtc_modern {
|
||||
module *module;
|
||||
xtc_plugin_format format;
|
||||
|
|
@ -48,6 +59,7 @@ struct xtc_modern {
|
|||
size_t vst_count;
|
||||
unsigned char keys[16][128];
|
||||
vst_events event_list;
|
||||
vst_parameter_changes parameters;
|
||||
unsigned int callback, restart;
|
||||
struct xtc_modern *next;
|
||||
};
|
||||
|
|
@ -208,6 +220,51 @@ static Steinberg_tresult host_query(void *self, const Steinberg_TUID iid, void *
|
|||
return Steinberg_kNoInterface;
|
||||
}
|
||||
static Steinberg_uint32 host_ref(void *self) { (void)self; return 1; }
|
||||
static Steinberg_tresult parameter_query(void *self,const Steinberg_TUID iid,void **out)
|
||||
{
|
||||
if(!iid || !out)return Steinberg_kInvalidArgument;
|
||||
*out=NULL;
|
||||
if(!memcmp(iid,Steinberg_Vst_IParameterChanges_iid,16) || !memcmp(iid,Steinberg_FUnknown_iid,16)){*out=self;return 0;}
|
||||
return Steinberg_kNoInterface;
|
||||
}
|
||||
static Steinberg_tresult queue_query(void *self,const Steinberg_TUID iid,void **out)
|
||||
{
|
||||
if(!iid || !out)return Steinberg_kInvalidArgument;
|
||||
*out=NULL;
|
||||
if(!memcmp(iid,Steinberg_Vst_IParamValueQueue_iid,16) || !memcmp(iid,Steinberg_FUnknown_iid,16)){*out=self;return 0;}
|
||||
return Steinberg_kNoInterface;
|
||||
}
|
||||
static Steinberg_Vst_ParamID queue_id(void *self){return ((vst_parameter_queue *)self)->id;}
|
||||
static Steinberg_int32 queue_count(void *self){return ((vst_parameter_queue *)self)->present;}
|
||||
static Steinberg_tresult queue_get(void *self,Steinberg_int32 index,Steinberg_int32 *offset,Steinberg_Vst_ParamValue *value)
|
||||
{
|
||||
vst_parameter_queue *q=self;
|
||||
if(index || !q->present || !offset || !value)return Steinberg_kInvalidArgument;
|
||||
*offset=q->offset;*value=q->value;return 0;
|
||||
}
|
||||
static Steinberg_tresult queue_add(void *self,Steinberg_int32 offset,Steinberg_Vst_ParamValue value,Steinberg_int32 *index)
|
||||
{
|
||||
vst_parameter_queue *q=self;
|
||||
if(!index || offset<0 || !isfinite(value) || value<0 || value>1)return Steinberg_kInvalidArgument;
|
||||
q->offset=offset;q->value=value;q->present=1;*index=0;return 0;
|
||||
}
|
||||
static Steinberg_Vst_IParamValueQueueVtbl parameter_queue_vtable={queue_query,host_ref,host_ref,queue_id,queue_count,queue_get,queue_add};
|
||||
static Steinberg_int32 parameter_count(void *self){return (Steinberg_int32)((vst_parameter_changes *)self)->count;}
|
||||
static Steinberg_Vst_IParamValueQueue *parameter_get(void *self,Steinberg_int32 index)
|
||||
{
|
||||
vst_parameter_changes *c=self;
|
||||
return index<0 || (size_t)index>=c->count ? NULL : &c->queues[index].api;
|
||||
}
|
||||
static Steinberg_Vst_IParamValueQueue *parameter_add(void *self,const Steinberg_Vst_ParamID *id,Steinberg_int32 *index)
|
||||
{
|
||||
vst_parameter_changes *c=self;size_t i;
|
||||
if(!id || !index)return NULL;
|
||||
for(i=0;i<c->count;++i)if(c->queues[i].id==*id)break;
|
||||
if(i==VST_OUTPUT_PARAMETERS)return NULL;
|
||||
if(i==c->count){++c->count;c->queues[i].api.lpVtbl=¶meter_queue_vtable;c->queues[i].id=*id;c->queues[i].present=0;}
|
||||
*index=(Steinberg_int32)i;return &c->queues[i].api;
|
||||
}
|
||||
static Steinberg_Vst_IParameterChangesVtbl parameter_changes_vtable={parameter_query,host_ref,host_ref,parameter_count,parameter_get,parameter_add};
|
||||
static Steinberg_tresult host_name(void *self, Steinberg_Vst_String128 name)
|
||||
{ (void)self; if (!name) return Steinberg_kInvalidArgument; memset(name,0,sizeof(Steinberg_Vst_String128)); name[0] = 'x'; name[1] = 't'; name[2] = 'c'; return 0; }
|
||||
static Steinberg_tresult host_create(void *self, Steinberg_TUID cid, Steinberg_TUID iid, void **out)
|
||||
|
|
@ -343,6 +400,8 @@ xtc_result xtc_modern_process(xtc_modern *p, const float *input, float *output,
|
|||
in.Steinberg_Vst_AudioBusBuffers_channelBuffers32 = &p->input;
|
||||
out.Steinberg_Vst_AudioBusBuffers_channelBuffers32 = &p->output;
|
||||
data.numSamples = (Steinberg_int32)frames; data.numInputs = p->instrument ? 0 : 1; data.numOutputs = 1; data.inputs = p->instrument ? NULL : ∈ data.outputs = &out;
|
||||
p->parameters.api.lpVtbl=¶meter_changes_vtable;p->parameters.count=0;
|
||||
data.outputParameterChanges=&p->parameters.api;
|
||||
if (p->instrument) data.inputEvents = &p->event_list.api;
|
||||
ok = p->processor->lpVtbl->process(p->processor,&data) == 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -259,6 +259,22 @@ int track_insert_tests()
|
|||
CHECK(xtc_project_get_chain(s->project(),track,&chain)==XTC_OK && chain.count==1);
|
||||
auto *preset_before=s->project();CHECK(panel->load_preset("/missing/preset.xtc")!=XTC_OK && s->project()==preset_before);
|
||||
unlink("/tmp/xtc-inserts-preset.xtc");
|
||||
for(const char *variable:{"XTC_TEST_PLUGIN","XTC_TEST_CLAP","XTC_TEST_VST2","XTC_TEST_VST3"}) {
|
||||
if(const char *binary=std::getenv(variable)) {
|
||||
CHECK(window.add_track_effect(track,binary,0)==XTC_OK);
|
||||
CHECK(xtc_project_get_chain(s->project(),track,&chain)==XTC_OK && chain.count==2);
|
||||
CHECK(window.undo()==XTC_OK);panel->refresh(true);
|
||||
}
|
||||
}
|
||||
CHECK(xtc_project_get_chain(s->project(),track,&chain)==XTC_OK);
|
||||
auto restored_chain=chain;for(size_t i=1;i<XTC_TRACK_INSERTS;++i)chain.slots[i]=chain.slots[0];chain.count=XTC_TRACK_INSERTS;
|
||||
for(auto &slot:chain.slots){slot.enabled=1;slot.wet=.5f;}
|
||||
CHECK(window.set_track_chain(track,chain)==XTC_OK);
|
||||
auto *insert_canvas=graph_canvas(panel);auto *insert_scroll=dynamic_cast<Fl_Scroll *>(insert_canvas->parent());
|
||||
CHECK(insert_scroll && insert_canvas->w()>insert_scroll->w());
|
||||
insert_scroll->scroll_to(insert_canvas->w()-insert_scroll->w(),0);CHECK(insert_scroll->xposition()>0);
|
||||
const auto *full_before=s->project();CHECK(window.add_track_effect(track,XTC_BUILTIN_PATH,2)==XTC_ERR_CAPACITY && s->project()==full_before);
|
||||
CHECK(window.set_track_chain(track,restored_chain)==XTC_OK);
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -83,6 +83,26 @@ with tempfile.TemporaryDirectory(prefix="xtc-inspect-") as directory:
|
|||
):
|
||||
project.write_text(malformed)
|
||||
check("--validate", project, 1)
|
||||
# V17 track-chain dependency inspection must not dlopen the readable fake module.
|
||||
for number, name, identity in ((1, "CLAP", "org.xtc.fixture"), (2, "VST2", ""),
|
||||
(3, "VST3", "0123456789abcdef0123456789abcdef")):
|
||||
chain = ("CHAINS 1\nCHAIN 0 1 3f800000\n"
|
||||
f"INSERT 1 3f800000 {number} 0 42 1 120000 {b'effect'.hex()}\n"
|
||||
f"MODULE {str(module).encode().hex()}\nIDENTITY {identity.encode().hex() if identity else '-'}\n"
|
||||
"CONTROLS 0\nPARAMETERS 0\n")
|
||||
document = base.replace("XTC_PROJECT 10", "XTC_PROJECT 17").replace(
|
||||
"END\n", "TRACK_INSTRUMENTS 0\nLIVE_INSTRUMENT 18446744073709551615\nMARKERS 0\n" + chain + "END\n")
|
||||
project.write_text(document)
|
||||
check("--inspect", project, 0, name + " insert track 0 slot 1", "no plugin code executed", "missing/unreadable: 0")
|
||||
for malformed in (document.replace("CHAIN 0 1", "CHAIN 0 9"),
|
||||
document.replace("INSERT 1 3f800000", "INSERT 1 7f800000"),
|
||||
document.replace("CHAIN 0 1", "CHAIN 99 1")):
|
||||
project.write_text(malformed)
|
||||
check("--validate", project, 1)
|
||||
project.write_text(document.replace(str(module).encode().hex(), b"/missing/effect.so".hex()))
|
||||
check("--inspect", project, 2, name + " insert track 0", "missing/unreadable: 1")
|
||||
project.write_text(document.replace("XTC_PROJECT 17", "XTC_PROJECT 16").replace(chain, ""))
|
||||
check("--validate", project, 0, "metadata valid")
|
||||
for text, messages in [
|
||||
(base.replace("XTC_PROJECT 10", "XTC_PROJECT 999"), ("line 1", "999", "1..17")),
|
||||
("XTC_PROJECT 10\n", ("line 2", "APP")),
|
||||
|
|
|
|||
|
|
@ -45,7 +45,21 @@ static Steinberg_uint32 latency(void *self) { (void)self; return 0; }
|
|||
static Steinberg_tresult setup(void *self, struct Steinberg_Vst_ProcessSetup *data) { (void)self; return data->maxSamplesPerBlock > 0 && data->sampleRate > 0 ? 0 : -1; }
|
||||
static Steinberg_tresult processing(void *self, Steinberg_TBool value) { instance *p = from_processor(self); assert(p->active); p->started = value; return 0; }
|
||||
static Steinberg_tresult process(void *self, struct Steinberg_Vst_ProcessData *data)
|
||||
{ instance *p = from_processor(self); int32_t i; assert(p->started); for (i = 0; i < data->numSamples; ++i) data->outputs[0].Steinberg_Vst_AudioBusBuffers_channelBuffers32[0][i] = data->inputs[0].Steinberg_Vst_AudioBusBuffers_channelBuffers32[0][i]*.5f; return 0; }
|
||||
{
|
||||
instance *p=from_processor(self);int32_t i;Steinberg_int32 index=-1,offset=-1;Steinberg_Vst_ParamValue value=-1;
|
||||
Steinberg_Vst_IParameterChanges *changes=data->outputParameterChanges;Steinberg_Vst_IParamValueQueue *queue;Steinberg_Vst_ParamID id=7;
|
||||
assert(p->started && changes && changes->lpVtbl->getParameterCount(changes)==0);
|
||||
queue=changes->lpVtbl->addParameterData(changes,&id,&index);assert(queue && index==0);
|
||||
assert(queue->lpVtbl->getParameterId(queue)==7 && queue->lpVtbl->getPointCount(queue)==0);
|
||||
assert(queue->lpVtbl->addPoint(queue,0,.2,&index)==0 && queue->lpVtbl->addPoint(queue,1,.8,&index)==0);
|
||||
assert(queue->lpVtbl->getPointCount(queue)==1 && queue->lpVtbl->getPoint(queue,0,&offset,&value)==0 && offset==1 && value==.8);
|
||||
assert(changes->lpVtbl->getParameterData(changes,0)==queue && changes->lpVtbl->getParameterData(changes,-1)==NULL);
|
||||
for(id=8;id<71;++id)assert(changes->lpVtbl->addParameterData(changes,&id,&index));
|
||||
assert(changes->lpVtbl->getParameterCount(changes)==64);
|
||||
assert(changes->lpVtbl->addParameterData(changes,&id,&index)==NULL);
|
||||
for(i=0;i<data->numSamples;++i)data->outputs[0].Steinberg_Vst_AudioBusBuffers_channelBuffers32[0][i]=data->inputs[0].Steinberg_Vst_AudioBusBuffers_channelBuffers32[0][i]*.5f;
|
||||
return 0;
|
||||
}
|
||||
static Steinberg_Vst_IComponentVtbl component_vtable = {cq,add,release,init,terminate,controller,mode,buses,bus_info,routing,activate_bus,active,state,state};
|
||||
static Steinberg_Vst_IAudioProcessorVtbl processor_vtable = {pq,pa,pr,arrangements,arrangement,sample_size,latency,setup,processing,process,latency};
|
||||
static Steinberg_tresult fq(void *self, const Steinberg_TUID iid, void **out) { (void)iid; *out = self; return 0; }
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include "xtc/xtc_plugin.h"
|
||||
#include "xtc/xtc_editing.h"
|
||||
#include "xtc/xtc_audio.h"
|
||||
#include "xtc/xtc_sample.h"
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
|
|
@ -47,9 +48,36 @@ static void rendering(void)
|
|||
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);
|
||||
}
|
||||
static void host_vst3(void)
|
||||
{
|
||||
xtc_project *p=NULL;xtc_track_chain c={0};xtc_midi_plan *plan=NULL;uint64_t t,clip,note;float out[256];double energy=0;
|
||||
if(!getenv("XTC_TEST_HOST_VST3"))return;
|
||||
assert(xtc_project_create(&p)==XTC_OK && xtc_project_add_track(p,"Host VST3",&t)==XTC_OK && xtc_project_add_midi_clip(p,t,0,960,&clip)==XTC_OK && xtc_project_add_note(p,clip,0,960,69,100,0,¬e)==XTC_OK);
|
||||
const char *names[]={"ZamEQ2","ZamAutoSat","ZamDelay"};c.count=3;c.output_gain=1;
|
||||
for(size_t i=0;i<3;++i) {
|
||||
char binary[512];snprintf(binary,sizeof(binary),"/usr/lib/vst3/%s.vst3/Contents/x86_64-linux/%s.so",names[i],names[i]);
|
||||
assert(xtc_plugin_probe_format(XTC_PLUGIN_VST3,binary,0,48000,&c.slots[i].plugin)==XTC_OK);
|
||||
c.slots[i].enabled=1;c.slots[i].wet=1;printf("Host VST3 loaded: %s (%s)\n",c.slots[i].plugin.label,c.slots[i].plugin.identifier);
|
||||
}
|
||||
assert(xtc_project_set_chain(p,t,&c)==XTC_OK && xtc_midi_plan_create(p,48000,&plan)==XTC_OK);
|
||||
for(size_t block=0;block<32;++block) {
|
||||
assert(xtc_midi_plan_render(plan,block*256,NULL,0,out,256)==XTC_OK);
|
||||
for(size_t i=0;i<256;++i){assert(isfinite(out[i]));energy+=out[i]*out[i];}
|
||||
}
|
||||
assert(energy>0);xtc_midi_plan_destroy(plan);
|
||||
{char wave[]="/tmp/xtc-vst3-export-XXXXXX";int fd=mkstemp(wave),done=0;xtc_export *job=NULL;xtc_sample *sample=NULL;xtc_sample_stats stats;size_t frames;unsigned int rate;
|
||||
assert(fd>=0);close(fd);unlink(wave);
|
||||
assert(xtc_export_create(p,wave,48000,100,&job)==XTC_OK);
|
||||
while(!done)assert(xtc_export_step(job,&done)==XTC_OK);
|
||||
xtc_export_destroy(job);
|
||||
assert(xtc_sample_load(wave,&sample)==XTC_OK && xtc_sample_info(sample,&rate,&frames)==XTC_OK && rate==48000 && frames>0);
|
||||
assert(xtc_sample_analyze(sample,0,frames,&stats)==XTC_OK && stats.rms>0);
|
||||
xtc_sample_destroy(sample);unlink(wave);}
|
||||
xtc_project_destroy(p);
|
||||
}
|
||||
int main(void)
|
||||
{
|
||||
rendering();
|
||||
rendering();host_vst3();
|
||||
xtc_project *p=NULL,*q=NULL;uint64_t a,b,duplicate;xtc_track_chain c={0},got={0};
|
||||
char file[]="/tmp/xtc-chain-XXXXXX";int fd=mkstemp(file);assert(fd>=0);close(fd);
|
||||
assert(xtc_project_create(&p)==XTC_OK && xtc_project_add_track(p,"Audio",&a)==XTC_OK && xtc_project_add_track(p,"Other",&b)==XTC_OK);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
#include "../src/gui/piano_roll.hpp"
|
||||
#include "../src/gui/plugin_dialog.hpp"
|
||||
#include "../src/gui/plugin_library.hpp"
|
||||
#include "../src/gui/track_inserts.hpp"
|
||||
#include <FL/Fl_Menu_Button.H>
|
||||
#include <FL/Fl.H>
|
||||
#include <FL/Fl_Tabs.H>
|
||||
#include <FL/Fl_Menu_Bar.H>
|
||||
|
|
@ -61,6 +63,18 @@ int main(int argc,char **argv) {
|
|||
MainWindow window(false,"");main_window=&window;window.resize(0,0,1400,900);window.show();check(window.open_project(project.c_str()));pump();
|
||||
auto *s=first<SequencerWidget>(window);if(!s)throw std::runtime_error("Sequencer missing");
|
||||
auto reset=[&](){window.reset_layout();check(window.open_project(project.c_str()));window.resize(0,20,1400,900);tab("Sequencer");s->select(0,0);s->position(0);check(window.set_loop(3840,7680,true));window.fit_song();pump();};
|
||||
if(std::getenv("XTC_CAPTURE_HOST_VST3")) {
|
||||
reset();capture("compact-workspace");
|
||||
auto *create=dynamic_cast<Fl_Menu_Button *>(named(window,"Create"));if(!create)throw std::runtime_error("Missing Create dropdown");
|
||||
Fl::add_timeout(.3,[](void*){capture("create-track-menu");if(system("xdotool key Escape"))std::abort();});
|
||||
create->menu()->pulldown(create->x(),create->y(),create->w(),create->h(),nullptr,create);
|
||||
const char *effects[]={"ZamEQ2","ZamAutoSat","ZamDelay"};
|
||||
for(const char *effect:effects){std::string binary=std::string("/usr/lib/vst3/")+effect+".vst3/Contents/x86_64-linux/"+effect+".so";check(window.add_track_effect(0,binary.c_str(),0));}
|
||||
auto *inserts=window.track_inserts();inserts->select_track(0);tab("Track inserts");capture("track-inserts-vst3");
|
||||
dialog("track-insert-vst3-identity",[&]{check(inserts->inspect_slot());});
|
||||
check(inserts->set_mix(.65,1));capture("track-inserts-parallel");
|
||||
window.do_callback();std::puts("Installed host VST3 captures completed.");return 0;
|
||||
}
|
||||
if(std::getenv("XTC_CAPTURE_SAMPLE_ONLY")) {
|
||||
reset();char source[4096];if(!realpath("examples/projects/original-pluck.wav",source))throw std::runtime_error("Missing sample WAV");
|
||||
check(window.create_track(false));check(window.import_audio(source));
|
||||
|
|
|
|||