Explicit CLAP discovery and trusted loading¶
For the separate optional external-SDK VST3 catalogue owner, see trusted VST3 catalogues. Instance/audio hosting below is CLAP.
The optional ravet-host crate supplies reusable explicit candidate discovery,
bounded owned plugin catalogues and loaded-entry lifetime access through pinned
Clack 0.2.0. It stays outside the portable umbrella. These are hosting mechanisms,
not a complete DAW, native plugin sandbox or third-party compatibility guarantee.
discover_clap scans only supplied directories: at most 32 directories, 4096
total entries and 1..1024 accepted candidates. It does not recurse, search system
paths or load code. It skips entries reported as symlinks, deduplicates canonical
paths and sorts results. macOS accepts .clap bundles/directories or regular
files; other targets accept regular files. Extensions match ASCII case-insensitively.
Missing/non-directory/symlink roots and I/O/capacity errors reject the complete
result. This is a candidate list; contents are validated only during selected
loading. Filesystem discovery is not an atomic snapshot or trust decision.
ClapLibrary::load is an explicit unsafe native-code boundary. The caller must
trust the currently selected file and its CLAP pointer/lifetime/thread compliance.
Loading/initialization and metadata inspection execute native code with full
process access. There is no timeout, crash/process isolation, signing validation
or protection from a noncompliant plugin. Do not load every discovered path just
to display a file list. from_entry also accepts an already-valid Clack entry,
including safely linked test plugins.
Catalogues accept 1..1024 plugins with unique nonempty IDs <=256 bytes, names and
optional vendor/version <=4096 bytes, and <=128 features of <=256 bytes. Owned
CString fields preserve native bytes; consumers explicitly choose display decoding.
Missing/oversized/duplicate metadata rejects. entry() exposes the valid Clack
entry for consumer-owned host callbacks, instances, audio/state/editor extensions
and lifecycle. Clack retains loaded code through instances independently of this
wrapper; dropping the catalogue does not invalidate a live instance.
cargo run -p ravet-host --example inspect --locked -- --discover target/clap/release
cargo run -p ravet-host --example inspect --locked -- --load 'target/clap/release/RAVET Gain.clap'
--discover executes no candidate. --load is for explicitly selected trusted
compliant libraries only. Its minimal offline example inspects the catalogue,
selects a plugin ID (optional last argument), instantiates with coalesced core
request handoff, saves/restores state through 1 MiB bounded storage when supported,
then activates, processes three f32 blocks on a separate thread and deactivates.
It accepts zero/one input bus and exactly one output bus with at most 64 channels;
no notes, output events, device or GUI is driven. It services at most one
main-thread callback at explicit pre/post-render points and reports remaining
requests while following its fixed offline lifecycle; it is not a persistent host
scheduler.
Do not infer full plugin compatibility from that example's narrow host contract.
The upstream unmodified Clack gain example at tag v0.2/commit
27ca28391b57a61bad4bb5099288a7bf9247350d was built under ignored target storage.
On local macOS arm64, discovery/load/catalogue, four-byte state roundtrip and three
stereo f32 blocks passed (0.25 output peak for 0.25 input). RAVET Gain/Sine bundles
also passed 32-/16-byte state and stereo/effect/instrument lifecycle checks. No
upstream source/binary is redistributed in this repository. Richer callback scheduling/transport/events, other formats and process isolation
remain separate work. See decision 0046.
Topics in this guide
- Guarded native editor lifecycle
- Deferred core host requests
- Hosted parameter metadata and inactive edits
- Deferred parameter extension callbacks
- Hosted audio configuration and timing
- Deferred audio configuration notifications
- Bounded native audio-owner handoff
- Prepared hosted f32/f64 graph nodes
- Packed native audio ports in graphs
- Opt-in hosted block-start transport
- Prepared global parameter input in native graphs
- Native parameter output and the preparation builder
- Selected native note/MIDI input in graphs
- Selected native performance output in graphs
- Main-owned editors for hosted graph nodes
- Global parameter gesture output
- Mapped global modulation in hosted nodes
- Mapped parameter gesture input
- Opt-in native note identities
- Selected standard note expressions
- Scoped release/choke directions
- Mapped scoped parameter values and modulation
- Completion notifications on originating input endpoints
Guarded native editor lifecycle¶
PluginEditor exclusively borrows a live Clack instance on its plugin/OS main
thread. create negotiates explicit API/floating configuration and owns successful
GUI creation. Embedded default and negotiated sizes must be 1..16384 per dimension.
A missing extension/unsupported API rejects before native creation; invalid initial
size after creation is paired with immediate destroy. Embedded show requires a
successful parent attachment. resize checks resize support and asks the plugin
to adjust before applying a bounded size. Successful show/hide calls are idempotent
and track state. Drop hides a shown GUI and always calls destroy, including scope
exit or early return. It borrows the instance, so the library remains live throughout.
set_parent and floating set_transient are explicit unsafe native operations:
the caller's compatible parent must remain alive through guard destruction, on
the correct OS UI thread. A Rust Window handle alone does not prove native-object
lifetime. Parent window creation/ownership, event-loop pumping, host GUI resize/show/
hide requests, focus/scaling and callback scheduling remain consumer responsibilities.
The temporary plugin_handle allows explicit state/parameter operations without
replacing/moving the borrowed instance; main_thread_callback services only an
explicitly scheduled plugin request. The guard does not activate/deactivate audio.
On macOS, the native fixture owns a Cocoa parent and verifies child creation and removal across three create/attach/show/resize-or-reject/hide/drop cycles:
cargo run -p ravet-editor-smoke --example hosted --locked -- 'target/clap/release/RAVET Gain.clap'
RAVET Gain and the unmodified upstream Clack EGUI gain at the pinned v0.2 commit both pass. The upstream GUI bundle was built only under target; its baseview/egui GPU dependencies are not RAVET dependencies. These tests establish local native child/lifecycle and callback acceptance, not visual hide/focus/accessibility or all-third-party editor compatibility. Floating GUIs and other OS/API combinations remain unvalidated. See decision 0048.
Deferred core host requests¶
RequestHost uses HostRequests for CLAP core restart, processing and
main-thread callback requests. Prepare one mailbox and clone it into the instance's
shared handler. Callback publication uses a single atomic byte without allocation,
locks or I/O. Repeated identical requests coalesce; these are pending actions,
not a lossless command history. take atomically drains all kinds, and defer
returns actions to the mailbox when the consumer cannot yet act.
service_callback consumes only a pending main-thread callback and invokes it
on the corresponding instance's main thread. It preserves restart/process flags
and services at most one callback. A reentrant request remains pending until
the next consumer tick. Restart/processing transitions, wakeups, audio ownership,
extension callbacks and application scheduling remain explicit consumer work.
The mailbox must belong to the supplied instance. No native event loop is created.
A linked synthetic plugin requests all kinds during actual CLAP initialization,
then requests another callback from on_main_thread. The fixture verifies bounded
deferred service and retained lifecycle actions. Concurrent publication checks
coalescing from three threads. This closes core request handoff, not the complete
host lifecycle or GUI/parameter extension contracts.
Hosted parameter metadata and inactive edits¶
HostedParameters exclusively borrows a live instance and inspects an explicit
maximum of 1..4096 parameters (zero actual parameters is valid). It owns stable
IDs, flags, raw name/module bytes and finite plain minimum/maximum/defaults.
Invalid IDs, duplicates, failed information queries, invalid ranges/defaults or
excess count reject the whole catalogue. No opaque parameter cookie is retained.
refresh replaces metadata only after the new catalogue fully validates;
consumers call it when state/rescan policy requires. The guard prevents cached
extensions from outliving or switching instances.
value reads a finite in-range plain value by stable ID. set_inactive validates
ID, read-only status, bounds and integral stepped values before dispatching one
global zero-time parameter event. It requires an inactive handle; active edits
belong to audio-thread event processing. The caller supplies the outgoing queue.
Dispatch is not acknowledgement; query the resulting value explicitly. There is
no normalization, guessing of slider skew, automatic rescan or persistence
fallback. Separate opt-in ParameterHost handlers receive extension requests. These are control-thread APIs,
not audio processing operations.
Linked Gain tests exercise exact value recall, invalid-edit preservation and active rejection. A synthetic CLAP parameter provider tests duplicate IDs, nonfinite/reversed ranges, bad defaults, stepped defaults, missing metadata, capacity, failed-refresh preservation, read-only edits and invalid returned values. The offline inspection example prints bounded hosted plain values when available.
Deferred parameter extension callbacks¶
ParameterHost explicitly advertises CLAP host parameters. Prepare one
ParameterSharedRequests and one ParameterMainRequests per instance, then pass
clones into its shared and main-thread factories. Shared core() preserves core
restart/process/callback handoff; take_flush() drains a coalesced atomic request.
Schedule either processing or the appropriate inactive/active flush when ownership
permits. Return an unserved request with defer_flush(). Never run process and
flush concurrently. Neither mailbox automatically invokes plugin code.
Main-thread take() returns coalesced rescan flags and up to 64 exact stable-ID
clear actions. Repeated IDs merge flags in first-observed order. Clear requests
invalidate consumer references such as automation/modulation; they are not value
edits. Rescan ALL needs a deactivated instance before refreshing the catalogue.
The main mailbox stays on its owner thread and cannot be sent/shared across
threads. Callback publication/drain uses prepared storage without heap operations.
More than 64 distinct IDs before draining sets overflow and retains the exact
prefix. Treat this as a host fault requiring explicit reference reconciliation or
instance rejection. A partial snapshot cannot prove that all references were
cleared; do not expand lost IDs into clearing unrelated user automation. There is
no automatic recovery, persistence or active automation scheduler. Clear/rescan
and flush drains are separate snapshots, rather than a transaction.
A linked native fixture calls the actual advertised extension during initialization and its deferred main-thread callback. Tests check exact IDs/flags, coalescing, bounded overflow, concurrent flush publication, retained core requests and 10,000 prepared callback/drain workloads with zero measured heap operations. See decision 0062.
The offline inspection example uses these handlers, rejects clear overflow and reports exact rescan/clear snapshots. It stores no automation/modulation references. Initialization flush requests are served by its subsequent process calls; post-render changes remain explicit diagnostics when the example stops.
Hosted audio configuration and timing¶
HostedAudioConfiguration::inspect owns an inactive instance's directional port
metadata. Missing audio-port extension means no ports. Each direction accepts
0..9 ports with 1..64 channels per port, unique IDs and optional main port at index
zero. Names remain bytes (at most 256); optional port types remain opaque owned
C strings (at most 256 bytes). IDs may match across directions. Advertised
in-place pairs must resolve to an opposite port of equal channel count. Preferred
64-bit ports must also advertise 64-bit support. These are deliberate validation
limits; larger or invalid declarations reject the whole snapshot.
refresh replaces the old snapshot only after full inspection succeeds. Both
inspection and refresh reject active instances. They allocate/call plugin code
on the control thread. Port enumeration preserves independent direction/order;
it does not infer portable main/aux pairs or negotiate a new plugin layout.
Consumers still choose precision and honor common-sample-size flags.
hosted_latency queries the main-thread latency extension, including after
activation when latency may change. hosted_tail requires a live audio-owner
Clack processor handle. Missing extensions return None, which differs from a
reported zero. Tail preserves finite sample counts or infinity (native raw values
at/above i32::MAX); the framework does not allocate in that query. Plugin callback
heap/deadline behavior remains the plugin's own contract. Separate opt-in ProcessingHost handlers receive timing/port change callbacks;
automatic compensation updates remain consumer work.
Inspect actual trusted bundle metadata without attempting an offline render:
cargo run -p ravet-host --example inspect --locked -- --metadata 'target/clap/release/RAVET Sidechain.clap'
Linked reference tests check directional stereo/mono sidechain and no-input instrument ports. A synthetic provider checks malformed/oversized declarations, failed refresh preservation, active rejection, exact large latency, finite/infinite tail and 10,000 prepared tail queries without heap operations. The actual built sidechain bundle also passes the metadata command. See decision 0063.
Deferred audio configuration notifications¶
ProcessingHost adds audio-port, latency and tail extensions to parameter-aware
handlers. Prepare a shared parameter mailbox, a main parameter mailbox and an
AudioConfigurationRequests mailbox for each instance. Construct
ProcessingMainRequests from main parameter/audio mailbox clones for the main
factory; pass an audio mailbox clone to activation's audio-handler factory. Keep
consumer clones outside callbacks for subsequent drains. Core request service
works with either handler type.
AudioConfigurationRequests::take() coalesces port rescan flags, latency changes
and tail changes into one atomic snapshot. defer() republishes unserved kinds.
Native main-thread port/latency and audio-thread tail callbacks only publish;
there is no plugin reentry, allocation, lock or automatic lifecycle transition.
The host advertises all defined port rescan flags. Non-name changes require
consumer deactivation; this framework's full snapshot API requires deactivation
even for names. Re-query latency after activation and tail on its audio owner.
Consumers must rebuild/retire prepared routes when configuration/timing changes.
The offline example now uses these combined handlers, reports initial/activation/ post-render changes, queries latency after activation and tail before stopping on the audio worker. It does not restart or rewire itself. Linked fixtures call the actual advertised extensions on their main/activation/audio paths; tests check coalescing, deactivation flags, deferred preservation, unknown flag masking and 10,000 prepared zero-heap workloads. See decision 0064.
Bounded native audio-owner handoff¶
hosted_audio_handoff accepts a tagged Clack stopped processor already activated
by its main-thread PluginInstance owner and allocates 1..4-slot pending/retired
queues. Invalid capacity returns initial ownership; full/abandoned offers retain
the rejected token. Keep each matching main-thread instance alive until its
stopped audio token returns for explicit instance.deactivate. Consumer tags,
configuration/latency compatibility and reference reconciliation remain explicit.
Move HostedAudioReady to the audio owner and call start. Initial failure returns
the complete ready state. HostedAudioRunning cannot move across threads: its
private started token stays behind audio-owner process, reset, replacement and
stop operations. A compile-fail example enforces the running engine's non-Send
contract. Prepared native buffer/event/transport validity and ordinary native
fault/silence/sleep policy remain the consumer's responsibility.
At a caller-selected valid block boundary, apply_pending accepts at most one
FIFO candidate. Full retirement space prevents dequeue/start/stop. It starts the
candidate before stopping the old processor, then returns the old stopped token
to control. A failed candidate start retires that stopped token and preserves the
old processor/history. Missing control preserves current processing and pending
ownership. There is no crossfade, old-tail drain, migration or automatic restart
request service. Ordinary swaps/process/reset allocate or destroy no framework
storage; native callbacks keep their own obligations and deadline evidence.
Finish on the audio owner outside callbacks. The returned ready engine is
transferable back to main; stop control offers, drain take_pending for every
unstarted proposal, then extract into_current. Each token must deactivate its
matching instance on main. Retired tokens remain on HostedAudioControl. Dropping
a running engine instead stops its current processor on its owner, but explicit
finish preserves tokens for orderly deactivation. Endpoint/token destruction is
outside callbacks. Keep main instances alive through all queued/running/retired
ownership; the handoff does not establish a new native-code trust boundary.
CARGO_BUILD_JOBS=2 cargo run --release -p ravet-host --example audio-handoff --locked -- \
'target/clap/release/RAVET Gain.clap' org.ravet.gain
# Explicit trusted stereo f32 effect, selected ID, expected default linear gain:
# audio-handoff <trusted CLAP library> <plugin ID> [expected default gain]
The example uses no audio device or window and expects one stereo input/output port. It renders 2000 blocks/32000 frames across two instances, compares samples, checks measured allocations and returns tokens for main-thread deactivation. Linked synthetic tests check actual native start/process/reset/stop/deactivate/ destroy thread IDs, initial/candidate failure recovery, backpressure, full/gone offers, abandoned control, pending shutdown and retained audio. Actual-binary scope belongs in work status. Graph port mapping, format/device scheduling, physical deadlines and general third-party compatibility remain separate work. See decision 0106.
The handoff adds a direct dependency on already-pinned rtrb 0.4.0, whose reviewed manifest declares MIT OR Apache-2.0. No new external lockfile package is introduced; retain its applicable notices when distributing binaries.
Prepared hosted f32/f64 graph nodes¶
prepare_hosted_graph_node (f32) and prepare_hosted_graph_node64 (f64) consume a caller-created trusted Clack instance on
main, fixed HostedGraphNodeOptions and an audio-handler factory. Inspection
requires zero/one main audio port per direction matching the declared layout;
1..64 channels per nonempty direction, 1..65536 frames, no auxiliary buses or
input/output events. Unsupported configurations/metadata retain instance ownership
before activation. Scratch/native port arrays and one return slot allocate on
main before activation. The optional host dependency adds only internal foundation
and development graph packages; no new external lockfile package is introduced.
The returned HostedGraphNode or HostedGraphNode64 implements the matching
f32/f64 Processor for a GraphNode. The f64 factory rejects with Precision
before activation unless every mapped port advertises 64-bit support. It does not
convert samples. Existing RAVET Gain CLAP is f32; the f64 validation fixture
is a separate public memoryless stereo x2 plug-in.
Its prepare checks the already activated fixed contract; it does not activate on
audio. Use explicit graph sessions.
Audio start/process/reset/stop run on the session owner, with native start rejected
on main and runtime owner checks. Input copies to prepared mutable native scratch;
output scratch clears before native calls and copies back afterward. Empty blocks
skip native processing. Native events remain separate work; packed auxiliary ports use their separate
factories below, and block-start transport has explicit opt-in below. Native tail/latency are consumer-declared graph timing, not guessed from
missing extensions or queried on incorrect threads. Node scratch is at most
32 MiB for f32 or 64 MiB for f64, outside the graph's own cap; aggregate/plugin memory remains consumer-budgeted.
Native success statuses are retained in independent atomic diagnostics; this node
does not automatically sleep, drain tails or restart. Runtime errors silence output
and return BackendFailure, acknowledging native histories may have advanced.
Graph fault/reset policy remains explicit. Node-local steady time advances per
native attempt, independent of playback sample position. Signed-clock overflow
makes steady time unavailable until reset. Reset while stopped defers the native
reset until audio start; reset while running executes on its owner. Normal rendering
introduces no framework heap action, locks or I/O; native callbacks retain obligations.
Retain HostedGraphNodeControl on main until the node has stopped on audio and
its graph deactivates/drops on control. Node deactivation returns its stopped token;
reclaim executes matching main-thread deactivation at most once. Control cannot
move across threads. Early control destruction follows Clack's protective lifetime
leak policy; orderly stopped retirement/reclaim is required. Explicit bounded
service_callback uses the installed HostRequests mailbox, leaving restart/process
scheduling to the application. After reclaim, into_instance recovers the inactive
instance for state/parameter editing or a fresh activation/node. Nodes are one-shot
after deactivation; graph reprepare does not implicitly reactivate them.
CARGO_BUILD_JOBS=2 cargo run --release -p ravet-host --example hosted-graph --locked -- \
'target/clap/release/RAVET Gain.clap' org.ravet.gain
# Caller-selected trusted zero-latency stereo gain probe:
# hosted-graph [--f64] <trusted CLAP library> <plugin ID> [expected default gain]
The no-device/no-window example declares the known gain probe's zero latency/no tail explicitly. It verifies native wet/dry routing across 2000 blocks/32000 frames, dynamic owner swap/stop, measured allocations and main graph/instance retirement. Synthetic linked callbacks verify native thread IDs, shape/phase rejection, backend silence/nonrollback, empty/malformed blocks, sleeping-output initialization, reset/steady clocks, main callback service and stopped token/instance recovery. Actual-binary evidence and its limits belong in work status. No broad third-party compatibility, editor/parameter automation, other format/device scheduling or physical deadline claim. See decision 0108.
The native f64 fixture validates wide finite values (including 1e200 and sub-f32 precision) through the actual loaded ABI, wet/dry routes and live graph swap:
CARGO_BUILD_JOBS=2 cargo build --release -p ravet-host --example graph-fixture --locked
CARGO_BUILD_JOBS=2 cargo run --release -p ravet-host --example hosted-graph --locked -- \
--f64 target/release/examples/libgraph_fixture.dylib org.ravet.host-graph-fixture 2
The displayed fixture filename is macOS-specific; use the local Cargo cdylib artifact on other systems. Only macOS arm64 has actual-binary evidence here. The fixture has no parameters/state/editor/input-output events or product assets; its optional known synthetic transport profile is described below. See decision 0109.
Packed native audio ports in graphs¶
prepare_hosted_graph_ports_node and prepare_hosted_graph_ports_node64 select
f32/f64 and pack each direction independently in native enumeration order, then
channel order within each port. Inspect HostedAudioConfiguration while inactive
to choose the graph layout and explicit route indices. The layout's input/output
counts must equal the respective native sums; up to nine ports per direction and
64 total channels per direction. Main flags do not determine portable pairing.
All f64 ports, including auxiliary ports, must advertise native 64-bit support.
Graph routes address packed main channel indices; foundation auxiliary buses remain empty. For the reference sidechain, inputs 0/1 map to native main port 0, input 2 to the mono detector port 1, and outputs 0/1 to native output port 0. Prepared per-port buffers preserve native boundaries and use separate input/output storage. These factories do not negotiate layouts or infer sidechain policy. The original main-only factories keep their zero/one-main-port restriction.
Bounds, node-owned scratch limits, declared timing, event/transport exclusion, one-shot owner sessions and main stopped retirement match the main-only profile. Use the explicit known zero-latency/no-tail sidechain validation profile:
CARGO_BUILD_JOBS=2 cargo run --release -p ravet-host --example hosted-graph --locked -- \
--sidechain 'target/clap/release/RAVET Sidechain.clap' org.ravet.sidechain
CARGO_BUILD_JOBS=2 cargo run --release -p ravet-host --example hosted-graph --locked -- \
--f64 --sidechain 'target/clap/release/RAVET Sidechain.clap' org.ravet.sidechain
This profile fixes the reference detector threshold at 0.1, exercises alternating 0.05/0.2 detector samples and verifies two graph instances/swaps/retirement without a device or window. It is not a general sidechain discovery/host compatibility claim. See decision 0110.
Opt-in hosted block-start transport¶
HostedGraphNode::set_transport_enabled(true) and the f64 counterpart enable
mapping on main while the native token is stopped, before transferring it to an
audio owner. Default nodes continue to omit native transport. The standalone
hosted_transport helper implements the same checked allocation-free conversion.
All absent optional fields produce no native context. If any field is known,
playing must be known: CLAP's transport flags cannot express unknown playing.
This policy preserves absence without silently assuming stopped.
Known positive finite tempo, beat position and sample position map to flagged
block-start fields. CLAP uses signed 31-fractional-bit beat/second timelines;
rounding outside that range rejects with ProcessError::InvalidTransport, clears
outputs and occurs before native calls/history/steady-time changes. Sample position
converts through f64 seconds, so floating and fixed-point quantization apply; the
whole i64 sample range does not promise exact roundtrip. Negative positions are
allowed. Discontinuity has no native flag and does not reset DSP.
Optional Transport.details forwards recording/active-loop/pre-roll flags,
positive signature components, finite BPM-per-sample tempo increment and current
bar start/zero-based number. Increment requires known tempo; bar requires known
beats. Active loops require ordered bounds for every known timeline; supplied
inactive bounds reject. Fixed-point rounding must preserve strict endpoint order.
Unknown details preserve legacy false flags/zero metadata; CLAP cannot distinguish
unknown from false for these status flags. A status-only active loop with neither
timeline is allowed. Negative bars/bounds and non-power-of-two denominators are
accepted. No extrapolation, automatic looping, resetting or mid-block transport
updates are added. See decision 0127.
New metadata has offline conversion evidence; the loaded fixture profile below
continues to cover the earlier playing/position/tempo fields.
The public fixture's --transport profile validates exact negative seconds/beats,
positive tempo and flags, then uses alternating playing/stopped blocks for x2/
silent native output. Ordinary no-context fixture behavior remains x2.
CARGO_BUILD_JOBS=2 cargo build --release -p ravet-host --example graph-fixture --locked
CARGO_BUILD_JOBS=2 cargo run --release -p ravet-host --example hosted-graph --locked -- \
--f64 --transport target/release/examples/libgraph_fixture.dylib org.ravet.host-graph-fixture 2
--transport is restricted to the known public fixture profile; it is not a
musical host application or device transport. Typed nodes support opt-in with
other explicit instances under their transport contracts. See
decision 0111.
Prepared global parameter input in native graphs¶
prepare_hosted_parameter_graph_node and its 64 counterpart add an explicit
input profile. Supply HostedGraphPortMode::{MainOnly,Packed}, fixed node options
with 1..4096 input-event capacity, and 1..4096 HostedGraphParameterMapping entries
from product-stable graph IDs to current native IDs. Main inspects at most 4096
current native parameters before activation. Missing, unknown, read-only and
ambiguous duplicate identities reject while retaining the inactive instance.
Metadata range/stepped constraints are cached without cookies or native pointers.
The whole portable block validates before native calls. Only global parameter values are accepted; unknown graph IDs, other event kinds, out-of-range values or nonintegral stepped values reject the entire block. Plain physical f32 event values widen to native f64, including when audio is f64; parameter precision remains the portable event precision. No normalization/scaling, per-note targets, gestures, automatic recording/GUI service or other parameter policy is inferred. Same-frame order and offsets are preserved with global wildcard targets and empty cookies.
Event slots allocate on main and never grow during processing. Sorted mapping
lookup is bounded, without callback locks/I/O/heap actions introduced by RAVET.
Native plug-ins must honor their callback obligations. Positive-frame input goes
through native process; valid zero-frame input uses active parameter flush on its
audio owner, with no native process/steady-time advancement. parameter_flushes
counts dispatches, without claiming acknowledgement. Native output is discarded
in this input-only profile. Parameter/state/rescan changes require stopped
retirement, metadata inspection and fresh preparation; this is a fixed snapshot.
The original audio-only factories keep rejecting nonzero event configurations.
CARGO_BUILD_JOBS=2 cargo build --release -p ravet-host --example parameter-fixture --locked
CARGO_BUILD_JOBS=2 cargo run --release -p ravet-host --example hosted-graph --locked -- \
--f64 --parameters target/release/examples/libparameter_fixture.dylib org.ravet.host-parameter-fixture
The public synthetic stepped stereo gain fixture exposes writable native ID 1 and read-only ID 2. The graph probe maps graph ID 9001 to native ID 1, sends gain 2 at frame 0, gain 3 then 1 at frame 4 and gain 0 at frame 12, verifying last-event order through wet/dry routes/swaps. Each instance also receives an empty-block gain-4 flush. There are no product assets, state/editor/device or general host compatibility claims. See decision 0112.
Native parameter output and the preparation builder¶
HostedGraphNodeBuilder::new(options) selects main-only audio by default.
with_port_mode, with_transport_enabled, with_parameter_input and
with_parameter_output select independent explicit profiles before prepare
(f32) or prepare64. The original factories keep their contracts. Validation,
metadata queries and storage allocation happen on main; failed preparation
retains the inactive instance. Configure exactly matching input/output event
capacities, with 1..4096 slots for each selected parameter direction.
Output mappings allow both writable and read-only native IDs. Each direction has
unique native/graph identities, checked against current metadata. Native global
parameter output maps to plain portable f32 values; f64 values round and must
remain finite/in-range/integral when stepped. Full native f64 parameter precision
is not preserved. Typed mapped output checks native ranges, offsets, order and
capacity. Any rejection poisons the block: audio/output events clear and
BackendFailure reports nonrollback native history. Unmapped, unsupported or
per-note output is intentionally filtered and counted. Cookies are never retained.
The native ABI still requires valid plugin event memory; this is not crash isolation.
filtered_native_events and rejected_native_events are independent relaxed
counters. Successful block counters/status require accepted mapped output; native
processing/steady time may advance on an output fault. Zero-frame output profiles
explicitly dispatch active parameter flush even without input, then publish mapped
output at offset zero. Flush counters count dispatch, not plugin acknowledgement.
Audio-only/input-only profiles retain void output behavior. No retry, rescan,
recording/GUI scheduler, per-note/MIDI/modulation/gesture output is added here.
For graph routing, prepare explicit event edges from host to the input node and node to host (or subsequent nodes). Outer/native node output capacities must match the graph's prepared routing contract. The public probe maps input graph ID 9001 to native gain 1 and read-only native output 2 to graph ID 9100:
CARGO_BUILD_JOBS=2 cargo build --release -p ravet-host --example parameter-fixture --locked
CARGO_BUILD_JOBS=2 cargo run --release -p ravet-host --example hosted-graph --locked -- \
--f64 --parameter-io target/release/examples/libparameter_fixture.dylib org.ravet.host-parameter-fixture
It verifies the last requested gain at frame 15 for each normal block and gain 4 at offset zero after flush, through two graph instances/wet-dry swaps. This is synthetic macOS arm64 actual-binary evidence, without broad plugin/device/DAW/ platform/deadline claims. See decision 0113.
Selected native note/MIDI input in graphs¶
The builder's with_note_input(HostedGraphNoteInput { port_index, dialect })
selects one current native input index, not its metadata ID. Inactive
preparation checks the note-ports extension, a maximum of 256 input ports, the
selected metadata and advertised dialect. Selection is fixed until retirement
and fresh preparation. Input capacity is 1..4096, shared with optional global
parameter input. Original factories retain their existing strict profiles.
Clap emits portable NoteOn/Off with selected port/channel/key and wildcard note
ID. Midi and MidiMpe accept validated short MIDI and portable notes; normalized
velocity rounds to 0..127. A quantized zero-velocity MIDI note-on follows MIDI's
note-off semantics. One/two-byte short messages pad unused native bytes with zero.
Midi2 accepts framed UMP only, preserving group/words and padding to four native
words; portable normalized notes and short MIDI are rejected in this dialect.
No protocol conversion, MPE zone negotiation, group rewrite, SysEx assembly,
native note ID or note-expression translation is inferred.
Offsets and same-frame ordering survive mixed parameter/performance input. Whole-block validation and encoding precede native calls. Unselected event kinds, dialect mismatches and invalid values reject before mutation and silence audio. Performance input in a zero-frame block rejects, including mixed parameter prefixes: parameter flush is not a performance delivery API. Valid parameter-only zero-frame behavior remains unchanged. A note-only node needs no params extension. All event storage is prepared on main; native plugin callback obligations remain its responsibility. Native note output uses the separate explicit profile below; port-rescan servicing remains separate.
The synthetic public parameter fixture exposes input index 1 with metadata ID 101 and four dialects. It deliberately checks the received target/payload. These commands verify sample-timed mixed streams and mapped read-only output through prepared graph edges, wet/dry routes, live swaps and main retirement:
CARGO_BUILD_JOBS=2 cargo build --release -p ravet-host --example parameter-fixture --locked
CARGO_BUILD_JOBS=2 cargo run --release -p ravet-host --example hosted-graph --locked -- \
--f64 --ump-notes target/release/examples/libparameter_fixture.dylib org.ravet.host-parameter-fixture
Use --clap-notes, --midi-notes or --mpe-notes for the other selected dialects;
omit --f64 for native f32. The independent public sine probe uses its actual
MIDI input index 0 and no audio input, checking timed onset and release silence:
CARGO_BUILD_JOBS=2 cargo run --release -p ravet-host --example hosted-sine --locked -- \
'target/clap/release/RAVET Sine.clap'
These are synthetic offline native-binary checks, with no device playback or DAW/platform/deadline guarantee. See decision 0114.
Selected native performance output in graphs¶
with_note_output(HostedGraphNoteOutput { port_index, dialect }) selects one
inactive-validated output index and advertised dialect, independently of input
selection and global parameter mappings. At most 256 outputs may be advertised;
1..4096 output slots are shared across accepted parameters/performance. A
performance-only profile needs no parameter mappings or params extension.
CLAP NoteOn/Off require the selected concrete port, concrete channel/key and wildcard note ID. Specific note IDs, wildcard channels/keys, choke/end/expression or other dialects/ports filter and count, because the portable events cannot retain those meanings. Accepted normalized native f64 velocity narrows to f32 and must be finite within 0..1. MIDI/MPE retain complete short-message wire bytes (including zero-velocity note-on), ignoring unused native padding. Malformed selected statuses/data reject; SysEx has no short-message representation. MIDI2 retains framed UMP words/groups, ignoring unused padded words and making no semantic conversion. No MPE configuration or automatic MIDI1/MIDI2 conversion.
Accepted events share exact native offsets/order/capacity checks with parameters.
Malformed selected output, overflow, out-of-range offset or decreasing order
poisons the whole block: clear audio/event prefixes and report nonrollback
BackendFailure. Native history/steady time may already advance. The existing
filtered/rejected counters cover both output kinds. Performance emitted during
zero-frame parameter flush rejects; the adapter never uses flush to deliver
performance. A params-less empty output-only block makes no native call.
Native event memory still must satisfy the trusted plugin ABI contract.
The hosted-graph note switches above now select both input and output. The
synthetic fixture echoes selected performance at its original frames, then emits
mapped read-only gain at the final frame. Explicit all-event graph edges verify
both directions through wet/dry swaps; parameter-only switches retain their old
mapping profiles. See
decision 0115.
Main-owned editors for hosted graph nodes¶
HostedGraphNodeControl::editor(configuration) creates the existing guarded
PluginEditor while borrowing the retained native instance. The separate audio
node/graph may continue running on its owner thread. Rust borrowing prevents
control reclaim/drop until the editor is destroyed. The main control/library
remain alive; editor drop pairs successful creation with hide/destroy. This adds
no editor calls to graph processing and does not change audio ownership.
The caller owns the OS main thread, live compatible parent and main event loop.
PluginEditor::service_callback(&requests) services at most one coalesced core
callback from the instance's own host mailbox, preserving restart/process flags;
re-requests wait for another tick. Parent/resize/focus, GUI extension requests,
parameter flushes, restart/rebuild and device scheduling remain consumer policy.
Close/destroy the editor before reclaiming the stopped graph token.
The native macOS example creates three guarded embedded editors while its hosted gain graph processes 1000 blocks per editor. It checks child cleanup, resize, finite exact stereo output, callback heap actions, audio stop and main retirement:
CARGO_BUILD_JOBS=2 cargo run --release -p ravet-editor-smoke --example graph-hosted --locked -- \
'target/clap/release/RAVET Gain.clap'
This explicit probe is for trusted zero-latency stereo default-unity gain bundles; it opens no device or DAW session. See decision 0116.
The optional device crate has an explicit buffered f32 graph-worker output bridge. A non-Send graph session stays on its worker while a separate device processor consumes a prepared queue; stop/join precedes main graph/native retirement. No device clock or callback-thread teardown is inferred. See the device guide and decision 0117.
Buffered output workers also accept explicitly staged bounded graph input via
pump_events, acknowledging it only when a fresh render succeeds. Pending audio
publication does not consume or repeat new parameters/notes. Native profiles
must match the prepared event capacity. See
the event-input guide and
decision 0118.
pump_io additionally returns a caller-owned bounded generated-event prefix once
per successful fresh render. Pending audio publication never repeats generated
events; offsets describe the rendered block, without device-clock mapping or
control-thread delivery. See
the output guide and
decision 0119.
Fixed complete-batch event queues can connect main control to graph workers and return worker-generated values. A borrowed batch stays retained until explicit acknowledgement, and full/invalid publication exposes no prefix. Tags/context remain caller-owned and scheduling/recovery stay explicit. See the queue guide and decision 0120.
Global parameter gesture output¶
HostedGraphNodeBuilder::with_parameter_gesture_output(true) includes native
global begin/end events alongside explicitly mapped parameter output values.
An output mapping is required before activation. Boundaries alias native IDs to
portable IDs, require writable cached metadata, and share prepared output capacity.
Disabled or unmapped boundaries filter. Mapped read-only boundaries, invalid
ordering/offset or overflow fail the entire block with nonrollback backend failure.
Zero-frame parameter flushes permit boundaries at offset zero. Pairing, repeated
ends, ownership, cancellation, recording and GUI delivery remain receiver policy.
The default output profile filters boundaries; native input translation is separate.
The public synthetic gesture fixture exercises f32/f64 rendering, zero-frame flush and live graph swaps without audio allocation:
CARGO_BUILD_JOBS=2 cargo build --release -p ravet-host --example gesture-fixture --locked
CARGO_BUILD_JOBS=2 cargo run --release -p ravet-host --example hosted-graph --locked -- \
--gestures target/release/examples/libgesture_fixture.dylib org.ravet.host-gesture-fixture
CARGO_BUILD_JOBS=2 cargo run --release -p ravet-host --example hosted-graph --locked -- \
--f64 --gestures target/release/examples/libgesture_fixture.dylib org.ravet.host-gesture-fixture
This synthetic native probe opens no editor/device/DAW and does not establish host automation recording. See decision 0121.
Mapped global modulation in hosted nodes¶
HostedGraphNodeBuilder::with_parameter_modulation_input(true) and
with_parameter_modulation_output(true) independently enable global modulation
on their explicit parameter maps. Both default false and require the matching
mapping at preparation. Input must target cached writable native modulatable
parameters; unsupported targets reject the whole input before native mutation.
Amounts widen from finite f32 to f64 independently of scalar base range/step checks.
Enabled output requires writable modulatable cached metadata and finite f64 amounts that narrow to finite f32. Disabled, unmapped and scoped native output filters; selected invalid amount/order/offset or shared-capacity overflow clears the block with nonrollback backend failure. Zero-frame parameter flush carries modulation at offset zero. Native DSP owns combination/clipping/smoothing and persistence; these signals do not overwrite saved base values by themselves. Metadata changes require fresh stopped preparation, without cookies or automatic rescans.
The public synthetic modulation fixture keeps a stepped base separate from a fractional signed modulation signal and reports both through explicit aliases:
CARGO_BUILD_JOBS=2 cargo build --release -p ravet-host --example modulation-fixture --locked
CARGO_BUILD_JOBS=2 cargo run --release -p ravet-host --example hosted-modulation --locked -- \
target/release/examples/libmodulation_fixture.dylib
CARGO_BUILD_JOBS=2 cargo run --release -p ravet-host --example hosted-modulation --locked -- \
--f64 target/release/examples/libmodulation_fixture.dylib
This checks 1000 native blocks per precision, timing/flush/base telemetry, no audio heap actions and main retirement; it opens no device/GUI/DAW. See decision 0123.
Buffered workers can also consume explicit queued f32 audio input, routing native effects between input/output queues without native lifecycle on device callbacks. Fresh renders zero input shortages and report queue source/underflow progress; pending output never consumes input twice. Graph faults retain their error until explicit reset. See the device guide and decision 0124.
Mapped parameter gesture input¶
HostedGraphNodeBuilder::with_parameter_gesture_input(true) enables raw global
begin/end input alongside scalar values. An explicit input mapping is required
before activation. Cached mappings already require writable native metadata;
unknown or disabled boundaries reject the whole input before native rendering or
parameter flush, even after a valid scalar prefix. Boundaries share the prepared
input capacity, retain process timing/order and accept offset zero during empty
parameter flushes. Repeated ends are raw input, with no pairing/ownership inference.
They do not themselves change saved base values. The receiver owns recording and
GUI/DSP policy. No MIDI port is needed. Default input rejects portable boundaries.
Synthetic linked native f32/f64 fixture tests echo mapped input to separately mapped output with exact timestamps, preserve base audio/values, normalize empty flush timing, reject disabled/unknown input atomically, require a map before activation and measure 1000 blocks without heap activity. This is offline ABI fixture evidence; no third-party gesture receiver, UI recording or DAW support claim. See decision 0130.
Opt-in native note identities¶
Select an explicit CLAP note input/output port, then enable the matching builder
with_note_identity_input/with_note_identity_output. This profile carries
NoteWithId On/Off events, preserving concrete channel/key, sample order, native
0..=i32::MAX identity and f32 velocity (native output narrows checked f64 0..1).
No graph/native ID aliasing is inferred. Zero-velocity On retains On semantics.
Unknown voice/lifetime/duplicate ID policy belongs to the receiver.
Default input rejects identified notes; default output continues to filter them. Unsupported MIDI/MPE/UMP selection or absent matching port rejects before activation. Invalid input IDs reject the whole input before native mutation, even after a valid note prefix. Selected identity output rejects raw IDs below native -1 wildcard instead of Clack's negative-to-wildcard normalization; unrelated ports filter. Wildcard-ID native output retains legacy NoteOn/Off. Performance zero-frame policy and output fault/nonrollback rules remain unchanged.
Portable validation/queue tests preserve full u32 IDs; native unit tests cover 0/i32::MAX, zero On and malformed negative IDs. Linked f32/f64 fixture tests cover 1000 blocks per profile, echo/time/audio, disabled filtering, complete-prefix rejection and no measured heap activity. Offline fixture evidence only, without third-party voice/DAW/native deadline support. Scope, choke, completion and scoped parameter mappings use separate profiles below. Scoped expression routing is described below. See decision 0132.
Selected standard note expressions¶
Enable with_note_expression_input(true) or with_note_expression_output(true)
after selecting the corresponding CLAP note port. These choices are independent
of identified On/Off selection. Standard volume/pan/tuning/vibrato/expression/
brightness/pressure preserve full f64 values and optional channel/key/note ID
matching scope; the selected port is exact, rather than a wildcard across ports.
Portable IDs are u32 and native IDs are limited to nonnegative i32. Disabled or
invalid input rejects the complete input before native mutation. Missing ports
and MIDI/MPE/UMP dialect selection reject before activation.
Disabled native output, unknown expression types and other/wildcard ports filter. For selected standard types, raw channel/key/ID values below -1 and invalid finite ranges reject rather than becoming wildcards or clipped values. Selected capacity/timing/scalar failures retain native history but silence/discard complete portable output. Performance in an empty parameter flush remains unsupported. The pinned Clack 0.2.0 type accessor omits Expression, so conversion explicitly matches all seven public wire discriminators.
Portable, queue and native unit tests cover ranges, all seven types, wildcard scope, exact f64 values and malformed fields. Linked f32/f64 fixtures cover 1000 blocks per profile, exact echo/audio timing, disabled filtering, whole-prefix rejection, output capacity/nonrollback behavior and no measured heap actions. This is offline ABI evidence; voice application and third-party/DAW/deadline validation remain separate. See decision 0134.
Scoped release/choke directions¶
with_scoped_note_input(true) and with_scoped_note_output(true) require explicit
CLAP ports for their corresponding directions. Input converts NoteOffScoped and
NoteChoke without loss of optional channel/key/native ID, independently of
identified On/Off and expression flags. Complete disabled/invalid input rejects
pre-native. Output preserves wildcard-channel/key Off and choke; concrete native
Off retains legacy/identified selection. Default scoped output filters.
Exact selected-port raw scope checks reject channel/key/ID below -1 and out-of- range concrete channel/key; other or wildcard ports filter. Off velocity narrows checked finite native 0..1 to f32; choke's ignored native velocity is not validated or carried as a scalar. Prepared capacities, input zero-frame rejection, output fault/nonrollback and retirement rules remain unchanged. Voice release/termination and NoteEnd completion belong to distinct receiver/protocol policies. Linked f32/f64 fixtures test 1000 blocks per selection, exact echo/audio timing, invalid prefix rejection, capacity/malformed output history and main-thread reclamation without measured processing heap actions. See decision 0136.
Mapped scoped parameter values and modulation¶
with_scoped_parameter_input(true) and with_scoped_parameter_output(true)
require explicit parameter maps and a selected CLAP note input port before
activation. Output scope refers to originating input notes, independently of any
selected note output port. Existing nonzero prepared input/shared event capacities
apply. Input maps are writable; selected scoped output also requires writable
metadata. Default input rejects scoped portable events and default output filters
scoped native parameters.
Preparation caches native range, stepped/read-only and all scope flags. This profile selects the most specific supplied dimension: note ID, then key, then channel, then port. Only its corresponding per-scope automation/modulation flag admits the event. Global automation/modulation flags grant no scoped support; scoped modulation does not require the separate global modulation flag. The remaining supplied target fields still restrict matching. All-wildcard NoteTarget remains scoped to the exact selected input port.
Input checks mapping, scope/native ID, finite full-f64 values and native metadata before any native callback. Scoped values obey cached range and integral stepped constraints; signed finite scoped modulation is independent of base bounds/steps. A bad event after a valid global/scoped prefix rejects the entire input. Output filters unknown IDs and unrelated/wildcard ports; selected malformed scope, unsupported flags/read-only metadata, range/step/scalar/timing/capacity reject complete portable output after native mutation. Cookies are never retained and input constructors use empty cookies. Only exact raw all--1 tuples are global, preventing malformed scope normalization into global edits.
Scoped parameter process and zero-time flush preserve full f64 value/amount and scope without narrowing to audio precision. No global state or voice application is synthesized. Metadata/rescan/state changes require stopped retirement and new preparation. Linked native f32/f64 fixtures cover 1000-block selected directions, exact audio/echo/flush, separate saved base state, invalid prefixes/native ID width, capacity/nonrollback and all four scope granularities. Independent native flag combinations, scalar/step/unknown-ID, unsupported output and preparation checks pass with measured heap-free prepared workloads. Offline synthetic ABI evidence only; no third-party voice/DAW/deadline claim. See decision 0138.
Completion notifications on originating input endpoints¶
HostedGraphNodeBuilder::with_note_end_output(true) preserves NoteEnd { target }
notifications from the selected CLAP note input endpoint. A note output
selector, identity output profile and parameter mappings are not required. The
selected input profile requires nonzero input capacity, and completion delivery
requires nonzero bounded output capacity shared with other retained events.
Disabled completion output filters; unsupported input dialects/absent routes and
capacities reject before activation. Portable completion input to a hosted plug-in
rejects the whole input before native mutation, including preceding valid edits.
CLAP specifies that completion reports voice lifetime against the original note input tuple and ignores timestamp and velocity; see its event contract. The adapter checks raw channel/key/optional nonnegative i32 ID, ignores native time/velocity, and filters other or wildcard ports. The portable carrier offset is the previous retained event's offset, or zero if none, preserving delivery order without inventing a termination instant. Subsequent timed events still obey normal ordering. Selected malformed scope/capacity faults discard complete portable output with native history retained. Parameter-only zero-frame flush rejects completion output. The framework infers no voice tracking, duplicate-ID policy or notification from note-off/choke.
Linked f32/f64 fixture tests cover complete delivery without a selected note output or parameter output map, independent filtering, ignored maximum/backward native timestamps and NaN velocity, carrier ordering, rejected input prefixes, capacity faults, retained saved state and 1000 blocks without counted heap actions. Raw tests cover native bounds, wildcard target, malformed fields and flush rejection. Offline ABI evidence only; no third-party/DAW claim. See decision 0140.