Processing and parameters
PluginProcessor::process runs on the audio thread once per engine sub-block. Everything is
preallocated (process.rs: up to 256 parameter queues of 64 points, 1024 events); on overflow
data is dropped rather than allocating. Each block:
- Clears the parameter and event lists and fills the input parameter changes: edits from the
plug-in’s own editor (drained from a ring, at offset 0), then host values queued with
queue_param(automation; two points per block so the plug-in ramps), then MIDI. - Converts MIDI: note-on/off become note events; CCs, channel pressure and pitch bend become
parameter changes through the
IMidiMappingtable. Unmapped controllers, poly pressure and program changes are dropped. - Copies the host’s stereo buffers into input bus 0 (a mono bus gets L+R/2), runs
processwith aProcessContext(tempo, time signature, musical position, bar position, playing, recording, cycle), and copies output bus 0 back (mono duplicated). - Sends the last value of each output parameter queue to the controller side, which applies
it with
setParamNormalizedinidle(). Plug-in event output is ignored.
setProcessing(true) is called lazily on the first block. Offline renders also run in
kRealtime mode.
Extra output buses
Section titled “Extra output buses”plugin/multi_out.rs. All buses’ buffers are allocated at instantiation, so switching one needs
no allocation, but VST3 only allows it while the component is inactive, and setActive belongs
to the main thread. PluginController::set_active_outputs therefore keeps the audio thread out
without it ever waiting: each process and suspend takes OutGate with a compare-and-swap. The
controller asks the processor to park; it fades its outputs over 5 ms, calls setProcessing(0)
and leaves the gate parked, and process then outputs silence. If no block comes within 250 ms
(nothing is calling process), or the plug-in isn’t processing, the controller takes the gate
itself, waiting only for a block under way. Then setActive(0), activateBus, setActive(1), and
the next block starts the plug-in again and fades in. The rest of the graph plays on.
PluginProcessor::output_bus reads a bus’s last block; the engine’s dsp/multi_out.rs uses it.
Parameters and gestures
Section titled “Parameters and gestures”- Editor → processor:
performEditpushes(id, value)into the edits ring, which the next block drains. - Automation → processor: the engine calls
queue_param(see the engine graph). - Automation → editor display:
Session::idle→follow_editors→PluginController::set_param_valuewhile playing in Read, Touch or Latch. - Gestures:
beginEdit,performEditandendEditalso pushGesture::{Begin, Perform, End}into a ring.Session::idle(every 30 ms) turns them into automation touches and pass points (session/auto_write.rs), so moves in a plug-in’s own editor are written as automation. They are timestamped with the playhead whenidleruns, so their resolution is about 30 ms. PluginController::parameters()lists the automatable, visible, writable parameters for the automation parameter pop-up.
Undo of editor edits
Section titled “Undo of editor edits”session/vst_undo.rs turns the same gestures into undo steps, for plug-ins with an open editor
(Session::vst_watches). Nothing else makes these steps, so a plug-in with its editor closed
costs nothing.
- The state from before an edit can’t be read once the edit has begun, so each
Watchkeeps the last known state: read when the editor opens, after each edit, and when the host has changed the plug-in (PluginController::host_changes: automation following, a controller assignment,load_state) and that has been quiet for 300 ms, at most once a second and 1 % of the time. An open drag callsgetStatenot at all, however long it lasts. Invst_undo_check: 24 µs on TONE3000 without a model (4 KB), 365 µs on the 98 KB of LSP’s Multi-Sampler. - When a step is made: when the gesture ends, 60 ms later, once the edits have left the ring
for the processor, whose state
getStatereads; a bypassed plug-in gets them throughPluginProcessor::pass_edits. A gesture starting in those 60 ms joins the step, since a plug-in loading a preset by hand sends a run of them.checkpoint,undoandredomake pending steps first, keeping the order. The state read then is compared with the known one: no difference, no step. So a click that moves nothing doesn’t clear the redo stack. - Edits without a begin and end are a burst until they pause for 250 ms, and bursts on one
parameter merge through the undo key
vst-edit:<uid>:<param>. Since a plug-in may be moving a parameter itself,Trackergives them less rope: read-only parameters are ignored, and a parameter is ignored from then on if its burst runs 10 s without a pause, three of its bursts leave the state as it was, or the plug-in has made eight such steps in 30 s (a gesture on it still counts).restartComponent(kParamValuesChanged)is treated as a burst, labelled “Parameters”, and gets the same cap. - Not steps: what the host sets (
set_param_value,edit_param), and the same value coming back from the plug-in within 200 ms (host_echo). While the track writes automation during playback the move belongs to the pass’s step, and the known state just catches up. - Restoring is
load_statewith no parameter messages: the plug-in applies it as it does when loading, on the main thread while the audio thread runs (VST3 allows this). Gestures from the plug-in for the next 300 ms are its answer to that, and are dropped, also for automation write. Edits still in the ring would land on top, soflush_vst_editsfirst waits up to 50 ms for them.
Latency
Section titled “Latency”PluginController::latency() calls getLatencySamples on the main thread. restartComponent
stores its flags; idle() reports kLatencyChanged, and Session::idle then reruns
update_pdc. kParamValuesChanged goes to undo. Other restart flags are
ignored, and the component is not reactivated.
save_state reads component.getState and controller.getState; load_state calls
component.setState, controller.setComponentState and controller.setState. The session
stores the pair as base64 in PluginSlot::state and restores it straight after instantiation.