diff --git a/Makefile b/Makefile index 9aa5817..d7578f3 100644 --- a/Makefile +++ b/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 diff --git a/README.md b/README.md index 0b6b46f..0ce21d7 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/docs/KNOWN_LIMITATIONS.md b/docs/KNOWN_LIMITATIONS.md index 3bc11d2..64414b4 100644 --- a/docs/KNOWN_LIMITATIONS.md +++ b/docs/KNOWN_LIMITATIONS.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. diff --git a/docs/PROJECT_FORMAT.md b/docs/PROJECT_FORMAT.md index a16123a..9aaf525 100644 --- a/docs/PROJECT_FORMAT.md +++ b/docs/PROJECT_FORMAT.md @@ -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. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index b9fcfcb..84909ba 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -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. --- diff --git a/docs/TRACK_INSERTS.md b/docs/TRACK_INSERTS.md index 6094dda..1d0e75b 100644 --- a/docs/TRACK_INSERTS.md +++ b/docs/TRACK_INSERTS.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). diff --git a/screenshots/README.md b/screenshots/README.md index 6555ab6..fb19f6b 100644 --- a/screenshots/README.md +++ b/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. diff --git a/screenshots/about.png b/screenshots/about.png index 7cf547a..2c0a6cb 100644 Binary files a/screenshots/about.png and b/screenshots/about.png differ diff --git a/screenshots/arrangement-markers.png b/screenshots/arrangement-markers.png index b49f047..553710e 100644 Binary files a/screenshots/arrangement-markers.png and b/screenshots/arrangement-markers.png differ diff --git a/screenshots/audio-clip-properties.png b/screenshots/audio-clip-properties.png index f6729b6..d172373 100644 Binary files a/screenshots/audio-clip-properties.png and b/screenshots/audio-clip-properties.png differ diff --git a/screenshots/audio-normalization-result.png b/screenshots/audio-normalization-result.png index ec1e7c8..cbfac94 100644 Binary files a/screenshots/audio-normalization-result.png and b/screenshots/audio-normalization-result.png differ diff --git a/screenshots/audio-normalization.png b/screenshots/audio-normalization.png index b777823..de63931 100644 Binary files a/screenshots/audio-normalization.png and b/screenshots/audio-normalization.png differ diff --git a/screenshots/clap-instrument-descriptors.png b/screenshots/clap-instrument-descriptors.png index a761056..cb5c7f6 100644 Binary files a/screenshots/clap-instrument-descriptors.png and b/screenshots/clap-instrument-descriptors.png differ diff --git a/screenshots/commands.png b/screenshots/commands.png index d306fe6..96e1ce5 100644 Binary files a/screenshots/commands.png and b/screenshots/commands.png differ diff --git a/screenshots/compact-workspace.png b/screenshots/compact-workspace.png new file mode 100644 index 0000000..553710e Binary files /dev/null and b/screenshots/compact-workspace.png differ diff --git a/screenshots/create-track-menu.png b/screenshots/create-track-menu.png new file mode 100644 index 0000000..902e86b Binary files /dev/null and b/screenshots/create-track-menu.png differ diff --git a/screenshots/graph-detached.png b/screenshots/graph-detached.png index 038336d..2680060 100644 Binary files a/screenshots/graph-detached.png and b/screenshots/graph-detached.png differ diff --git a/screenshots/internal-delay.png b/screenshots/internal-delay.png index f54d8f8..88652da 100644 Binary files a/screenshots/internal-delay.png and b/screenshots/internal-delay.png differ diff --git a/screenshots/internal-instruments.png b/screenshots/internal-instruments.png index 7c9dc8c..09b7022 100644 Binary files a/screenshots/internal-instruments.png and b/screenshots/internal-instruments.png differ diff --git a/screenshots/internal-reverb.png b/screenshots/internal-reverb.png index 75b6817..f945eb2 100644 Binary files a/screenshots/internal-reverb.png and b/screenshots/internal-reverb.png differ diff --git a/screenshots/loop-range.png b/screenshots/loop-range.png index 051b236..b9b0399 100644 Binary files a/screenshots/loop-range.png and b/screenshots/loop-range.png differ diff --git a/screenshots/marker-edit.png b/screenshots/marker-edit.png index 7acdeed..d0a0ae3 100644 Binary files a/screenshots/marker-edit.png and b/screenshots/marker-edit.png differ diff --git a/screenshots/midi-arpeggio-result.png b/screenshots/midi-arpeggio-result.png index 51c4d28..6527405 100644 Binary files a/screenshots/midi-arpeggio-result.png and b/screenshots/midi-arpeggio-result.png differ diff --git a/screenshots/midi-arpeggio.png b/screenshots/midi-arpeggio.png index 2313027..435fdcc 100644 Binary files a/screenshots/midi-arpeggio.png and b/screenshots/midi-arpeggio.png differ diff --git a/screenshots/midi-chord-builder.png b/screenshots/midi-chord-builder.png index fb28f7e..4b17e84 100644 Binary files a/screenshots/midi-chord-builder.png and b/screenshots/midi-chord-builder.png differ diff --git a/screenshots/midi-chords-result.png b/screenshots/midi-chords-result.png index 7d66799..81d9dbe 100644 Binary files a/screenshots/midi-chords-result.png and b/screenshots/midi-chords-result.png differ diff --git a/screenshots/midi-consolidation.png b/screenshots/midi-consolidation.png index a1d75f0..79b4065 100644 Binary files a/screenshots/midi-consolidation.png and b/screenshots/midi-consolidation.png differ diff --git a/screenshots/midi-controller-mapping.png b/screenshots/midi-controller-mapping.png index 37232dc..d224c7b 100644 Binary files a/screenshots/midi-controller-mapping.png and b/screenshots/midi-controller-mapping.png differ diff --git a/screenshots/midi-humanize.png b/screenshots/midi-humanize.png index d494204..56b1c48 100644 Binary files a/screenshots/midi-humanize.png and b/screenshots/midi-humanize.png differ diff --git a/screenshots/midi-split.png b/screenshots/midi-split.png index 662fcad..f39be74 100644 Binary files a/screenshots/midi-split.png and b/screenshots/midi-split.png differ diff --git a/screenshots/mixer-automation.png b/screenshots/mixer-automation.png index 02df1e0..25a5540 100644 Binary files a/screenshots/mixer-automation.png and b/screenshots/mixer-automation.png differ diff --git a/screenshots/mixer-detached.png b/screenshots/mixer-detached.png index 5a96676..68ead36 100644 Binary files a/screenshots/mixer-detached.png and b/screenshots/mixer-detached.png differ diff --git a/screenshots/mixer-minimum-size.png b/screenshots/mixer-minimum-size.png index f02e624..4e3a94c 100644 Binary files a/screenshots/mixer-minimum-size.png and b/screenshots/mixer-minimum-size.png differ diff --git a/screenshots/mixer-playing.png b/screenshots/mixer-playing.png index ce7c0be..4fee17e 100644 Binary files a/screenshots/mixer-playing.png and b/screenshots/mixer-playing.png differ diff --git a/screenshots/mixer-scrolled-session.png b/screenshots/mixer-scrolled-session.png index 27eb2d3..4c04274 100644 Binary files a/screenshots/mixer-scrolled-session.png and b/screenshots/mixer-scrolled-session.png differ diff --git a/screenshots/modular-graph.png b/screenshots/modular-graph.png index 6c30106..c542a9d 100644 Binary files a/screenshots/modular-graph.png and b/screenshots/modular-graph.png differ diff --git a/screenshots/overview.png b/screenshots/overview.png index b49f047..553710e 100644 Binary files a/screenshots/overview.png and b/screenshots/overview.png differ diff --git a/screenshots/piano-roll-velocity.png b/screenshots/piano-roll-velocity.png index 140c82f..9ec4509 100644 Binary files a/screenshots/piano-roll-velocity.png and b/screenshots/piano-roll-velocity.png differ diff --git a/screenshots/plugin-controls.png b/screenshots/plugin-controls.png index 41b3be4..d0e1465 100644 Binary files a/screenshots/plugin-controls.png and b/screenshots/plugin-controls.png differ diff --git a/screenshots/plugin-descriptors.png b/screenshots/plugin-descriptors.png index 4c8f82a..8639c92 100644 Binary files a/screenshots/plugin-descriptors.png and b/screenshots/plugin-descriptors.png differ diff --git a/screenshots/plugin-library.png b/screenshots/plugin-library.png index 8c4ca22..5bf129a 100644 Binary files a/screenshots/plugin-library.png and b/screenshots/plugin-library.png differ diff --git a/screenshots/preferences-audio.png b/screenshots/preferences-audio.png index 9ff62d9..b819158 100644 Binary files a/screenshots/preferences-audio.png and b/screenshots/preferences-audio.png differ diff --git a/screenshots/preferences-editing.png b/screenshots/preferences-editing.png index 9f91374..a30e801 100644 Binary files a/screenshots/preferences-editing.png and b/screenshots/preferences-editing.png differ diff --git a/screenshots/preferences-interface.png b/screenshots/preferences-interface.png index 6c0e652..33c7f9b 100644 Binary files a/screenshots/preferences-interface.png and b/screenshots/preferences-interface.png differ diff --git a/screenshots/preferences-plugins.png b/screenshots/preferences-plugins.png index 6c374d2..ed3ac46 100644 Binary files a/screenshots/preferences-plugins.png and b/screenshots/preferences-plugins.png differ diff --git a/screenshots/preferences-project_defaults.png b/screenshots/preferences-project_defaults.png index aad049b..e23bbe4 100644 Binary files a/screenshots/preferences-project_defaults.png and b/screenshots/preferences-project_defaults.png differ diff --git a/screenshots/preferences-transport.png b/screenshots/preferences-transport.png index f4d9288..f38ccd3 100644 Binary files a/screenshots/preferences-transport.png and b/screenshots/preferences-transport.png differ diff --git a/screenshots/project-collection.png b/screenshots/project-collection.png index 6acd41c..1a60aaf 100644 Binary files a/screenshots/project-collection.png and b/screenshots/project-collection.png differ diff --git a/screenshots/ripple-result.png b/screenshots/ripple-result.png index 044ba7b..50f81fc 100644 Binary files a/screenshots/ripple-result.png and b/screenshots/ripple-result.png differ diff --git a/screenshots/ripple-time.png b/screenshots/ripple-time.png index 5b930b4..e6c2127 100644 Binary files a/screenshots/ripple-time.png and b/screenshots/ripple-time.png differ diff --git a/screenshots/sample-editor.png b/screenshots/sample-editor.png index e4eebee..c1d4f26 100644 Binary files a/screenshots/sample-editor.png and b/screenshots/sample-editor.png differ diff --git a/screenshots/sample-transients.png b/screenshots/sample-transients.png index 13be3f8..49449db 100644 Binary files a/screenshots/sample-transients.png and b/screenshots/sample-transients.png differ diff --git a/screenshots/section-navigation.png b/screenshots/section-navigation.png index b49f047..553710e 100644 Binary files a/screenshots/section-navigation.png and b/screenshots/section-navigation.png differ diff --git a/screenshots/sequencer-audio-waveform.png b/screenshots/sequencer-audio-waveform.png index ec1e7c8..cbfac94 100644 Binary files a/screenshots/sequencer-audio-waveform.png and b/screenshots/sequencer-audio-waveform.png differ diff --git a/screenshots/sequencer-detached.png b/screenshots/sequencer-detached.png index 2ac7ae0..5a4052b 100644 Binary files a/screenshots/sequencer-detached.png and b/screenshots/sequencer-detached.png differ diff --git a/screenshots/sequencer-loop.png b/screenshots/sequencer-loop.png index b49f047..553710e 100644 Binary files a/screenshots/sequencer-loop.png and b/screenshots/sequencer-loop.png differ diff --git a/screenshots/track-duplication.png b/screenshots/track-duplication.png index f2b44b4..d90c876 100644 Binary files a/screenshots/track-duplication.png and b/screenshots/track-duplication.png differ diff --git a/screenshots/track-insert-vst3-identity.png b/screenshots/track-insert-vst3-identity.png new file mode 100644 index 0000000..6a631c2 Binary files /dev/null and b/screenshots/track-insert-vst3-identity.png differ diff --git a/screenshots/track-inserts-parallel.png b/screenshots/track-inserts-parallel.png new file mode 100644 index 0000000..14b6062 Binary files /dev/null and b/screenshots/track-inserts-parallel.png differ diff --git a/screenshots/track-inserts-vst3.png b/screenshots/track-inserts-vst3.png new file mode 100644 index 0000000..3fd2235 Binary files /dev/null and b/screenshots/track-inserts-vst3.png differ diff --git a/screenshots/transport-detached.png b/screenshots/transport-detached.png index e103638..38cc181 100644 Binary files a/screenshots/transport-detached.png and b/screenshots/transport-detached.png differ diff --git a/screenshots/vst2-instrument-descriptors.png b/screenshots/vst2-instrument-descriptors.png index b924fcf..d956efb 100644 Binary files a/screenshots/vst2-instrument-descriptors.png and b/screenshots/vst2-instrument-descriptors.png differ diff --git a/screenshots/vst3-instrument-descriptors.png b/screenshots/vst3-instrument-descriptors.png index 7bce34b..28d8532 100644 Binary files a/screenshots/vst3-instrument-descriptors.png and b/screenshots/vst3-instrument-descriptors.png differ diff --git a/screenshots/workspace-detached.png b/screenshots/workspace-detached.png index 5f4b8b5..0eed4dc 100644 Binary files a/screenshots/workspace-detached.png and b/screenshots/workspace-detached.png differ diff --git a/src/gui/graph_widget.cpp b/src/gui/graph_widget.cpp index 6368d12..e70cd99 100644 --- a/src/gui/graph_widget.cpp +++ b/src/gui/graph_widget.cpp @@ -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); diff --git a/src/gui/track_inserts.hpp b/src/gui/track_inserts.hpp index 34cdd44..b1c0b47 100644 --- a/src/gui/track_inserts.hpp +++ b/src/gui/track_inserts.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -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 ids_; + std::vector 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(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(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 ids;std::vector names; for(size_t i=0;iclear();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;ivalue(static_cast(i)); xtc_track_chain chain{};chain.output_gain=1; @@ -123,7 +127,14 @@ class TrackInsertsPanel : public Fl_Group { for(size_t i=0;iadd(label.c_str());} if(chain.count)slots_->select(selected>0 && static_cast(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 rows{}; + xtc_graph_get_counts(model_,&nodes,&edges); + for(size_t i=0;irouting(node.id,route))max_level=std::max(max_level,route.level);} + graph_->size(std::max(scroll_->w()-20,static_cast(max_level+1)*174+40),4000); + for(size_t i=0;irouting(node.id,route) && route.levelmove_node(node.id,20+static_cast(route.level)*174,24+static_cast(row)*104);}} + graph_->size(graph_->w(),std::max(scroll_->h()-20,static_cast(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(); } }; diff --git a/src/main.c b/src/main.c index 74eaaa9..d72683b 100644 --- a/src/main.c +++ b/src/main.c @@ -78,6 +78,19 @@ static int inspect(const xtc_project *project, uint64_t version) } } } + for(i=0;iplugin; + 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; diff --git a/src/plugin/modern.c b/src/plugin/modern.c index 60eeaf6..013f606 100644 --- a/src/plugin/modern.c +++ b/src/plugin/modern.c @@ -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;icount;++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; } diff --git a/tests/integration/test_gui.cpp b/tests/integration/test_gui.cpp index 68c2ca1..7eedfb6 100644 --- a/tests/integration/test_gui.cpp +++ b/tests/integration/test_gui.cpp @@ -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(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; diff --git a/tests/integration/test_project_inspection.py b/tests/integration/test_project_inspection.py index b17a35f..577ce51 100644 --- a/tests/integration/test_project_inspection.py +++ b/tests/integration/test_project_inspection.py @@ -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")), diff --git a/tests/plugins/vst3_fixture.c b/tests/plugins/vst3_fixture.c index 6176e57..0db1675 100644 --- a/tests/plugins/vst3_fixture.c +++ b/tests/plugins/vst3_fixture.c @@ -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;inumSamples;++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; } diff --git a/tests/unit/test_track_fx.c b/tests/unit/test_track_fx.c index a5d4663..3ace6cd 100644 --- a/tests/unit/test_track_fx.c +++ b/tests/unit/test_track_fx.c @@ -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 #include #include @@ -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); diff --git a/tools/capture_screenshots.cpp b/tools/capture_screenshots.cpp index 0f1cc41..7a9c9c3 100644 --- a/tools/capture_screenshots.cpp +++ b/tools/capture_screenshots.cpp @@ -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 #include #include #include @@ -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(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(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));