AUDIO
Source snapshot · 12 October 2026

Reference VST3 modules for hosted-VST3 tests

RAVET builds two small VST3 modules whose only job is to let the hosted-VST3 stack (vst3-hosting.md) run against real loaded modules. They cover two paths that otherwise have only mocks and hand-written fixtures: auxiliary buses, and the full rich-event set through the Rust Graph bank. Expected output follows from closed forms, so tests compare against independent references rather than recorded output. They are validation references, not production dynamics processors or instruments. See ADR 0274.

Module Bundle Processing
RAVET Keyed Sidechain ravet_sidechain_vst3.vst3 (org.ravet.sidechain.vst3) Stereo main in/out plus one auxiliary sidechain input, mono or stereo. Output is main * key. key is the mono key sample, the stereo mean (left + right) * 0.5, or exactly 1 when the auxiliary bus is inactive. Input channels flagged in silenceFlags are read as exact zeros, whatever their buffers hold. Output silenceFlags are always cleared. Other auxiliary arrangements are refused.
RAVET Event Probe ravet_event_probe_vst3.vst3 (org.ravet.event-probe.vst3) Stereo instrument output, one 16-channel event input and one event output. Each admitted bus-0 input event of the nine SDK input kinds (note on/off, data, poly pressure, note expression value, note expression text, chord, scale, note expression int), and each point of the Marker parameter, adds an impulse at its exact sample offset. The impulse is code + 1 on the left channel and hash20 * 2^-20 on the right. Each admitted input event is echoed unchanged to the event output. When at least one event was echoed, one legacy MIDI CC output follows (controller 16, channel 0, at the latest echoed offset). It carries the number of echoed events in 14 bits across value/value2; Marker points are not counted. The read-only Digest parameter reports a running 20-bit digest over every record, including zero-frame blocks, at the last frame of every block.

hash20 is a folded 20-bit FNV-1a hash of a canonical serialization of the record. It covers bus, flags, PPQ position and every payload field, including data bytes and UTF-16 text. It excludes the sample offset, which the impulse position encodes instead. The serialization is documented in the ravet_vst3_reference_native::record_hash Rustdoc and in examples/vst3-reference-native/include/RavetVst3Reference.h. Rendering is exact in f64 for every admitted block. In f32 it is exact while at most 16 records share one sample offset; the tests stay within that bound.

Records that share an offset are summed. The left-channel sum alone therefore does not identify them: a note-on plus a note-off (1 + 2) reads like one data event (3). A decoder must either know the candidate records, as the tests do, or place records at distinct offsets.

Topics in this guide

Admission and refusal

The Event Probe admits, per block, at most 2048 input events and at most 4096 - events Marker points. Event payloads share 65536 + 4096 + 16 bytes of module storage: data bytes and UTF-16 text plus a terminator, each 2-aligned. Events on buses other than 0, and unknown or output-only event kinds, are ignored before their offset is examined. Parameter queues other than Marker are ignored unread.

Anything else malformed refuses the whole block. That covers:

A refused block returns kResultFalse, zeroes every reachable output channel, posts no events or parameter points, and leaves the digest unchanged.

One exception is deliberate: the digest advances in the render, before output events and the Digest point are posted. If the host's output lists refuse addEvent or addPoint, the block returns kResultFalse with the digest already advanced, and the events posted so far stay in place.

The Keyed Sidechain refuses the same malformed-buffer cases, a missing key bus when the auxiliary bus is active, and a key channel count that differs from the arrangement. It also refuses setActive(true) before initialize.

Layout

Build and test

The SDK stays external; nothing is installed, registered or linked into a plug-in folder. Bundles are ad-hoc signed in place inside the build tree. The native tests read the in-module Rust allocation counter, so build the archive with allocation-probe:

cargo build --release -p ravet-vst3-reference-native --features allocation-probe
cmake -S examples/vst3-reference -B target/vst3-reference \
  -DCMAKE_BUILD_TYPE=Release -DRAVET_VST3_SDK=/path/to/vst3sdk \
  -DRAVET_VST3_REFERENCE_LIBRARY=$PWD/target/release/libravet_vst3_reference_native.a
cmake --build target/vst3-reference --parallel 2
ctest --test-dir target/vst3-reference --output-on-failure

cargo build --release -p ravet-host --features vst3-hosted-audio \
  --example vst3-reference-bank
M=target/vst3-reference/VST3/Release
cmake -S crates/ravet-host/native/vst3 -B target/vst3-host -DBUILD_TESTING=ON \
  -DCMAKE_BUILD_TYPE=Release -DRAVET_VST3_SDK=/path/to/vst3sdk \
  "-DRAVET_VST3_REFERENCE_MODULES=$PWD/$M/ravet_sidechain_vst3.vst3;$PWD/$M/ravet_event_probe_vst3.vst3" \
  -DRAVET_VST3_REFERENCE_BANK_LIBRARY=$PWD/target/release/examples/libvst3_reference_bank.a
cmake --build target/vst3-host --parallel 2
ctest --test-dir target/vst3-host -R reference --output-on-failure

The reference project forces SMTG_CREATE_PLUGIN_LINK off and SMTG_PLUGIN_TARGET_USER_PATH empty, and fails configuration otherwise. For each bundle it registers three tests:

Without RAVET_VST3_REFERENCE_MODULES, the native host project still compiles the reference test executables, registers no tests for them, and prints a configure-time status message. Without RAVET_VST3_REFERENCE_BANK_LIBRARY it skips the Rust bank executable.

What the tests cover

Native provider (normal and fast-harness profiles):

Direct SDK robustness (ravet-vst3-reference-robustness-tests, f32 and f64):

Each refusal must be silent, post nothing, and leave the digest unchanged: a twin instance that never saw the block must agree on the next good block. - Event Probe, admitted blocks: - capacity edges: 2048 events plus 2048 points, 4096 points, and a payload of exactly the capacity; - empty payloads, and events on other buses or of unknown or output-only kinds, which must be ignored even with invalid offsets; - a malformed non-Marker queue, which must be ignored; - an empty event list, a null event list, a parameter-only block, null output lists, and a zero-frame block with no output bus. - Event Probe, pinned behaviours: the documented host-output-refusal exception, and the digest reset on reactivation. - Keyed Sidechain with 0, 1 and 2 key channels: - silence-flag combinations on main and key inputs with garbage in the flagged buffers; - every malformed-buffer refusal; - zero-frame blocks with no buffers; - setActive(true) before initialize. - Every call, refused or not, reads the module's Rust allocation counter and requires zero.

Rust Graph bank (vst3-hosted-audio):

Limits

Direct malloc calls by the C++ wrappers or SDK are not counted. Whether the harness counter also sees C++ new calls made inside a loaded module has not been verified. No real-time deadline is claimed. - The allocation-probe feature is for tests only. It installs a global allocator in the staticlib and exports one extra symbol. - The ravet_* C symbols are exported from the crate's rlib as well. Linking it next to another archive that exports the same names would clash. - Keyed Sidechain accepts one auxiliary input bus, mono or stereo, only. - No state, editor, presets, bypass or latency reporting. Neither module is a public plug-in product.