Added VST SDK support.
This commit is contained in:
parent
a1482ec110
commit
8e09f58944
25 changed files with 850 additions and 23 deletions
32
Makefile
32
Makefile
|
|
@ -27,7 +27,7 @@ CXXFLAGS ?= -O2 -g
|
|||
CXXFLAGS += -std=c++17 -Wall -Wextra -Wpedantic -Iinclude
|
||||
LDFLAGS ?=
|
||||
|
||||
CORE_SOURCES := src/midi/file.c src/midi/output.c src/project/crossfade.c src/control/tempo.c src/project/chain_tools.c src/project/chain_preset.c src/mixer/track_fx.c src/audio/sample.c src/persistence/collect.c src/audio/normalize.c src/project/sections.c src/project/editing.c src/audio/export.c src/plugin/builtin.c src/settings/settings.c src/control/model.c src/audio/waveform.c src/plugin/ladspa.c src/plugin/host.c src/plugin/modern.c src/native/processor.c src/audio/recovery.c src/dsp/ramp.c src/native/native.c src/mixer/model.c src/mixer/render.c src/project/history.c src/midi/model.c src/midi/instrument.c src/audio/wave.c src/audio/arrangement.c src/audio/recorder.c src/core/xtc.c src/project/project.c src/persistence/project_file.c src/graph/graph.c src/dsp/gain.c src/engine/offline_engine.c src/engine/realtime.c src/backend/jack_backend.c
|
||||
CORE_SOURCES := src/plugin/module_path.c src/midi/file.c src/midi/output.c src/project/crossfade.c src/control/tempo.c src/project/chain_tools.c src/project/chain_preset.c src/mixer/track_fx.c src/audio/sample.c src/persistence/collect.c src/audio/normalize.c src/project/sections.c src/project/editing.c src/audio/export.c src/plugin/builtin.c src/settings/settings.c src/control/model.c src/audio/waveform.c src/plugin/ladspa.c src/plugin/host.c src/plugin/modern.c src/native/processor.c src/audio/recovery.c src/dsp/ramp.c src/native/native.c src/mixer/model.c src/mixer/render.c src/project/history.c src/midi/model.c src/midi/instrument.c src/audio/wave.c src/audio/arrangement.c src/audio/recorder.c src/core/xtc.c src/project/project.c src/persistence/project_file.c src/graph/graph.c src/dsp/gain.c src/engine/offline_engine.c src/engine/realtime.c src/backend/jack_backend.c
|
||||
JACK_AVAILABLE := $(shell $(PKG_CONFIG) --exists jack 2>/dev/null && echo yes)
|
||||
ifeq ($(JACK_AVAILABLE),yes)
|
||||
JACK_CFLAGS := $(shell $(PKG_CONFIG) --cflags jack)
|
||||
|
|
@ -616,3 +616,33 @@ test-configuration-startup: gui
|
|||
.PHONY: test-gui-midi-labels
|
||||
test-gui-midi-labels: $(BUILD_DIR)/test_gui
|
||||
XTC_TEST_MIDI_LABELS_ONLY=1 xvfb-run -a -s "-screen 0 1440x960x24" ./$(BUILD_DIR)/test_gui
|
||||
|
||||
# Optional external Steinberg SDK qualification. Normal builds never fetch SDKs.
|
||||
VST3_SDK_ROOT ?=
|
||||
STEINBERG_BUILD_DIR ?= $(BUILD_DIR)/steinberg-sdk
|
||||
.PHONY: steinberg-sdk test-steinberg-sdk test-plugin-module-path
|
||||
steinberg-sdk:
|
||||
@test -n "$(VST3_SDK_ROOT)" || { echo "Set VST3_SDK_ROOT to an official recursive SDK checkout."; exit 1; }
|
||||
cmake -S examples/steinberg -B "$(STEINBERG_BUILD_DIR)" -DXTC_VST3_SDK_ROOT="$(abspath $(VST3_SDK_ROOT))" -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build "$(STEINBERG_BUILD_DIR)" --target xtc-sdk-examples validator --parallel 4
|
||||
|
||||
$(BUILD_DIR)/test_plugin_module_path: $(BUILD_DIR)/tests/unit/test_plugin_module_path.o $(BUILD_DIR)/libxtc.a
|
||||
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
|
||||
|
||||
test-plugin-module-path: $(BUILD_DIR)/test_plugin_module_path
|
||||
./$(BUILD_DIR)/test_plugin_module_path
|
||||
|
||||
test: test-plugin-module-path
|
||||
|
||||
$(BUILD_DIR)/test_steinberg_sdk: $(BUILD_DIR)/tests/integration/test_steinberg_sdk.o $(BUILD_DIR)/libxtc.a
|
||||
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
|
||||
|
||||
$(BUILD_DIR)/steinberg-render: $(BUILD_DIR)/examples/steinberg/render.o $(BUILD_DIR)/libxtc.a
|
||||
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl
|
||||
|
||||
# Linux bundle convention; native Windows SDK examples use CMake directly.
|
||||
test-steinberg-sdk: steinberg-sdk $(BUILD_DIR)/test_steinberg_sdk $(BUILD_DIR)/steinberg-render
|
||||
"$(STEINBERG_BUILD_DIR)/bin/Release/validator" "$(abspath $(STEINBERG_BUILD_DIR))/VST3/Release/xtc-sdk-examples.vst3"
|
||||
./$(BUILD_DIR)/test_steinberg_sdk "$(abspath $(STEINBERG_BUILD_DIR))/VST3/Release/xtc-sdk-examples.vst3"
|
||||
|
||||
-include $(BUILD_DIR)/tests/unit/test_plugin_module_path.d $(BUILD_DIR)/tests/integration/test_steinberg_sdk.d $(BUILD_DIR)/examples/steinberg/render.d
|
||||
|
|
|
|||
|
|
@ -194,3 +194,10 @@ JACK/PipeWire input/output choices, channel filtering and optional thru. Use
|
|||
Application preferences load from **`~/.xtc.conf`** at startup and save on Apply
|
||||
and clean exit. See [configuration and MIDI workflows](docs/MIDI_WORKFLOWS.md)
|
||||
for file support, routing and persistence details.
|
||||
|
||||
|
||||
[Steinberg SDK integration (Phase 106)](docs/STEINBERG.md) adds conventional Linux
|
||||
VST3 bundle loading, transport context, official-SDK gain/synth examples and
|
||||
validator checks. Run `make VST3_SDK_ROOT=/path/to/vst3sdk test-steinberg-sdk`
|
||||
for optional SDK qualification. A Windows ASIO device diagnostic is provided;
|
||||
a native ASIO playback/capture backend remains planned.
|
||||
|
|
|
|||
|
|
@ -233,9 +233,10 @@ parameters.
|
|||
|
||||
Choose the actual binary: `.clap` for CLAP, a VST2 `.so` exporting
|
||||
`VSTPluginMain`, or a VST3 bundle's `Contents/<architecture>-linux/<module>.so`
|
||||
exporting `ModuleEntry`, `ModuleExit` and `GetPluginFactory`. The chooser does
|
||||
not resolve bundle directories or recursively scan plugin folders. On Linux this
|
||||
avoids executing unrelated plugins just to discover one selected module.
|
||||
exporting `ModuleEntry`, `ModuleExit` and `GetPluginFactory`. The C API also
|
||||
accepts conventional `.vst3` bundle paths and resolves the matching architecture
|
||||
and bundle-stem binary. The file chooser may require selecting the inner binary;
|
||||
no recursive plugin-folder scan is performed.
|
||||
Auto-detection prefers CLAP, then VST2, then VST3, otherwise LADSPA; the C API
|
||||
also provides `xtc_plugin_probe_format` for an explicit format.
|
||||
|
||||
|
|
@ -312,3 +313,9 @@ 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.
|
||||
|
||||
|
||||
Phase 106 adds [official SDK examples and qualification](STEINBERG.md), including
|
||||
VST3 process context (sample position, rate, tempo and musical position), an
|
||||
SDK-built gain/synth pair and a WAV render example. Native Windows ASIO remains
|
||||
in progress; the optional ASIO SDK example currently provides device diagnostics.
|
||||
|
|
|
|||
|
|
@ -3363,6 +3363,83 @@ from software-port tests. No dependencies or project-format change were added.
|
|||
See [MIDI_WORKFLOWS.md](MIDI_WORKFLOWS.md), [PREFERENCES.md](PREFERENCES.md) and
|
||||
[the gallery](../screenshots/README.md) for reproduction and exact limits.
|
||||
|
||||
---
|
||||
|
||||
## Phase 106 — Steinberg VST SDK and ASIO Integration
|
||||
|
||||
### Goal
|
||||
|
||||
Use the official Steinberg SDKs to qualify and extend xtc's VST hosting and
|
||||
prepare native Windows ASIO audio I/O, with runnable examples. Keep the C99
|
||||
engine and public C API; SDK-specific C++ belongs in optional tools/examples or
|
||||
an isolated platform bridge. This is a staged phase, **in progress**, not a
|
||||
claim that every VST extension or ASIO driver is supported.
|
||||
|
||||
### Tasks and acceptance criteria
|
||||
|
||||
- [x] Add an opt-in external VST3 SDK CMake build, official validator target,
|
||||
and original SDK-built gain effect and MIDI instrument examples. Ordinary
|
||||
builds must remain offline and SDK-independent.
|
||||
- [x] Load conventional Linux VST3 bundles as well as inner binaries, preserving
|
||||
the chosen bundle path in plugin metadata and existing module ownership.
|
||||
- [x] Supply VST3 process context with sample position, sample rate, tempo and
|
||||
musical position for effect and instrument processing, including seeks.
|
||||
- [x] Provide a headless C example rendering the SDK synth through the gain
|
||||
effect to a no-clobber WAV, and automated SDK/xtc interoperability checks.
|
||||
- [ ] Qualify the ASIO SDK device/channel/format/latency diagnostic example on
|
||||
native Windows with a real installed driver. Explicit driver selection;
|
||||
no automatic installation, sample-rate changes or device routing.
|
||||
- [ ] Add VST3 edit-controller lifecycle/connection handling, normalized
|
||||
parameters, sample-accurate input automation and parameter feedback to
|
||||
xtc's existing controls and undo paths; test with SDK and real plugins.
|
||||
- [ ] Add bounded component/controller opaque-state persistence and transactional
|
||||
reload, with relocation/missing-module and corrupt-state tests.
|
||||
- [ ] Extend the graph/host contract for native stereo, multiple buses,
|
||||
sidechains, latency compensation, tails and float64 negotiation; retain
|
||||
explicit rejection until each layout is supported and tested.
|
||||
- [ ] Embed native VST3 editors with Linux run-loop/resize ownership, close and
|
||||
reopen tests, and main/audio-thread separation. Extend note expression,
|
||||
MIDI mapping and process-context requirements without inventing timing.
|
||||
- [ ] Extend legacy VST2 interoperability (parameters/state/editor/transport)
|
||||
against user-supplied licensed plugins. The discontinued VST2 SDK is not
|
||||
supplied by VST3 and must not be presented as a downloadable dependency.
|
||||
- [ ] Introduce a native Windows ASIO backend behind the C backend interface:
|
||||
explicit driver/device selection, buffer negotiation, integer/float sample
|
||||
conversion, double buffers/time-info callbacks, channel mapping, latency,
|
||||
reset/resync/rate-change recovery and callback quiescence. First establish
|
||||
a Windows build of the C engine; do not pretend ASIO is a Linux backend.
|
||||
- [ ] Qualify ASIO start/stop/reopen, playback/capture, MIDI coexistence, format
|
||||
conversion and failure cleanup with deterministic driver tests and real
|
||||
Windows hardware loopback; record SDK, OS, driver and hardware versions.
|
||||
- [ ] Audit SDK licenses/notices and packaging for each enabled integration;
|
||||
update examples, user documentation and the compatibility matrix after
|
||||
each completed slice. No third-party plugin redistribution implied.
|
||||
|
||||
### Implementation status
|
||||
|
||||
The first slice adds optional SDK builds/examples, Linux bundle resolution and
|
||||
VST3 transport context. See [STEINBERG.md](STEINBERG.md) and
|
||||
[examples/steinberg](../examples/steinberg/README.md) for reproduction and exact
|
||||
limits. The optional ASIO diagnostic source uses a separately supplied SDK;
|
||||
Windows compilation and hardware validation remain pending. The application
|
||||
still uses JACK/PipeWire on Linux. Controller/editor/opaque-state, richer bus
|
||||
layouts and the native Windows backend remain open acceptance criteria above.
|
||||
|
||||
### Validation of the initial slice
|
||||
|
||||
On 2026-11-06, `make BUILD_DIR=build/steinberg-host all test` passed with
|
||||
warnings enabled. The optional official-SDK target passed Steinberg validator
|
||||
**94/94** plus xtc gain/synth, sample-offset and bundle-path save/reload checks
|
||||
against SDK revision `3cdf9ca5d1f5b1b21e0a86832aa4abe55607bd96` (3.8.1).
|
||||
Focused Clang ASan/UBSan module-path, modern-host and SDK interoperability tests
|
||||
passed; static analysis of path resolution and the host facade reported no
|
||||
findings. Independent WAV checks confirmed float32/mono/48 kHz, 96000 frames,
|
||||
note-off silence and no-clobber destination preservation. Metadata inspection
|
||||
accepts readable bundle binaries without executing them. Linux ASIO-option
|
||||
configuration correctly rejects the unsupported platform; no Windows build or
|
||||
ASIO hardware result is claimed.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Recommended Execution Order
|
||||
|
|
|
|||
57
docs/STEINBERG.md
Normal file
57
docs/STEINBERG.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# Steinberg SDK integration (Phase 106)
|
||||
|
||||
The VST3 host uses Steinberg's pinned generated C ABI in the C99 core. Optional
|
||||
SDK examples now also build against the full, separately supplied official C++
|
||||
SDK and run Steinberg's validator. This adds an independent interoperability
|
||||
check without adding a C++ runtime or network dependency to libxtc.
|
||||
|
||||
## Working integration
|
||||
|
||||
- Linux `.vst3` bundles resolve to
|
||||
`Contents/<architecture>-linux/<bundle-stem>.so`. Direct `.so` paths still
|
||||
work. The original chosen path remains the saved identity reference. This
|
||||
supports conventional bundles, not arbitrary renamed modules or Windows DLLs.
|
||||
- VST3 processing receives sample rate, absolute sample position, tempo and
|
||||
musical position (`samples / rate * BPM / 60`). Position follows the existing
|
||||
effect/instrument `_at` APIs, including seek. Tempo is the prepared plugin
|
||||
spec's milli-BPM value; reprepare when it changes. Processing is marked playing
|
||||
because this API represents active rendering, including offline rendering.
|
||||
Loop, recording, wall-clock, time signature and continuous-time flags are not
|
||||
asserted: the current plugin API does not carry that information. Positions
|
||||
above `INT64_MAX` reject before processing, matching VST3's signed sample clock.
|
||||
- Original SDK-built examples expose a mono half-gain effect and a mono,
|
||||
16-channel sine instrument. The instrument handles note-on/off at their exact
|
||||
block offsets; the host translates all-notes-off to held-note releases.
|
||||
- The C render example demonstrates the actual xtc plugin facade and WAV writer,
|
||||
including no-clobber output, without requiring JACK or a GUI.
|
||||
|
||||
See [the example instructions](../examples/steinberg/README.md) for SDK versions,
|
||||
build commands, validation and ASIO diagnostics.
|
||||
|
||||
## ASIO and VST2 scope
|
||||
|
||||
ASIO is native driver I/O, separate from VST plugin hosting. The optional Windows
|
||||
diagnostic enumerates drivers and inspects an explicitly selected driver's
|
||||
channels, sample types, current sample rate, buffer bounds and reported latency.
|
||||
It does not create/start audio buffers and is **not an xtc ASIO backend**.
|
||||
The current xtc engine/backend still targets Linux/POSIX and JACK/PipeWire.
|
||||
Windows portability, a C API platform bridge and native-driver qualification
|
||||
are remaining Phase 106 work, not simulated by Linux tests.
|
||||
|
||||
VST2 support retains the existing original minimal ABI declarations. VST3's SDK
|
||||
does not include the discontinued VST2 SDK. No VST2 SDK or user plugin is copied,
|
||||
fetched or licensed by these build targets.
|
||||
|
||||
## Remaining work
|
||||
|
||||
The application VST3 adapter still accepts one mono input (or no audio input for
|
||||
instruments), one mono output, float32 and zero reported latency. Its two mono
|
||||
instances are not a native stereo plugin instance. It uses plugin defaults;
|
||||
controller parameters, opaque-state persistence and custom editor windows remain
|
||||
unimplemented in xtc. The SDK examples include an edit-controller class for
|
||||
standard SDK-host compatibility; xtc does not instantiate it yet. The examples
|
||||
have no editable parameters or opaque state, so they do not prove those paths.
|
||||
No VST extension support or general third-party compatibility is inferred from
|
||||
the validator passing these examples.
|
||||
|
||||
The complete acceptance checklist is in [Phase 106](ROADMAP.md#phase-106--steinberg-vst-sdk-and-asio-integration).
|
||||
|
|
@ -12,3 +12,8 @@ listening acceptance. It never starts a backend or plays audio.
|
|||
|
||||
`projects/midi-demo.xtc` is an original media/plugin-free MIDI example installed
|
||||
under `share/xtc/examples`. See projects/README.md for opening and playback.
|
||||
|
||||
[Steinberg SDK examples](steinberg/README.md) build an official-SDK VST3 gain
|
||||
and MIDI synth, qualify them with Steinberg's validator and the xtc C host, and
|
||||
render a WAV offline. An optional Windows ASIO device diagnostic is also provided;
|
||||
it is not yet a qualified audio backend.
|
||||
|
|
|
|||
52
examples/steinberg/CMakeLists.txt
Normal file
52
examples/steinberg/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
cmake_minimum_required(VERSION 3.25)
|
||||
project(xtc_steinberg_examples VERSION 1.0.0 LANGUAGES C CXX)
|
||||
set(XTC_VST3_SDK_ROOT "" CACHE PATH "External official VST3 SDK checkout (with submodules)")
|
||||
option(XTC_BUILD_ASIO_EXAMPLE "Build Windows ASIO device diagnostics" OFF)
|
||||
set(XTC_ASIO_SDK_ROOT "" CACHE PATH "External ASIO SDK root")
|
||||
|
||||
if(XTC_VST3_SDK_ROOT)
|
||||
if(NOT EXISTS "${XTC_VST3_SDK_ROOT}/public.sdk/source/vst/vstaudioeffect.h")
|
||||
message(FATAL_ERROR "XTC_VST3_SDK_ROOT needs the official SDK and its submodules")
|
||||
endif()
|
||||
# Never install/link example plugins into the user's plugin directories.
|
||||
set(SMTG_CREATE_PLUGIN_LINK OFF CACHE BOOL "" FORCE)
|
||||
set(SMTG_RUN_VST_VALIDATOR OFF CACHE BOOL "" FORCE)
|
||||
set(SMTG_ENABLE_VSTGUI_SUPPORT OFF CACHE BOOL "" FORCE)
|
||||
set(SMTG_ENABLE_VST3_PLUGIN_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
set(SMTG_ENABLE_VST3_HOSTING_EXAMPLES ON CACHE BOOL "" FORCE)
|
||||
add_subdirectory("${XTC_VST3_SDK_ROOT}" sdk)
|
||||
set(public_sdk_SOURCE_DIR "${XTC_VST3_SDK_ROOT}/public.sdk")
|
||||
smtg_add_vst3plugin(xtc-sdk-examples sdk_plugins.cpp)
|
||||
target_link_libraries(xtc-sdk-examples PRIVATE sdk)
|
||||
target_compile_features(xtc-sdk-examples PRIVATE cxx_std_17)
|
||||
target_include_directories(xtc-sdk-examples PRIVATE "${XTC_VST3_SDK_ROOT}")
|
||||
if(MSVC)
|
||||
target_compile_options(xtc-sdk-examples PRIVATE /W4)
|
||||
else()
|
||||
target_compile_options(xtc-sdk-examples PRIVATE -Wall -Wextra -Wpedantic)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(XTC_BUILD_ASIO_EXAMPLE)
|
||||
if(NOT WIN32)
|
||||
message(FATAL_ERROR "The ASIO example needs Windows and an installed ASIO driver; Linux uses JACK/PipeWire")
|
||||
endif()
|
||||
foreach(source common/asio.cpp host/asiodrivers.cpp host/pc/asiolist.cpp)
|
||||
if(NOT EXISTS "${XTC_ASIO_SDK_ROOT}/${source}")
|
||||
message(FATAL_ERROR "XTC_ASIO_SDK_ROOT is missing ${source}")
|
||||
endif()
|
||||
endforeach()
|
||||
add_executable(xtc-asio-devices asio_devices.cpp
|
||||
"${XTC_ASIO_SDK_ROOT}/common/asio.cpp"
|
||||
"${XTC_ASIO_SDK_ROOT}/host/asiodrivers.cpp"
|
||||
"${XTC_ASIO_SDK_ROOT}/host/pc/asiolist.cpp")
|
||||
target_include_directories(xtc-asio-devices PRIVATE
|
||||
"${XTC_ASIO_SDK_ROOT}/common" "${XTC_ASIO_SDK_ROOT}/host"
|
||||
"${XTC_ASIO_SDK_ROOT}/host/pc")
|
||||
target_compile_features(xtc-asio-devices PRIVATE cxx_std_17)
|
||||
target_link_libraries(xtc-asio-devices PRIVATE ole32 advapi32 uuid user32)
|
||||
endif()
|
||||
|
||||
if(NOT XTC_VST3_SDK_ROOT AND NOT XTC_BUILD_ASIO_EXAMPLE)
|
||||
message(FATAL_ERROR "Set XTC_VST3_SDK_ROOT or enable XTC_BUILD_ASIO_EXAMPLE with XTC_ASIO_SDK_ROOT")
|
||||
endif()
|
||||
91
examples/steinberg/README.md
Normal file
91
examples/steinberg/README.md
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# Official Steinberg SDK examples
|
||||
|
||||
These are optional integration examples. Normal `make` / `make test` uses the
|
||||
existing pinned C ABI headers and never downloads SDKs. Original example source
|
||||
is MIT-licensed like xtc. SDK code is compiled from an external checkout, not
|
||||
vendored or installed by these targets. C++ is confined to the SDK examples;
|
||||
`render.c` and the xtc host remain C99.
|
||||
|
||||
## VST3 effect and instrument (Linux)
|
||||
|
||||
Requirements: the normal C toolchain, C++17, CMake >= 3.25, and an official VST3
|
||||
SDK checkout with its submodules. SDK hosting utilities can require additional
|
||||
platform development libraries (the inspected SDK discovers X11/GTK). Dependencies
|
||||
are never installed automatically. Obtain the SDK explicitly, for example:
|
||||
|
||||
```sh
|
||||
git clone --recursive https://github.com/steinbergmedia/vst3sdk.git /your/path/vst3sdk
|
||||
```
|
||||
|
||||
Qualified SDK revision: `3cdf9ca5d1f5b1b21e0a86832aa4abe55607bd96`
|
||||
(3.8.1; MIT license). To reproduce this revision, checkout it and run
|
||||
`git submodule update --init --recursive`. Review the license of the actual SDK
|
||||
revision used; this example neither bundles SDK sources nor grants plugin or
|
||||
trademark distribution rights.
|
||||
|
||||
From the xtc repository root:
|
||||
|
||||
```sh
|
||||
make VST3_SDK_ROOT=/your/path/vst3sdk test-steinberg-sdk
|
||||
```
|
||||
|
||||
This builds the original `xtc-sdk-examples.vst3` bundle using Steinberg's factory,
|
||||
AudioEffect, controller and platform-entry implementations; runs the official
|
||||
validator; and runs xtc's gain/MIDI interoperability checks. Build output stays
|
||||
under `build/steinberg-sdk`; automatic plugin-directory links and installation
|
||||
are disabled. SDK source may emit its own compiler warnings.
|
||||
|
||||
The bundle contains:
|
||||
|
||||
| Factory index | Class | Behavior |
|
||||
| --- | --- | --- |
|
||||
| 0 | xtc SDK Half Gain | mono float32 input multiplied by 0.5 |
|
||||
| 1 | xtc SDK Sine | mono MIDI synth; 16 channels; note velocity; note-on/off offsets |
|
||||
| 2 | xtc SDK Controller | non-audio class; no editable parameters |
|
||||
|
||||
Both audio classes are zero-latency, stateless, default-configuration examples.
|
||||
These make good host compatibility checks; they are not production instruments.
|
||||
The sine example uses immediate note release and has no envelope or antialiasing.
|
||||
|
||||
Create a two-second WAV (one second of A4 followed by silence) through the actual
|
||||
xtc synth -> effect host path:
|
||||
|
||||
```sh
|
||||
./build/steinberg-render \
|
||||
"$PWD/build/steinberg-sdk/VST3/Release/xtc-sdk-examples.vst3" \
|
||||
/absolute/path/to/new-sdk-demo.wav
|
||||
```
|
||||
|
||||
The destination must not exist. This executable is built by `test-steinberg-sdk`.
|
||||
To use a different xtc build directory, pass `BUILD_DIR=...`; the SDK build then
|
||||
lives under that directory unless `STEINBERG_BUILD_DIR=...` is explicitly set.
|
||||
|
||||
The main app's existing plugin loader can use the bundle path through its C API;
|
||||
its file chooser may require selecting the inner `.so`. The effect loads as a
|
||||
track insert; the synth is available to the instrument descriptor workflow.
|
||||
See [the hosting contract](../../docs/STEINBERG.md) for remaining limitations.
|
||||
|
||||
## ASIO driver diagnostics (Windows, pending qualification)
|
||||
|
||||
Supply the official ASIO SDK separately. The source layout targets the inspected
|
||||
ASIO 2.3.3 SDK (`common/asio.cpp`, `host/asiodrivers.cpp`, `host/pc/asiolist.cpp`).
|
||||
Its Steinberg SDK license differs from the VST3 revision above; follow the
|
||||
license accompanying your SDK before redistribution. SDK source or binaries are
|
||||
not included in xtc packages. A current SDK may require adapting its source layout.
|
||||
|
||||
On native Windows with a matching MSVC toolchain and an installed ASIO driver:
|
||||
|
||||
```powershell
|
||||
cmake -S examples/steinberg -B build/asio-sdk -DXTC_BUILD_ASIO_EXAMPLE=ON -DXTC_ASIO_SDK_ROOT=C:/SDKs/asiosdk
|
||||
cmake --build build/asio-sdk --config Release --target xtc-asio-devices
|
||||
./build/asio-sdk/Release/xtc-asio-devices.exe
|
||||
./build/asio-sdk/Release/xtc-asio-devices.exe --driver "Exact installed driver name"
|
||||
```
|
||||
|
||||
The first invocation lists drivers registered for the executable's architecture.
|
||||
The second opens only the selected driver, queries its existing channel formats,
|
||||
sample rate, buffer constraints and latencies, then exits. It never calls
|
||||
ASIOStart, ASIOCreateBuffers, ASIOSetSampleRate or the driver's control panel.
|
||||
Linux configuration with this option deliberately fails with a platform message.
|
||||
Windows compilation and real driver execution have **not** been verified here.
|
||||
This is a diagnostic example, not audio playback/capture or an app backend.
|
||||
81
examples/steinberg/asio_devices.cpp
Normal file
81
examples/steinberg/asio_devices.cpp
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
// Original MIT-licensed diagnostics; build against a separately supplied ASIO SDK.
|
||||
// This example inspects an explicitly selected driver. It never starts audio,
|
||||
// changes sample rate, opens a control panel, or installs/registers a driver.
|
||||
#include "asiosys.h"
|
||||
#include "asio.h"
|
||||
#include "asiodrivers.h"
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
// The SDK dispatch layer (including ASIOExit) uses this SDK-owned pointer.
|
||||
extern AsioDrivers* asioDrivers;
|
||||
|
||||
namespace {
|
||||
struct Driver {
|
||||
AsioDrivers drivers;
|
||||
bool initialized = false;
|
||||
Driver() { asioDrivers = &drivers; }
|
||||
~Driver() {
|
||||
if (initialized) ASIOExit();
|
||||
drivers.removeCurrentDriver();
|
||||
asioDrivers = nullptr;
|
||||
}
|
||||
};
|
||||
}
|
||||
int main(int argc, char** argv) {
|
||||
if (argc != 1 && (argc != 3 || std::strcmp(argv[1], "--driver"))) {
|
||||
std::fprintf(stderr, "Usage: xtc-asio-devices [--driver \"installed driver name\"]\n");
|
||||
return 2;
|
||||
}
|
||||
Driver session;
|
||||
if (argc == 1) {
|
||||
const long count = session.drivers.asioGetNumDev();
|
||||
if (count < 0) return 1;
|
||||
for (long i = 0; i < count; ++i) {
|
||||
std::array<char, MAXDRVNAMELEN> name{};
|
||||
if (session.drivers.asioGetDriverName(static_cast<int>(i), name.data(),
|
||||
static_cast<int>(name.size())) != 0) return 1;
|
||||
std::puts(name.data());
|
||||
}
|
||||
if (!count) std::puts("No ASIO drivers registered for this executable's architecture.");
|
||||
return 0;
|
||||
}
|
||||
// ASIO SDK uses char*, but does not retain or modify this argument.
|
||||
if (!session.drivers.loadDriver(argv[2])) {
|
||||
std::fprintf(stderr, "Cannot load the named ASIO driver.\n"); return 1;
|
||||
}
|
||||
ASIODriverInfo info{};
|
||||
info.asioVersion = 2;
|
||||
info.sysRef = GetDesktopWindow();
|
||||
if (ASIOInit(&info) != ASE_OK) {
|
||||
std::fprintf(stderr, "ASIO initialization failed: %.124s\n", info.errorMessage); return 1;
|
||||
}
|
||||
session.initialized = true;
|
||||
long inputs = 0, outputs = 0, minimum = 0, maximum = 0, preferred = 0, granularity = 0;
|
||||
long inputLatency = 0, outputLatency = 0;
|
||||
ASIOSampleRate rate = 0;
|
||||
if (ASIOGetChannels(&inputs, &outputs) != ASE_OK ||
|
||||
ASIOGetSampleRate(&rate) != ASE_OK ||
|
||||
ASIOGetBufferSize(&minimum, &maximum, &preferred, &granularity) != ASE_OK ||
|
||||
ASIOGetLatencies(&inputLatency, &outputLatency) != ASE_OK) {
|
||||
std::fprintf(stderr, "ASIO format/status query failed.\n"); return 1;
|
||||
}
|
||||
std::printf("%.32s | version %ld\nrate %.0f Hz | %ld inputs / %ld outputs\n"
|
||||
"buffer %ld..%ld frames, preferred %ld, granularity %ld\n"
|
||||
"latency input %ld / output %ld frames\n",
|
||||
info.name, info.driverVersion, rate, inputs, outputs,
|
||||
minimum, maximum, preferred, granularity, inputLatency, outputLatency);
|
||||
if (inputs < 0 || outputs < 0 || inputs > 1024 || outputs > 1024) return 1;
|
||||
for (long direction = 0; direction < 2; ++direction) {
|
||||
for (long i = 0; i < (direction ? outputs : inputs); ++i) {
|
||||
ASIOChannelInfo channel{};
|
||||
channel.channel = i;
|
||||
channel.isInput = direction ? ASIOFalse : ASIOTrue;
|
||||
if (ASIOGetChannelInfo(&channel) != ASE_OK) return 1;
|
||||
std::printf("%s %ld: %.32s | ASIO sample type %ld\n",
|
||||
direction ? "output" : "input", i, channel.name, channel.type);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
43
examples/steinberg/render.c
Normal file
43
examples/steinberg/render.c
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/* Render the SDK sine -> half-gain examples without an audio server. */
|
||||
#include "xtc/xtc_plugin.h"
|
||||
#include "xtc/xtc_audio.h"
|
||||
#include <stdio.h>
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
xtc_plugin_spec spec;
|
||||
xtc_plugin *synth = NULL, *gain = NULL;
|
||||
xtc_recorder *writer = NULL;
|
||||
xtc_result result = XTC_ERR_INVALID_ARGUMENT;
|
||||
size_t position;
|
||||
float dry[256], wet[256];
|
||||
if (argc != 3) {
|
||||
fprintf(stderr,"Usage: steinberg-render /absolute/xtc-sdk-examples.vst3 /absolute/new.wav\n");
|
||||
return 2;
|
||||
}
|
||||
result = xtc_plugin_probe_format(XTC_PLUGIN_VST3,argv[1],1,48000,&spec);
|
||||
if (result == XTC_OK) result = xtc_plugin_create(&spec,48000,256,&synth);
|
||||
if (result == XTC_OK) result = xtc_plugin_probe_format(XTC_PLUGIN_VST3,argv[1],0,48000,&spec);
|
||||
if (result == XTC_OK) result = xtc_plugin_create(&spec,48000,256,&gain);
|
||||
if (result == XTC_OK) result = xtc_recorder_create(argv[2],48000,512,&writer);
|
||||
for (position = 0; result == XTC_OK && position < 96000; position += 256) {
|
||||
size_t frames = 96000-position < 256 ? 96000-position : 256;
|
||||
xtc_midi_event event = {0};
|
||||
size_t count = 0;
|
||||
event.channel = 0; event.pitch = 69; event.velocity = 100;
|
||||
if (!position) { event.type = XTC_MIDI_NOTE_ON; count = 1; }
|
||||
if (position <= 48000 && position+frames > 48000) {
|
||||
event.type = XTC_MIDI_NOTE_OFF; event.offset = (unsigned int)(48000-position); count = 1;
|
||||
}
|
||||
result = xtc_plugin_process_midi_at(synth,&event,count,dry,frames,position);
|
||||
if (result == XTC_OK) result = xtc_plugin_process_at(gain,dry,wet,frames,position);
|
||||
if (result == XTC_OK) {
|
||||
xtc_recorder_push(writer,wet,frames);
|
||||
result = xtc_recorder_drain(writer);
|
||||
}
|
||||
}
|
||||
if (result == XTC_OK) result = xtc_recorder_finish(writer);
|
||||
xtc_recorder_destroy(writer); xtc_plugin_destroy(gain); xtc_plugin_destroy(synth);
|
||||
if (result != XTC_OK) { fprintf(stderr,"SDK example render failed (%d).\n",(int)result); return 1; }
|
||||
puts("Rendered 2 seconds at 48 kHz: A4 for 1 second, then silence.");
|
||||
return 0;
|
||||
}
|
||||
132
examples/steinberg/sdk_plugins.cpp
Normal file
132
examples/steinberg/sdk_plugins.cpp
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
// Original MIT-licensed xtc examples using an external official Steinberg SDK.
|
||||
// C++ is confined to this optional SDK example, never linked into libxtc.
|
||||
#include "public.sdk/source/vst/vstaudioeffect.h"
|
||||
#include "public.sdk/source/vst/vsteditcontroller.h"
|
||||
#include "public.sdk/source/main/pluginfactory.h"
|
||||
#include "pluginterfaces/base/ibstream.h"
|
||||
#include "pluginterfaces/vst/ivstevents.h"
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
namespace {
|
||||
using namespace Steinberg;
|
||||
using namespace Steinberg::Vst;
|
||||
|
||||
const FUID controllerID(0x58544343,0x4F4E5431,0x98561234,0xABCD0003);
|
||||
class Controller final : public EditController {
|
||||
public:
|
||||
static FUnknown* create(void*) { return static_cast<IEditController*>(new Controller); }
|
||||
tresult PLUGIN_API setComponentState(IBStream* stream) override { return stream ? kResultOk : kInvalidArgument; }
|
||||
};
|
||||
|
||||
class Gain final : public AudioEffect {
|
||||
public:
|
||||
Gain() { setControllerClass(controllerID); }
|
||||
static FUnknown* create(void*) { return static_cast<IAudioProcessor*>(new Gain); }
|
||||
tresult PLUGIN_API initialize(FUnknown* host) override {
|
||||
const auto result = AudioEffect::initialize(host);
|
||||
if (result != kResultOk) return result;
|
||||
addAudioInput(STR16("Input"), SpeakerArr::kMono);
|
||||
addAudioOutput(STR16("Output"), SpeakerArr::kMono);
|
||||
return kResultOk;
|
||||
}
|
||||
tresult PLUGIN_API setBusArrangements(SpeakerArrangement* in, int32 ni,
|
||||
SpeakerArrangement* out, int32 no) override {
|
||||
if (ni != 1 || no != 1 || !in || !out || in[0] != SpeakerArr::kMono || out[0] != SpeakerArr::kMono)
|
||||
return kResultFalse;
|
||||
return AudioEffect::setBusArrangements(in, ni, out, no);
|
||||
}
|
||||
tresult PLUGIN_API canProcessSampleSize(int32 size) override {
|
||||
return size == kSample32 ? kResultOk : kResultFalse;
|
||||
}
|
||||
tresult PLUGIN_API setProcessing(TBool) override { return kResultOk; }
|
||||
tresult PLUGIN_API process(ProcessData& data) override {
|
||||
if (data.symbolicSampleSize != kSample32 || data.numSamples < 0) return kInvalidArgument;
|
||||
if (!data.numSamples) return kResultOk;
|
||||
if (data.numInputs != 1 || data.numOutputs != 1 || !data.inputs || !data.outputs ||
|
||||
data.inputs[0].numChannels != 1 || data.outputs[0].numChannels != 1 ||
|
||||
!data.inputs[0].channelBuffers32 || !data.outputs[0].channelBuffers32 ||
|
||||
!data.inputs[0].channelBuffers32[0] || !data.outputs[0].channelBuffers32[0]) return kInvalidArgument;
|
||||
for (int32 i = 0; i < data.numSamples; ++i)
|
||||
data.outputs[0].channelBuffers32[0][i] = data.inputs[0].channelBuffers32[0][i] * .5f;
|
||||
data.outputs[0].silenceFlags = data.inputs[0].silenceFlags;
|
||||
return kResultOk;
|
||||
}
|
||||
// Stateless examples still implement the SDK's stream contract.
|
||||
tresult PLUGIN_API setState(IBStream* stream) override { return stream ? kResultOk : kInvalidArgument; }
|
||||
tresult PLUGIN_API getState(IBStream* stream) override { return stream ? kResultOk : kInvalidArgument; }
|
||||
};
|
||||
|
||||
class Sine final : public AudioEffect {
|
||||
struct Voice { double phase = 0; float velocity = 0; };
|
||||
std::array<std::array<Voice, 128>, 16> voices{};
|
||||
void event(const Event& e) {
|
||||
if (e.type == Event::kNoteOnEvent && e.noteOn.channel >= 0 && e.noteOn.channel < 16 &&
|
||||
e.noteOn.pitch >= 0 && e.noteOn.pitch < 128) {
|
||||
auto& v = voices[e.noteOn.channel][e.noteOn.pitch];
|
||||
v.phase = 0; v.velocity = e.noteOn.velocity;
|
||||
} else if (e.type == Event::kNoteOffEvent && e.noteOff.channel >= 0 && e.noteOff.channel < 16 &&
|
||||
e.noteOff.pitch >= 0 && e.noteOff.pitch < 128)
|
||||
voices[e.noteOff.channel][e.noteOff.pitch].velocity = 0;
|
||||
}
|
||||
public:
|
||||
Sine() { setControllerClass(controllerID); }
|
||||
static FUnknown* create(void*) { return static_cast<IAudioProcessor*>(new Sine); }
|
||||
tresult PLUGIN_API initialize(FUnknown* host) override {
|
||||
const auto result = AudioEffect::initialize(host);
|
||||
if (result != kResultOk) return result;
|
||||
addEventInput(STR16("Notes"), 16);
|
||||
addAudioOutput(STR16("Output"), SpeakerArr::kMono);
|
||||
return kResultOk;
|
||||
}
|
||||
tresult PLUGIN_API setBusArrangements(SpeakerArrangement*, int32 ni,
|
||||
SpeakerArrangement* out, int32 no) override {
|
||||
if (ni != 0 || no != 1 || !out || out[0] != SpeakerArr::kMono) return kResultFalse;
|
||||
return kResultOk;
|
||||
}
|
||||
tresult PLUGIN_API canProcessSampleSize(int32 size) override { return size == kSample32 ? kResultOk : kResultFalse; }
|
||||
tresult PLUGIN_API setProcessing(TBool) override { voices = {}; return kResultOk; }
|
||||
tresult PLUGIN_API process(ProcessData& data) override {
|
||||
if (data.symbolicSampleSize != kSample32 || data.numSamples < 0) return kInvalidArgument;
|
||||
if (!data.numSamples) return kResultOk;
|
||||
if (data.numOutputs != 1 || !data.outputs || data.outputs[0].numChannels != 1 ||
|
||||
!data.outputs[0].channelBuffers32 || !data.outputs[0].channelBuffers32[0]) return kInvalidArgument;
|
||||
int32 index = 0;
|
||||
const int32 count = data.inputEvents ? data.inputEvents->getEventCount() : 0;
|
||||
Event next{};
|
||||
bool pending = index < count && data.inputEvents->getEvent(index, next) == kResultOk;
|
||||
bool silent = true;
|
||||
for (int32 i = 0; i < data.numSamples; ++i) {
|
||||
while (pending && next.sampleOffset <= i) {
|
||||
event(next); ++index;
|
||||
pending = index < count && data.inputEvents->getEvent(index, next) == kResultOk;
|
||||
}
|
||||
double value = 0;
|
||||
for (auto& channel : voices) for (size_t key = 0; key < channel.size(); ++key) {
|
||||
auto& v = channel[key];
|
||||
if (v.velocity == 0) continue;
|
||||
value += std::sin(v.phase) * v.velocity * .1;
|
||||
v.phase += 6.283185307179586 * 440.0 * std::pow(2.0, (static_cast<double>(key)-69.0)/12.0) / processSetup.sampleRate;
|
||||
v.phase = std::fmod(v.phase, 6.283185307179586);
|
||||
}
|
||||
data.outputs[0].channelBuffers32[0][i] = static_cast<float>(std::tanh(value));
|
||||
if (value != 0) silent = false;
|
||||
}
|
||||
data.outputs[0].silenceFlags = silent ? 1 : 0;
|
||||
return kResultOk;
|
||||
}
|
||||
tresult PLUGIN_API setState(IBStream* stream) override { return stream ? kResultOk : kInvalidArgument; }
|
||||
tresult PLUGIN_API getState(IBStream* stream) override { return stream ? kResultOk : kInvalidArgument; }
|
||||
};
|
||||
}
|
||||
BEGIN_FACTORY("xtc", "", "", 0)
|
||||
DEF_CLASS2(INLINE_UID(0x58544347,0x41494E31,0x98561234,0xABCD0001),
|
||||
PClassInfo::kManyInstances, kVstAudioEffectClass, "xtc SDK Half Gain", 0,
|
||||
"Fx", "1.0.0", kVstVersionString, Gain::create)
|
||||
DEF_CLASS2(INLINE_UID(0x58544353,0x494E4531,0x98561234,0xABCD0002),
|
||||
PClassInfo::kManyInstances, kVstAudioEffectClass, "xtc SDK Sine", 0,
|
||||
"Instrument|Synth", "1.0.0", kVstVersionString, Sine::create)
|
||||
DEF_CLASS2(INLINE_UID(0x58544343,0x4F4E5431,0x98561234,0xABCD0003),
|
||||
PClassInfo::kManyInstances, kVstComponentControllerClass, "xtc SDK Controller", 0,
|
||||
"", "1.0.0", kVstVersionString, Controller::create)
|
||||
END_FACTORY
|
||||
|
|
@ -84,7 +84,7 @@ typedef struct xtc_plugin xtc_plugin;
|
|||
/* Serialized control/main thread: service CLAP callback requests. */
|
||||
void xtc_plugin_idle(void);
|
||||
/* Serialized main/control thread only (also create/destroy/idle). Probe explicitly
|
||||
* enumerates a Linux binary file by descriptor index;
|
||||
* enumerates a Linux binary or conventional .vst3 bundle by descriptor index;
|
||||
* NOT_FOUND means end, UNSUPPORTED means descriptor/layout unsupported. Loading
|
||||
* trusted libraries can execute arbitrary constructors. Never probe in audio. */
|
||||
xtc_result xtc_plugin_probe_format(xtc_plugin_format format, const char *path, unsigned int index, double rate, xtc_plugin_spec *out);
|
||||
|
|
@ -106,7 +106,8 @@ void xtc_plugin_destroy(xtc_plugin *plugin); /* control, after quiescence */
|
|||
* must obey realtime rules. No MIDI; max one mono input and one mono output.
|
||||
* Transport reset retains LADSPA delay history (no realtime reset contract). */
|
||||
/* Instruments: sorted bounded MIDI, mono output, single audio owner. */
|
||||
/* Absolute sample position supports instrument parameter automation and seek. */
|
||||
/* Absolute sample position supports instrument parameter automation and seek.
|
||||
* VST3 receives this clock plus the prepared spec tempo in its process context. */
|
||||
xtc_result xtc_plugin_process_midi_at(xtc_plugin *plugin, const xtc_midi_event *events, size_t count, float *output, size_t frames, uint64_t position);
|
||||
/* Control-thread preset files use a versioned single-instrument project container.
|
||||
* Load is transactional, validates metadata and never loads executable modules. */
|
||||
|
|
|
|||
15
src/main.c
15
src/main.c
|
|
@ -13,6 +13,7 @@
|
|||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include "xtc/xtc_plugin.h"
|
||||
#include "plugin/module_path.h"
|
||||
|
||||
/* Dependency inspection checks readable regular files, never plugin code or
|
||||
* audio contents. Escape untrusted path/label bytes in terminal output. */
|
||||
|
|
@ -32,6 +33,14 @@ static int readable_file(const char *path)
|
|||
struct stat info;
|
||||
return stat(path,&info) == 0 && S_ISREG(info.st_mode) && access(path,R_OK) == 0;
|
||||
}
|
||||
static int readable_plugin(const xtc_plugin_spec *spec)
|
||||
{
|
||||
char binary[512];
|
||||
if (spec->format == XTC_PLUGIN_BUILTIN) return 1;
|
||||
if (spec->format == XTC_PLUGIN_VST3)
|
||||
return xtc_plugin_module_path(spec->path,binary) == XTC_OK && readable_file(binary);
|
||||
return readable_file(spec->path);
|
||||
}
|
||||
static int inspect(const xtc_project *project, uint64_t version)
|
||||
{
|
||||
size_t tracks = 0, clips = 0, nodes = 0, edges = 0, i, missing = 0;
|
||||
|
|
@ -54,7 +63,7 @@ static int inspect(const xtc_project *project, uint64_t version)
|
|||
xtc_node_info node; xtc_plugin_spec spec;
|
||||
xtc_graph_get_node(graph,i,&node);
|
||||
if (xtc_graph_get_plugin(graph,node.id,&spec) == XTC_OK) {
|
||||
int available = spec.format == XTC_PLUGIN_BUILTIN || readable_file(spec.path);
|
||||
int available = readable_plugin(&spec);
|
||||
printf("%s node %zu ID %u descriptor %u ",xtc_plugin_format_name(spec.format),node.id,spec.unique_id,spec.index);
|
||||
if (spec.identifier[0]) { quoted(spec.identifier); putchar(' '); }
|
||||
quoted(spec.label); printf(": %s ",available ? "available" : "missing/unreadable");
|
||||
|
|
@ -66,7 +75,7 @@ static int inspect(const xtc_project *project, uint64_t version)
|
|||
xtc_track_info track; xtc_plugin_spec spec;
|
||||
xtc_project_get_track(project,i,&track);
|
||||
if (xtc_project_get_instrument(project,track.id,&spec) == XTC_OK) {
|
||||
int available = spec.format == XTC_PLUGIN_BUILTIN || readable_file(spec.path);
|
||||
int available = readable_plugin(&spec);
|
||||
printf("%s instrument track %" PRIu64 " descriptor %u ",xtc_plugin_format_name(spec.format),track.id,spec.index);
|
||||
quoted(spec.identifier); putchar(' '); quoted(spec.label);
|
||||
printf(": %s ",available ? "available" : "missing/unreadable"); quoted(spec.path); putchar('\n');
|
||||
|
|
@ -85,7 +94,7 @@ static int inspect(const xtc_project *project, uint64_t version)
|
|||
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);
|
||||
int available=readable_plugin(spec);
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "modern.h"
|
||||
#include "module_path.h"
|
||||
#include "builtin.h"
|
||||
#include <dlfcn.h>
|
||||
#include <math.h>
|
||||
|
|
@ -11,7 +12,7 @@ xtc_result xtc_ladspa_describe(const xtc_plugin_spec *, double, xtc_plugin_detai
|
|||
xtc_result xtc_ladspa_create(const xtc_plugin_spec *, double, size_t, xtc_ladspa_plugin **);
|
||||
void xtc_ladspa_destroy(xtc_ladspa_plugin *);
|
||||
xtc_result xtc_ladspa_process_at(xtc_ladspa_plugin *, const float *, float *, size_t, uint64_t);
|
||||
struct xtc_plugin { xtc_ladspa_plugin *ladspa; xtc_modern *modern; xtc_builtin *builtin; uint64_t next; };
|
||||
struct xtc_plugin { xtc_ladspa_plugin *ladspa; xtc_modern *modern; xtc_builtin *builtin; uint64_t next; unsigned int tempo; };
|
||||
const char *xtc_plugin_format_name(xtc_plugin_format f)
|
||||
{ static const char *names[] = {"LADSPA", "CLAP", "VST2", "VST3", "Internal"}; return f >= 0 && f <= XTC_PLUGIN_BUILTIN ? names[f] : "Unknown"; }
|
||||
xtc_result xtc_plugin_probe_format(xtc_plugin_format f, const char *path, unsigned int index, double rate, xtc_plugin_spec *out)
|
||||
|
|
@ -28,9 +29,12 @@ xtc_result xtc_plugin_probe_format(xtc_plugin_format f, const char *path, unsign
|
|||
xtc_result xtc_plugin_probe(const char *path, unsigned int index, double rate, xtc_plugin_spec *out)
|
||||
{
|
||||
void *module; xtc_plugin_format f = XTC_PLUGIN_LADSPA;
|
||||
char binary[512]; xtc_result r;
|
||||
if (!path || path[0] != '/' || strlen(path) >= 512) return XTC_ERR_INVALID_ARGUMENT;
|
||||
if (!strcmp(path,XTC_BUILTIN_PATH)) return xtc_plugin_probe_format(XTC_PLUGIN_BUILTIN,path,index,rate,out);
|
||||
module = dlopen(path,RTLD_NOW | RTLD_LOCAL);
|
||||
r = xtc_plugin_module_path(path,binary);
|
||||
if (r != XTC_OK) return r;
|
||||
module = dlopen(binary,RTLD_NOW | RTLD_LOCAL);
|
||||
if (!module) return XTC_ERR_NOT_FOUND;
|
||||
if (dlsym(module,"clap_entry")) f = XTC_PLUGIN_CLAP;
|
||||
else if (dlsym(module,"VSTPluginMain")) f = XTC_PLUGIN_VST2;
|
||||
|
|
@ -74,7 +78,7 @@ xtc_result xtc_plugin_create(const xtc_plugin_spec *s, double rate, size_t frame
|
|||
strcmp(actual.identifier,s->identifier) || strcmp(actual.label,s->label))) r = XTC_ERR_UNSUPPORTED;
|
||||
}
|
||||
if (r != XTC_OK) { xtc_plugin_destroy(p); return r; }
|
||||
*out = p; return XTC_OK;
|
||||
p->tempo = s->tempo; *out = p; return XTC_OK;
|
||||
}
|
||||
xtc_result xtc_plugin_process_stereo_at(xtc_plugin *p,const float *left,const float *right,float *out,float *out_right,size_t frames,uint64_t position)
|
||||
{
|
||||
|
|
@ -87,7 +91,7 @@ xtc_result xtc_plugin_process_at(xtc_plugin *p, const float *in, float *out, siz
|
|||
{
|
||||
xtc_result r;
|
||||
if (!p) return XTC_ERR_INVALID_ARGUMENT;
|
||||
r = p->builtin ? xtc_builtin_process(p->builtin,in,out,frames,position) : p->modern ? xtc_modern_process(p->modern,in,out,frames) : xtc_ladspa_process_at(p->ladspa,in,out,frames,position);
|
||||
r = p->builtin ? xtc_builtin_process(p->builtin,in,out,frames,position) : p->modern ? xtc_modern_process(p->modern,in,out,frames,position,p->tempo) : xtc_ladspa_process_at(p->ladspa,in,out,frames,position);
|
||||
if (r == XTC_OK) p->next = position <= UINT64_MAX-frames ? position+frames : 0;
|
||||
return r;
|
||||
}
|
||||
|
|
@ -95,7 +99,7 @@ xtc_result xtc_plugin_process_midi_at(xtc_plugin *p, const xtc_midi_event *event
|
|||
{
|
||||
xtc_result r;
|
||||
if (!p || position>UINT64_MAX-frames) return XTC_ERR_INVALID_ARGUMENT;
|
||||
r=p->builtin ? xtc_builtin_midi(p->builtin,events,count,out,frames,position) : p->modern ? xtc_modern_process_midi(p->modern,events,count,out,frames) : XTC_ERR_UNSUPPORTED;
|
||||
r=p->builtin ? xtc_builtin_midi(p->builtin,events,count,out,frames,position) : p->modern ? xtc_modern_process_midi(p->modern,events,count,out,frames,position,p->tempo) : XTC_ERR_UNSUPPORTED;
|
||||
if(r==XTC_OK)p->next=position+frames;
|
||||
return r;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "modern.h"
|
||||
#include "module_path.h"
|
||||
#include "vst2_abi.h"
|
||||
#include "xtc/xtc_dsp.h"
|
||||
#include "xtc/xtc_version.h"
|
||||
|
|
@ -86,8 +87,11 @@ static void module_release(module *m)
|
|||
}
|
||||
static xtc_result module_open(xtc_plugin_format format, const char *path, module **out)
|
||||
{
|
||||
void *handle = dlopen(path,RTLD_NOW | RTLD_LOCAL);
|
||||
module *m; xtc_result r = XTC_ERR_UNSUPPORTED;
|
||||
char binary[512]; void *handle;
|
||||
module *m; xtc_result r = xtc_plugin_module_path(path,binary);
|
||||
if (r != XTC_OK) return r;
|
||||
handle = dlopen(binary,RTLD_NOW | RTLD_LOCAL);
|
||||
r = XTC_ERR_UNSUPPORTED;
|
||||
if (!handle) return XTC_ERR_NOT_FOUND;
|
||||
for (m = modules; m; m = m->next) if (m->handle == handle && m->format == format) {
|
||||
++m->references; dlclose(handle); *out = m; return XTC_OK;
|
||||
|
|
@ -367,10 +371,11 @@ void xtc_modern_destroy(xtc_modern *p)
|
|||
if (p->factory && p->factory->lpVtbl && p->factory->lpVtbl->release) p->factory->lpVtbl->release(p->factory);
|
||||
module_release(p->module); free(p->input); free(p->output); free(p);
|
||||
}
|
||||
xtc_result xtc_modern_process(xtc_modern *p, const float *input, float *output, size_t frames)
|
||||
xtc_result xtc_modern_process(xtc_modern *p, const float *input, float *output, size_t frames, uint64_t position, unsigned int tempo)
|
||||
{
|
||||
size_t i; int ok = 1;
|
||||
if (!p || !output || frames > p->frames) return XTC_ERR_INVALID_ARGUMENT;
|
||||
if (!p || !output || frames > p->frames || position > INT64_MAX ||
|
||||
tempo < 20000 || tempo > 300000) return XTC_ERR_INVALID_ARGUMENT;
|
||||
if (!frames) return XTC_OK;
|
||||
if (__atomic_load_n(&p->restart,__ATOMIC_ACQUIRE)) { memset(output,0,frames*sizeof(float)); return XTC_ERR_INVALID_STATE; }
|
||||
for (i = 0; i < frames; ++i) p->input[i] = input ? xtc_dsp_finite(input[i]) : 0;
|
||||
|
|
@ -396,6 +401,15 @@ xtc_result xtc_modern_process(xtc_modern *p, const float *input, float *output,
|
|||
else {
|
||||
struct Steinberg_Vst_AudioBusBuffers in = {0}, out = {0};
|
||||
struct Steinberg_Vst_ProcessData data = {0};
|
||||
struct Steinberg_Vst_ProcessContext context = {0};
|
||||
context.state = Steinberg_Vst_ProcessContext_StatesAndFlags_kPlaying |
|
||||
Steinberg_Vst_ProcessContext_StatesAndFlags_kTempoValid |
|
||||
Steinberg_Vst_ProcessContext_StatesAndFlags_kProjectTimeMusicValid;
|
||||
context.sampleRate = p->rate;
|
||||
context.projectTimeSamples = (Steinberg_Vst_TSamples)position;
|
||||
context.tempo = (double)tempo / 1000.0;
|
||||
context.projectTimeMusic = (double)position / p->rate * context.tempo / 60.0;
|
||||
data.processContext = &context;
|
||||
in.numChannels = out.numChannels = 1;
|
||||
in.Steinberg_Vst_AudioBusBuffers_channelBuffers32 = &p->input;
|
||||
out.Steinberg_Vst_AudioBusBuffers_channelBuffers32 = &p->output;
|
||||
|
|
@ -443,10 +457,11 @@ static void vst_note(xtc_modern *p, unsigned int offset, unsigned int channel, u
|
|||
}
|
||||
p->keys[channel][pitch] = (unsigned char)on;
|
||||
}
|
||||
xtc_result xtc_modern_process_midi(xtc_modern *p, const xtc_midi_event *events, size_t count, float *output, size_t frames)
|
||||
xtc_result xtc_modern_process_midi(xtc_modern *p, const xtc_midi_event *events, size_t count, float *output, size_t frames, uint64_t position, unsigned int tempo)
|
||||
{
|
||||
size_t i; xtc_result r;
|
||||
if (!p || !p->instrument || !output || (count && !events) || count > XTC_MIDI_MAX_EVENTS || frames > p->frames) return XTC_ERR_INVALID_ARGUMENT;
|
||||
if (!p || !p->instrument || !output || (count && !events) || count > XTC_MIDI_MAX_EVENTS || frames > p->frames ||
|
||||
position > INT64_MAX || tempo < 20000 || tempo > 300000) return XTC_ERR_INVALID_ARGUMENT;
|
||||
for (i = 0; i < count; ++i) {
|
||||
const xtc_midi_event *e = events+i;
|
||||
if (e->offset >= frames || (i && e->offset < events[i-1].offset) || e->channel > 15 || e->pitch > 127 || e->velocity > 127 ||
|
||||
|
|
@ -482,6 +497,6 @@ xtc_result xtc_modern_process_midi(xtc_modern *p, const xtc_midi_event *events,
|
|||
for (k = 0; k < 128; ++k) if (p->keys[e->channel][k]) vst_note(p,e->offset,e->channel,k,0,0);
|
||||
}
|
||||
}
|
||||
r = xtc_modern_process(p,NULL,output,frames);
|
||||
r = xtc_modern_process(p,NULL,output,frames,position,tempo);
|
||||
p->event_count = 0; p->clap_count = 0; p->vst_count = 0; return r;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
#include "xtc/xtc_plugin.h"
|
||||
typedef struct xtc_modern xtc_modern;
|
||||
xtc_result xtc_modern_open(xtc_plugin_format format, const char *path, unsigned int index, double rate, size_t frames, xtc_modern **out, xtc_plugin_spec *spec);
|
||||
xtc_result xtc_modern_process(xtc_modern *plugin, const float *input, float *output, size_t frames);
|
||||
xtc_result xtc_modern_process_midi(xtc_modern *plugin, const xtc_midi_event *events, size_t count, float *output, size_t frames);
|
||||
xtc_result xtc_modern_process(xtc_modern *plugin, const float *input, float *output, size_t frames, uint64_t position, unsigned int tempo);
|
||||
xtc_result xtc_modern_process_midi(xtc_modern *plugin, const xtc_midi_event *events, size_t count, float *output, size_t frames, uint64_t position, unsigned int tempo);
|
||||
void xtc_modern_destroy(xtc_modern *plugin);
|
||||
#endif
|
||||
|
|
|
|||
40
src/plugin/module_path.c
Normal file
40
src/plugin/module_path.c
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#include "module_path.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
xtc_result xtc_plugin_module_path(const char *path, char out[512])
|
||||
{
|
||||
struct stat status;
|
||||
char resolved[512];
|
||||
const char *name;
|
||||
size_t length, stem;
|
||||
int count;
|
||||
if (!path || !out || path[0] != '/' || (length = strlen(path)) >= sizeof(resolved))
|
||||
return XTC_ERR_INVALID_ARGUMENT;
|
||||
if (stat(path, &status)) return XTC_ERR_NOT_FOUND;
|
||||
if (S_ISREG(status.st_mode)) { memcpy(out, path, length + 1); return XTC_OK; }
|
||||
if (!S_ISDIR(status.st_mode)) return XTC_ERR_UNSUPPORTED;
|
||||
name = strrchr(path, '/') + 1;
|
||||
stem = strlen(name);
|
||||
if (stem <= 5 || strcmp(name + stem - 5, ".vst3")) return XTC_ERR_UNSUPPORTED;
|
||||
#if defined(__x86_64__)
|
||||
#define XTC_VST3_ARCH "x86_64-linux"
|
||||
#elif defined(__aarch64__)
|
||||
#define XTC_VST3_ARCH "aarch64-linux"
|
||||
#elif defined(__i386__)
|
||||
#define XTC_VST3_ARCH "i386-linux"
|
||||
#elif defined(__arm__)
|
||||
#define XTC_VST3_ARCH "arm-linux"
|
||||
#else
|
||||
return XTC_ERR_UNSUPPORTED;
|
||||
#define XTC_VST3_ARCH "unsupported"
|
||||
#endif
|
||||
count = snprintf(resolved, sizeof(resolved), "%s/Contents/" XTC_VST3_ARCH "/%.*s.so",
|
||||
path, (int)(stem - 5), name);
|
||||
if (count < 0 || (size_t)count >= sizeof(resolved)) return XTC_ERR_INVALID_ARGUMENT;
|
||||
if (stat(resolved, &status)) return XTC_ERR_NOT_FOUND;
|
||||
if (!S_ISREG(status.st_mode)) return XTC_ERR_UNSUPPORTED;
|
||||
memcpy(out, resolved, (size_t)count + 1);
|
||||
return XTC_OK;
|
||||
}
|
||||
6
src/plugin/module_path.h
Normal file
6
src/plugin/module_path.h
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#ifndef XTC_PLUGIN_MODULE_PATH_H
|
||||
#define XTC_PLUGIN_MODULE_PATH_H
|
||||
#include "xtc/xtc.h"
|
||||
/* Control-thread Linux binary/bundle resolution. Preserves out on error. */
|
||||
xtc_result xtc_plugin_module_path(const char *path, char out[512]);
|
||||
#endif
|
||||
|
|
@ -87,10 +87,36 @@ static void check(const char *path, xtc_plugin_format format)
|
|||
xtc_realtime_destroy(rt); xtc_graph_destroy(graph); xtc_project_destroy(p); xtc_project_destroy(loaded); unlink(document);
|
||||
printf("%s identity, buffers, stereo graph, retirement, reload and missing dependency passed.\n",xtc_plugin_format_name(format));
|
||||
}
|
||||
static void context_check(const char *path)
|
||||
{
|
||||
xtc_plugin_spec spec; xtc_plugin *plugin = NULL;
|
||||
void *handle, *symbol;
|
||||
void (*read_context)(double *, int64_t *, double *, uint32_t *) = NULL;
|
||||
float data[31] = {0}; double tempo, music; int64_t position; uint32_t flags;
|
||||
assert(xtc_plugin_probe_format(XTC_PLUGIN_VST3,path,0,48000,&spec) == XTC_OK);
|
||||
spec.tempo = 137000;
|
||||
assert(xtc_plugin_create(&spec,48000,31,&plugin) == XTC_OK);
|
||||
handle = dlopen(path,RTLD_NOW | RTLD_LOCAL); assert(handle);
|
||||
symbol = dlsym(handle,"xtc_fixture_context"); assert(symbol && sizeof(symbol) == sizeof(read_context));
|
||||
memcpy(&read_context,&symbol,sizeof(symbol));
|
||||
audio = 1; assert(xtc_plugin_process_at(plugin,data,data,31,48000) == XTC_OK); audio = 0;
|
||||
read_context(&tempo,&position,&music,&flags);
|
||||
assert(tempo == 137 && position == 48000 && music == 137.0/60.0);
|
||||
assert(flags == ((1U << 1) | (1U << 9) | (1U << 10)));
|
||||
assert(xtc_plugin_process_at(plugin,data,data,31,0) == XTC_OK);
|
||||
read_context(&tempo,&position,&music,&flags); assert(position == 0 && music == 0);
|
||||
assert(xtc_plugin_process(plugin,data,data,31) == XTC_OK);
|
||||
read_context(&tempo,&position,&music,&flags); assert(position == 31);
|
||||
assert(xtc_plugin_process_at(plugin,data,data,31,UINT64_MAX) == XTC_ERR_INVALID_ARGUMENT);
|
||||
read_context(&tempo,&position,&music,&flags); assert(position == 31);
|
||||
xtc_plugin_destroy(plugin); assert(dlclose(handle) == 0);
|
||||
puts("VST3 tempo, signed sample clock, seek and sequential context passed.");
|
||||
}
|
||||
int main(int argc,char **argv)
|
||||
{
|
||||
unsigned int i; xtc_plugin_spec spec; xtc_plugin *p = NULL; float data[1] = {1};
|
||||
assert(argc == 4);
|
||||
context_check(argv[3]);
|
||||
check(argv[1],XTC_PLUGIN_CLAP); check(argv[2],XTC_PLUGIN_VST2); check(argv[3],XTC_PLUGIN_VST3);
|
||||
for (i = 1; i < 4; ++i) assert(xtc_plugin_probe_format(XTC_PLUGIN_CLAP,argv[1],i,48000,&spec) != XTC_OK);
|
||||
assert(xtc_plugin_probe_format(XTC_PLUGIN_CLAP,argv[1],4,48000,&spec) == XTC_OK);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from pathlib import Path
|
|||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import platform
|
||||
|
||||
binary = str(Path(sys.argv[1]).resolve())
|
||||
|
||||
|
|
@ -103,6 +104,18 @@ with tempfile.TemporaryDirectory(prefix="xtc-inspect-") as directory:
|
|||
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")
|
||||
# A conventional VST3 bundle is inspected without executing its fake binary.
|
||||
architecture = {"x86_64": "x86_64", "aarch64": "aarch64",
|
||||
"i386": "i386", "i686": "i386", "armv7l": "arm"}.get(platform.machine())
|
||||
if architecture:
|
||||
bundle = root / "Example.vst3"
|
||||
inner = bundle / "Contents" / (architecture + "-linux") / "Example.so"
|
||||
inner.parent.mkdir(parents=True)
|
||||
bundled = document.replace(str(module).encode().hex(), str(bundle).encode().hex())
|
||||
project.write_text(bundled)
|
||||
check("--inspect", project, 2, "VST3 insert track 0", "missing/unreadable: 1")
|
||||
inner.write_text("Not a module: metadata inspection must not execute it")
|
||||
check("--inspect", project, 0, "VST3 insert track 0", "no plugin code executed", "missing/unreadable: 0")
|
||||
for text, messages in [
|
||||
(base.replace("XTC_PROJECT 10", "XTC_PROJECT 999"), ("line 1", "999", "1..17")),
|
||||
("XTC_PROJECT 10\n", ("line 2", "APP")),
|
||||
|
|
|
|||
59
tests/integration/test_steinberg_sdk.c
Normal file
59
tests/integration/test_steinberg_sdk.c
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "xtc/xtc_plugin.h"
|
||||
#include "xtc/xtc_project.h"
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
xtc_plugin_spec gain, synth, actual;
|
||||
xtc_plugin *g = NULL, *s = NULL;
|
||||
float in[256], out[256], wet[256];
|
||||
size_t i;
|
||||
xtc_project *project = NULL, *loaded = NULL;
|
||||
uint64_t track;
|
||||
char document[] = "/tmp/xtc-sdk-project-XXXXXX";
|
||||
int fd;
|
||||
xtc_midi_event events[] = {
|
||||
{0}, {0}
|
||||
};
|
||||
assert(argc == 2 && argv[1][0] == '/');
|
||||
assert(xtc_plugin_probe(argv[1],0,48000,&gain) == XTC_OK);
|
||||
assert(gain.format == XTC_PLUGIN_VST3 && gain.inputs == 1 && !gain.instrument);
|
||||
assert(!strcmp(gain.path,argv[1]) && !strcmp(gain.label,"xtc SDK Half Gain"));
|
||||
assert(xtc_plugin_probe_format(XTC_PLUGIN_VST3,argv[1],1,48000,&synth) == XTC_OK);
|
||||
assert(synth.instrument && !synth.inputs && strcmp(synth.identifier,gain.identifier));
|
||||
assert(xtc_plugin_probe(argv[1],2,48000,&actual) == XTC_ERR_UNSUPPORTED); /* Controller, not audio. */
|
||||
assert(xtc_plugin_probe(argv[1],3,48000,&actual) == XTC_ERR_NOT_FOUND);
|
||||
assert(xtc_project_create(&project) == XTC_OK && xtc_project_create(&loaded) == XTC_OK);
|
||||
assert(xtc_project_add_track(project,"SDK Sine",&track) == XTC_OK);
|
||||
assert(xtc_project_set_instrument(project,track,&synth) == XTC_OK);
|
||||
fd = mkstemp(document); assert(fd >= 0 && close(fd) == 0);
|
||||
assert(xtc_project_save(project,document) == XTC_OK && xtc_project_load(loaded,document) == XTC_OK);
|
||||
assert(xtc_project_get_instrument(loaded,track,&actual) == XTC_OK);
|
||||
assert(!strcmp(actual.path,argv[1]) && !strcmp(actual.identifier,synth.identifier));
|
||||
assert(unlink(document) == 0); xtc_project_destroy(project); xtc_project_destroy(loaded);
|
||||
assert(xtc_plugin_create(&gain,48000,256,&g) == XTC_OK);
|
||||
assert(xtc_plugin_create(&synth,48000,256,&s) == XTC_OK);
|
||||
for (i = 0; i < 256; ++i) in[i] = i % 2 ? -.75f : .25f;
|
||||
assert(xtc_plugin_process_at(g,in,out,256,48000) == XTC_OK);
|
||||
for (i = 0; i < 256; ++i) assert(out[i] == in[i]*.5f);
|
||||
events[0].type = XTC_MIDI_NOTE_ON; events[0].offset = 13;
|
||||
events[0].channel = 3; events[0].pitch = 69; events[0].velocity = 127;
|
||||
events[1] = events[0]; events[1].type = XTC_MIDI_NOTE_OFF; events[1].offset = 200;
|
||||
assert(xtc_plugin_process_midi_at(s,events,2,out,256,0) == XTC_OK);
|
||||
for (i = 0; i <= 13; ++i) assert(out[i] == 0);
|
||||
assert(fabsf(out[40]) > .05f);
|
||||
for (i = 200; i < 256; ++i) assert(out[i] == 0);
|
||||
assert(xtc_plugin_process(g,out,wet,256) == XTC_OK);
|
||||
for (i = 0; i < 256; ++i) assert(wet[i] == out[i]*.5f && isfinite(wet[i]));
|
||||
assert(xtc_plugin_process_midi(s,NULL,0,out,256) == XTC_OK);
|
||||
for (i = 0; i < 256; ++i) assert(out[i] == 0);
|
||||
xtc_plugin_destroy(g); xtc_plugin_destroy(s);
|
||||
puts("Official SDK gain/synth: bundle discovery, identities, processing, note offsets and release passed.");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -5,6 +5,13 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
static int loaded, instances;
|
||||
static struct Steinberg_Vst_ProcessContext last_context;
|
||||
void xtc_fixture_context(double *tempo, int64_t *position, double *music, uint32_t *flags);
|
||||
void xtc_fixture_context(double *tempo, int64_t *position, double *music, uint32_t *flags)
|
||||
{
|
||||
*tempo=last_context.tempo; *position=last_context.projectTimeSamples;
|
||||
*music=last_context.projectTimeMusic; *flags=last_context.state;
|
||||
}
|
||||
static const Steinberg_TUID cid = SMTG_INLINE_UID(0x58544333,0x12345678,0x12345678,0x12345678);
|
||||
typedef struct { Steinberg_Vst_IComponent component; Steinberg_Vst_IAudioProcessor processor; unsigned int refs; int initialized, active, started; } instance;
|
||||
static instance *from_processor(void *p) { return (instance *)((char *)p - offsetof(instance,processor)); }
|
||||
|
|
@ -48,6 +55,8 @@ static Steinberg_tresult process(void *self, struct Steinberg_Vst_ProcessData *d
|
|||
{
|
||||
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(data->processContext && data->processContext->sampleRate > 0);
|
||||
last_context = *data->processContext;
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ static Steinberg_tresult process(void *self, struct Steinberg_Vst_ProcessData *d
|
|||
Steinberg_Vst_IEventList *events = data->inputEvents;
|
||||
int32_t count = events->lpVtbl->getEventCount(events);
|
||||
assert(p->started && data->numInputs == 0);
|
||||
assert(data->processContext && data->processContext->sampleRate > 0 && data->processContext->tempo > 0);
|
||||
for (i = 0; i < data->numSamples; ++i) {
|
||||
while (j < count) {
|
||||
struct Steinberg_Vst_Event event;
|
||||
|
|
|
|||
57
tests/unit/test_plugin_module_path.c
Normal file
57
tests/unit/test_plugin_module_path.c
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "../../src/plugin/module_path.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
char root[] = "/tmp/xtc-module-path-XXXXXX", out[512] = "preserved", path[512];
|
||||
char bundle[512], contents[512], binary[512];
|
||||
FILE *file;
|
||||
assert(mkdtemp(root));
|
||||
assert(xtc_plugin_module_path(NULL,out) == XTC_ERR_INVALID_ARGUMENT);
|
||||
assert(xtc_plugin_module_path("relative.vst3",out) == XTC_ERR_INVALID_ARGUMENT);
|
||||
assert(xtc_plugin_module_path(root,out) == XTC_ERR_UNSUPPORTED);
|
||||
assert(!strcmp(out,"preserved"));
|
||||
snprintf(bundle,sizeof(bundle),"%s/Example.vst3",root);
|
||||
assert(mkdir(bundle,0700) == 0);
|
||||
#if defined(__x86_64__) || defined(__aarch64__) || defined(__i386__) || defined(__arm__)
|
||||
assert(xtc_plugin_module_path(bundle,out) == XTC_ERR_NOT_FOUND);
|
||||
#else
|
||||
assert(xtc_plugin_module_path(bundle,out) == XTC_ERR_UNSUPPORTED);
|
||||
assert(rmdir(bundle) == 0 && rmdir(root) == 0);
|
||||
return 0;
|
||||
#endif
|
||||
assert(!strcmp(out,"preserved"));
|
||||
snprintf(contents,sizeof(contents),"%s/Example.vst3/Contents",root);
|
||||
assert(mkdir(contents,0700) == 0);
|
||||
#if defined(__x86_64__)
|
||||
#define ARCH "x86_64-linux"
|
||||
#elif defined(__aarch64__)
|
||||
#define ARCH "aarch64-linux"
|
||||
#elif defined(__i386__)
|
||||
#define ARCH "i386-linux"
|
||||
#elif defined(__arm__)
|
||||
#define ARCH "arm-linux"
|
||||
#else
|
||||
#define ARCH "unsupported"
|
||||
#endif
|
||||
snprintf(path,sizeof(path),"%s/Example.vst3/Contents/" ARCH,root);
|
||||
assert(mkdir(path,0700) == 0);
|
||||
snprintf(binary,sizeof(binary),"%s/Example.vst3/Contents/" ARCH "/Example.so",root);
|
||||
file = fopen(binary,"wb"); assert(file); assert(fclose(file) == 0);
|
||||
assert(xtc_plugin_module_path(bundle,out) == XTC_OK && !strcmp(out,binary));
|
||||
assert(xtc_plugin_module_path(binary,out) == XTC_OK && !strcmp(out,binary));
|
||||
assert(unlink(binary) == 0);
|
||||
assert(mkfifo(binary,0600) == 0);
|
||||
strcpy(out,"preserved");
|
||||
assert(xtc_plugin_module_path(bundle,out) == XTC_ERR_UNSUPPORTED && !strcmp(out,"preserved"));
|
||||
assert(unlink(binary) == 0);
|
||||
assert(rmdir(path) == 0 && rmdir(contents) == 0 && rmdir(bundle) == 0 && rmdir(root) == 0);
|
||||
puts("Plugin binary/bundle resolution and transactional errors passed.");
|
||||
return 0;
|
||||
}
|
||||
5
third_party/README.md
vendored
5
third_party/README.md
vendored
|
|
@ -18,3 +18,8 @@ Steinberg VST2 SDK, implementation or VST2-wrapping SDK is copied or distributed
|
|||
Legacy VST2 support does not grant SDK/trademark/plugin distribution rights. There
|
||||
is no VST2 plugin-building target for third-party SDKs. The original test fixture
|
||||
implements only the ABI subset needed to test this host.
|
||||
|
||||
Optional full-SDK examples in `examples/steinberg` compile from explicitly
|
||||
supplied external VST3/ASIO SDK roots. Normal builds do not fetch or link them;
|
||||
no external SDK source or ASIO runtime is included in source/binary packages.
|
||||
See [SDK integration and licenses](../examples/steinberg/README.md).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue