Skip to content
On this page

MIDI and event timing

Build instruments that handle note identity, automation, and dense event blocks correctly.

Vesty delivers VST3 events as typed Event values in ProcessContext and ProcessContext64. Start from the MIDI synth example, a monophonic instrument with tests for timing, expression, and SysEx.

Declare your inputs

An instrument declares PluginKind::Instrument and an event input bus in its plugin bus layout. A plugin receives only the event types and buses it supports. VST3 represents mapped MIDI controllers through parameter changes: declare mappings with with_midi_mapping on your parameter, rather than expecting every host to forward raw MIDI CC bytes. See Parameters and the example's program parameter.

Channel numbers are zero-based (0..=15), keys are 0..=127, and note events carry a host note_id. For polyphonic voice tracking, prefer the note ID when the host supplies one; define a channel/key fallback for events without a usable ID. Match the channel as well as the key when releasing a voice.

Render between events

Offsets are relative to the audio range in the current process call. Do not apply all events before rendering the entire buffer.

For example, with NoteOn at 8 and NoteOff at 24 in a 32-frame context:

RangeWork
Samples 0–7Render the previous voice state
Before sample 8Apply NoteOn
Samples 8–23Render the active voice
Before sample 24Apply NoteOff
Samples 24–31Render the released voice state

Use this loop structure in your kernel; render and apply below represent your own allocation-free DSP functions:

Text
cursor = 0for event in context.events(): render(cursor .. event.sample_offset()) apply(event) cursor = event.sample_offset()render(cursor .. context.audio().frames())

Consume events at identical offsets in their provided order. Parameter events precede note-list events at equal offsets; order within each source is stable. Keep oscillator and envelope state across calls, and update sample-rate-dependent state in prepare().

Treat zero-velocity NoteOn as a release where MIDI semantics require it. The example implements this. The adapter filters invalid bus/channel/key/offset data and non-finite NoteOn velocity or pressure; a NoteOff with non-finite velocity is preserved with velocity zero so the release is not lost.

Dense blocks and zero-frame calls

512 is the batch cache size, not an event limit for the host block. When a host sends more events, Vesty delivers successive batches using preallocated storage. A single host block can therefore produce multiple kernel calls.

When more than 512 events share one sample, earlier batches at that position can have zero audio frames. Always consume their events. Skip only audio generation; do not return before processing events. This rule also applies to native process_f64.

A host's zero-frame parameter flush is different: the adapter updates parameter state without invoking the audio kernel, including while processing is stopped.

The overflow path rescans host lists for each batch. Memory stays bounded, but CPU cost grows with event volume and batch count. Avoid generating unnecessary automation points and profile dense arrangements in your target DAWs. Removing the event cutoff does not remove the callback deadline.

Payload limits

PayloadCurrent contract
Events per cache batch512; remaining events continue in later batches
SysExUp to 256 bytes per event; inspect data_len and truncated
Note-expression textUp to 64 UTF-16 code units; inspect text_len

Do not interpret a truncated SysEx prefix as a complete message. The example rejects truncated messages before validating its experimental manufacturer ID and framing. Larger SysEx payloads require a separate API/storage design; batching does not enlarge an individual event's payload.

Verify your instrument

Test the same event sequence with different block sizes and compare the rendered samples. Include multiple channels, repeated notes, zero-velocity NoteOn, expression, automation, and a final NoteOff. Also test zero-frame event contexts and bursts above 512 events at one timestamp.

The repository's adapter regression suite covers 4,800 mixed events, 2,050 events at one timestamp, f32 and f64 paths, allocation checks, and overlapping audio buffers. Run it with:

Bash
cargo test -p vesty-vst3 --features vst3-bindingscargo test -p vesty-example-midi-synth

Local tests supplement real DAW release evidence. Verify note release, automation playback, transport changes, and editor open/close behavior in your target hosts.