Repository navigation
Releases
Releases · emulette/plughost
v0.2.1
Added
RealtimeStatus::non_finite_outputscounts the callbacks that played the chain's output with
a NaN or infinite sample in it, which the device receives as is.
Fixed
- A realtime callback that began before
stop_realtimeorrecover_realtimeno longer submits
to the old generation after the next start, which sent the new generation into a resync:
stopping and recovering wait for a callback in progress and stop its generation's
submissions, asresync_realtimedoes.stop_realtime,recover_realtime, and dropping or
preparing again a chain with an endpoint now wait for that callback. - A realtime callback that panics no longer leaves the endpoint's call count unbalanced, after
which a resync either went on while a callback submitted or never returned. - A
Fallback::Dryendpoint called with more frames than the quantum plays the input its delay
line still holds, delayed, and silence before it; it read overwritten history and, in a debug
build, panicked in the callback. - The helper no longer ends with a control timeout when timing changes keep arriving while a
started realtime endpoint submits nothing, as when the device stopped or a resync is pending:
its worker takes them as they come. A CLAP plugin reporting a new tail on every tick filled
the queue in about 0.64 s. - Audio Unit editors, parameter lists, parameter edits and lookups no longer hold up the
processing thread: they call the unit without the lock it takes for each block. Opening an
editor while the realtime endpoint ran could make the worker miss. - A
restore_state, preset, factory preset orreprepare_slotthat changes the chain's latency
while the realtime endpoint runs succeeds when the resync it starts does not complete; the
endpoint plays its fallback withresyncset meanwhile. It returned the resync's error though
the slot was already replaced. - The events of an output the realtime callback missed are no longer lost: once the helper has
completed it, they go out at offset 0, before the output's own, with the next callback. A note
off in a missed output left its note on until the next generation. - A VST3 plugin's component and separate controller now get the messages they send each other
from any thread; only those sent on the thread that created the plugin went through. An editor
that asks its processor for levels from a display-link thread, as FabFilter Pro-C 3's does,
showed no meters. - The note offs a new realtime generation sends for the notes earlier outputs left on go out with
its first callback that returnsOk. A first callback with invalid input dropped them with its
error. RealtimeStatus::lateno longer counts a quantum the helper could not read, and goes on across
recover_realtimeinstead of starting again from 0.- A failure the helper reports in a slot the chain does not have counts as a protocol failure
without a slot. - On Windows, the helper's main thread handles the messages other threads send it while it waits
for its processing thread, so a plugin that sends it one from its processing no longer holds
it up until the control timeout.
v0.2.0
0.2.0 settles the public API: types later versions extend are non-exhaustive, states and plugin
identities have stable forms of their own, and events carry system exclusive data without an
allocation per message. Upgrading from 0.1:
- Rebuild and ship the helper with the application; the helper protocol is 34. Scan caches
written by 0.1 are read as empty and their bundles scanned again. - Store plugins as
PluginIdand states asPluginState::to_bytes(); the public types have no
serde form, and 0.2 cannot read states 0.1 serialized with it, so re-create those. Read a
state throughid(),component()and the other accessors. Build references with
PluginRef::vst3,PluginRef::claporPluginRef::audio_unit, readPluginInfo::id, and
readDiagnostic::pluginas anOption<PluginId>. - Spawn chains with
Chain::spawn(helper, &[plugin.into()], &SpawnOptions::default()), and
recover them fromChainSnapshot::slots.recover(&[])no longer means the initial states:
it fails withInputError::EmptyChain, so pass aChainSlotperchain.references(). - Pass events as
&Eventsand produce them into a reusedEvents::for_block();
Event::sysextakes a slice, and anEventborrows its system exclusive bytes from the
Eventsit was read from, so keep theEventsto keep the events. - Build routing, processing, render and preset types with their constructors instead of struct
literals, and add a wildcard arm to matches on the enums that are now non-exhaustive. CLAP
preset flags are aPresetFlags. - Read parameter values as
Option<f64>;Noneis a value the plugin does not give. - Expect
RenderError::NonFiniteOutputinstead ofnon_finite_samples, and
RenderStatus::TailLimitfor a tail that reached its maximum. - Match rejected render input as
Error::Input { error, .. }, which plughost now reports
instead ofError::Render(RenderError::Input(..)), and buffer mismatches as
InputError::AudioBuffers:Error::Buffers,RenderError::Events,InputChannelsand
InputLengthare removed. - Match
plughost_formats::Errorbykind()andinput_error(). Errors display only their own
context; walksource()(or usefailure()) to show their causes. - Wrap the plughost-formats loaders in
unsafewith the reason the plugin is trusted, and load a
CLAP module as aclap::Entry.HostedPlugin,BlockProcessorandSampleare sealed, so
implementrender::Processfor a processor of your own.preset::supportsissupported,
andvst3::Preset::controllerandDecodedPreset::controllerare anOption. - Stop using the buffers and checks plughost-core exported for its own crates
(DiagnosticBuffer,events_fit,validate_normalizedand the like) andsysex_bytes. - Write
let _ =where atake_*drain discards its batch.
Added
PluginIdnames a plugin class independently of where it is installed: itsPluginFormat
and aClassIdkept in canonical form, VST3 and Audio Unit IDs in upper case and CLAP IDs as
given, so IDs written in another case are one class. It supports serde, with format names
"vst3","audio-unit"and"clap", for storing with the work that uses the plugin.
Catalog::resolve(&id)finds the installed class as aPluginRef, from the highest priority
directory, andBundleScan::plugins()gives the references of one scanned bundle.- Constructors for the routing, processing, render and preset types an application builds, which
are now#[non_exhaustive]:RoutedChainConfig::new,SlotAudioConfig::new,
AudioConfig::new,AudioBusConfig::new,AudioInputRoute::new,SlotEventConfig::new,
EventInputRoute::new,EventConfig::new,ProcessConfig::new,HostIdentity::new,
ParameterChange::new,AutomationEvent::new,RenderInput::new,AutomationRamp::new,
TransportChange::new,TimeSignature::new,LoopRegion::new,BarPosition::new,
PresetDirectory::new,PresetFile::new,PresetDiscoveryTarget::newand
DiscoveredPreset::new, withwith_*methods for their optional parts (mode, named
configuration, events, event inputs, channel adaptation, render schedule) and
RenderSchedule::with_transport,with_automationandwith_ramps. ChainSlot(aPluginRefwith an optional state and native bypass) andSpawnOptions(host
identity and timeouts) describe whatChain::spawnloads, andChain::references()gives the
references a chain loaded.PresetFlagsnames the CLAP preset discovery flags.PresetDiscoveryTarget::validatechecks a target's provider ID and location, failing with the
newInputError::PresetDiscoveryTarget.plughost::discover_presetscalls it before starting
a helper, and the helper before loading the module, so invalid input never runs plugin code.plughost_formats::clap::Entryis a loaded CLAP module, asvst3::Moduleis for VST3:
Entry::load(bundle)loads it once, andclasses(),clap::Plugin::new,
clap::preset_providersandclap::discover_presetsuse it instead of loading the module
again on each call.PluginState::to_bytesandPluginState::from_bytesstore a state in plughost's own
versioned format, which later versions keep reading and which does not depend on the helper
protocol; 0.2 cannot read 0.1 states, which had only a serde form. It is
a container of tagged sections for the identity and the native payloads, with a VST3
controller state kept apart from its component and absent, not empty, when the controller
has none. Reading needs no plugin, writes a state back unchanged, and checks every
length againstMAX_STATE_BYTESbefore allocating; bytes it cannot read fail with the new
InputError::StateBytes.Eventsholds a block's or a render's events as fixed records, with the system exclusive
bytes of all of them in one area, so a system exclusive message no longer allocates on its own.
Events::for_block()reserves one block'sMAX_BLOCK_EVENTSevents and
MAX_BLOCK_SYSEX_BYTESbytes, within whichpushnever allocates and beyond which it fails
withInputError::EventCapacity, andEvents::try_for_block()reports a failed reservation
instead of aborting;Events::new()grows without that limit. It is built withpushor by
collectingEvents, read withiter(anEventsIter),getandlen, and compares equal
to a slice or array ofEvent. A clone of a block'sEventsreserves a block's room again.RenderStatus::TailLimitandRendered::statustell aTailPolicy::UntilSilencetail that
reachedmax_tail_secondsbefore the output stayed silent apart from one that ended in
silence.RenderProgress::statusfromrender_streamandRenderSession::finishreports it
too, where such a render reportedComplete;Renderedhad no status.
Changed
- Events are passed as
&Eventsand produced into&mut Eventsinstead of&[Event]and&mut Vec<Event>: inChain::process_audio_*,RealtimeEndpoint::process_audio_*,render,
render_with_schedule,RenderInput,Delivery, theProcesstrait,BlockProcessorand the
format plugins' processing calls, andRendered::eventsis anEvents.
plughost_core::sysex_bytesis removed; readEvents::sysex_bytes(). Write&Events::new()for
no events and&Events::from_iter([Event::note_on(0, 0, 60, 100)])for a literal list, and reuse
anEvents::for_block()forproduced.EventandEventDataareCopywith a lifetime:
EventData::SysExborrows its bytes, from theEventsit was read from or from the caller, so
Event::sysex(offset, &bytes)takes a slice and.clone()calls become copies. - A realtime endpoint delivers the output's system exclusive events into
produced, which it
dropped and counted before.RealtimeStatus::dropped_eventsnow counts only events beyond the
roomproducedhas reserved, so pass it anEvents::for_block(). System exclusive output an
Audio Unit sends over several calls is joined in room reserved when the unit is prepared,
instead of in a buffer allocated per message on the render thread. - The public types no longer implement serde's
SerializeandDeserialize, apart from
PluginId: persist states withPluginState::to_bytesand plugins byPluginId, and keep
routing, configurations and other settings in the application's own model. Their serde form
was the helper protocol's, which changes with every protocol version; the protocol now uses
definitions of its own. Chain::spawn(helper, slots, &options)takes aChainSlotper plugin, aPluginRefwith an
optional project state and native bypass, and aSpawnOptionswith the host identity and the
timeouts: writeChain::spawn(helper, &[plugin.into()], &SpawnOptions::default()), or
SpawnOptions::new(host, timeouts). Each plugin restores its state on the instance it loaded,
which loads it once instead of twice as arestore_stateafter spawning does, and then takes
its bypass.recoverandrecover_realtimetake the same slots, whichChainSnapshot::slots
holds in place ofstatesandbypass, andrecovernow applies the recorded bypass too;
recovering from states alone pairs each with its slot's plugin in aChainSlot.
recover(&[]), which recovered the initial states, fails withInputError::EmptyChain; pass
a slot perChain::references(), the new accessor for the references a chain loaded.
InputError::StateCountis removed, since a slot carries its own state.PluginState's fields are private and it has no serde form: storeto_bytes()instead of
serializing it.id()replacesformatandclass_id, andname(),vendor()and
version()the fields of those names. A restore checks only thePluginId, not the plugin
version.vst3::Preset::controllerandDecodedPreset::controllerare anOption,None
for a preset without aContchunk; an emptyContchunk is kept when a p...
v0.1.6
Added
Chain::cancellerreturns aChainCancellerwhosecancelterminates the chain's helper from
any thread, which ends a call the chain is blocked in, such as a plugin that does not return
from restoring state, loading a preset, opening its editor, preparing again or processing a
block. That call, or the next one if none is blocked, returns the newError::Cancelled
(FailureKind::Cancelled) with the slot the helper was calling, and later calls return
Error::Crashedas after a timeout.HelperMonitor::end_reasontells why a helper ended once its exit is observed: it crashed or
exited on its own, plughost terminated it after a timeout, a protocol error or a cancellation,
or its chain shut it down. The errors after the first one reportError::Crashedwhatever the
reason was.Rendered::non_finite_samplesandRenderProgress::non_finite_samplescount the NaN and
infinite samples a render delivered, over all channels and excluding the removed latency;
a render session reports them perpushandfinish. Rendering still neither rejects nor
replaces them, and they still count as sound forTailPolicy::UntilSilence, so a plugin that
keeps producing NaN renders up to the maximum tail.
Changed
- The load timeout bounds each plugin of a chain rather than the whole chain: loading a chain,
and preparing an already configured chain again, renew it after each plugin they load or
prepare, also inrecoverandrecover_realtime. A chain of instruments that each load
within the timeout no longer times out together. - A panic in the helper aborts it, so the application reports a crash of the slot the helper
was calling. A panic on its processing thread made the request it served time out without
naming a slot before. RenderSessionreuses its render buffers from onepushto the next instead of allocating
and zero-filling block buffers and event storage for every segment, and trims the latency
without copying each delivery's channel list. Output and progress are unchanged.- The internal helper protocol is now 31. Applications and helpers with different protocol
versions fail the handshake; rebuild and distribute the helper with the application.
Fixed
- A VST3 editor whose view reports a size with coordinates far apart no longer panics the
helper when it computes the window size. - A scan no longer forgets the bundles of a scanned directory that is missing, such as one on a
disconnected volume, so they are cache hits again when it returns instead of loading again,
and a bundle that crashed stays excluded. Bundles removed from a directory that exists are
still forgotten, except that an entry with failures keeps its count and exclusion until a
retry, and a link to a bundle or folder on a disconnected volume keeps its entries. A retry no
longer drops the entries of other removed bundles. - In a prepared chain,
restore_state, presets, factory presets andreprepare_slotfail with
FailureKind::Configurationand keep the active slot when the fresh instance has other active
audio buses or event ports than the slot was prepared with;reprepareroutes them. The slot
was replaced before, and processing went on routing by the replaced instance's native ports. - A
resetthat fails at one slot after earlier slots were reset leaves the chain to be prepared
again: blocks fail withFailureKind::NotPrepared. They went on with alignment delays that no
longer matched the reset slots. - A parameter edit or reset that took effect succeeds when the chain's timing cannot be read or
planned afterwards; the chain keeps its last latency and tail, and the diagnostics record the
failure. It failed with that error before, as if the edit had not been made. Transport::advancedno longer fails withInputError::Transportfor a beat a few ulps
below a bar line, where rounding placed the computed bar start after the beat; the beat now
stays in the earlier bar. A scheduled render or session segment whose blocks reached such a
beat failed partway through after the schedule had been accepted, and a session segment that
stopped at one, by cancellation or at the end of a silent tail, returned the error while the
session stayedActive.- A CLAP plugin's output event whose header gives another size than its type's is counted in
diagnostics as an event with no portable form. It was read as its type, past the end of an
event the plugin declared shorter, and became a note or a parameter notification. - VST3 and CLAP output system exclusive data is copied only once the size the plugin declares
fits what is left of the block's budget; a larger one fails the block as more output than a
block allows, before anything is read. It was copied at the declared size on the processing
thread first, so a size past the plugin's buffer crashed the helper. - The counts a CLAP plugin reports are bounded before they are followed. A plugin's first 65,536
parameters are read, with aPluginMisbehavingdiagnostic when it reports more;
audio_configurationsfails with the audio metadata error for more than 1,024 configurations
or more thanMAX_AUDIO_BUSESports in a direction of one, asaudio_busesdoes for the active
ports; and capabilities areSupport::Unknownwhere more note ports (beyond 65,535), audio ports
or parameters would have to be looked at. A plugin reporting billions made the helper allocate
without bound or run until the control timeout. - Parameter values and ranges that
parametersandparameter_detailsread follow one rule in
every format: a value outside its range is clamped into it, and one that is not finite, or
whose range is not, reads as 0. A CLAP parameter whose range is not finite has no default, andset_parameter
and automation of a CLAP or Audio Unit parameter with such a range fail with the format's
parameter result error (FailureKind::Host).parametersreported such values as NaN or
outside 0 to 1, and edits sent NaN to the plugin. - Text an Audio Unit gives (parameter, group, bus, MIDI output and preset names, parameter units,
value text and error descriptions) is converted with U+FFFD in place of what is not valid
UTF-16, such as an unpaired surrogate, as component names already were. Such text aborted the
helper in debug builds and was undefined behaviour in release builds. - A CLAP plugin that registers or unregisters a timer off the main thread is refused, with a
PluginMisbehavingdiagnostic. The host's timer list, which only the main thread may use, was
changed from that thread. - Host edits of a CLAP plugin's parameters stay queued until a block the plugin processes has
delivered them. A block the plugin failed took them, so the plugin kept the old values. - A CLAP plugin's output notes with IDs end with the note choke or note end that names them, as
with a note off. They stayed among the 4,096 notes the host follows, and once those were taken,
a later note's expressions naming it by ID alone counted as events with no portable form.
v0.1.5
Added
Error::ProtocolVersionreports a helper built with another plughost version than the
application, with both internal protocol versions, instead ofError::Handshake, which now
means only that the helper did not present the expected token. Scans record it as a protocol
failure with the same message.Scanner::hostsets the identity native inspection presents to plugins, which VST3 factories
receive as their host context. Scans presented the default plughost identity before, while a
chain presented the application's.
Changed
- The internal helper protocol is now 30. Applications and helpers with different protocol
versions fail the handshake; rebuild and distribute the helper with the application.
v0.1.4
Added
Chain::resync_realtimeresynchronizes a running realtime endpoint without stopping it: the
callbacks play the fallback while every plugin is reset as byreset, which ends the notes the
plugins hold and applies an announced latency, and the helper's worker goes on with a new
generation. It returns the endpoint's whole latency, asstart_realtimedoes, and changes
nothing on a stopped endpoint. Stopping the endpoint, resetting the chain and starting it again
still works.Chain::snapshotreturns aChainSnapshotwith each slot's project state and native bypass,
also while the endpoint runs.Chain::recover_realtime(&snapshot)recovers the endpoint in a
new helper: it prepares a chain from the snapshot, applies the bypass, and moves the same
endpoint to it while the audio callback keeps theRealtimeEndpointit has. The callback plays
its fallback during the recovery, and afterlatencypriming callbacks the new chain's output.
Switch to the returned chain and drop the old one. A failed recovery leaves the endpoint with
the old chain and retries nothing.recover_realtimeloads with the load timeout and prepares
with the control timeout.RealtimeStatus::stalledreports how long the helper's worker has gone without completing a
quantum the callback submitted, which grows while a plugin hangs in its processing; a crashed
helper shows throughhelper_monitor. Together they tell when to callrecover_realtime.Chain::reprepare_slotprepares one slot again on a fresh instance holding the active one's
project state, which drops a pending restart request, asrepreparedoes for the chain. It
uses the load timeout and works while the realtime endpoint runs.
Changed
restore_state,import_preset,load_discovered_presetandselect_factory_presetwork
while the realtime endpoint runs: the helper stages the fresh instance as it does offline and
the worker swaps it in between quanta, and the stream goes on. A replacement that changes the
chain's latency resynchronizes the endpoint right after, asresync_realtimedoes. Only
reset,prepare_audio,reprepare,realtime_endpoint, processing through the chain and
rendering still fail withError::RealtimeRunning; useresync_realtimeinstead ofreset,
andreprepare_slotinstead ofrepreparefor a restart request.- The first callback of every new generation (a start, a resync or a recovery) ends the notes
that earlier outputs delivered throughproducedand did not end, with note offs at offset 0;
the endpoint keeps track of up to 256 notes, and events that do not fit count as
dropped_events. Notes a replaced instance sent to later slots are still ended only by a
reset or resync. restore_state, presets and factory presets on a slot with a pending VST3 or CLAP restart
request now succeed, also offline, and drop the request with the replaced instance; they
failed withRestartRequiredfor that slot before.- The internal helper protocol is now 29. Applications and helpers with different protocol
versions fail the handshake; rebuild and distribute the helper with the application.
Fixed
- VST3
reset, the VST3 parameter flush beforeprepare_audioand Audio Unitresetalso run
with subnormal numbers flushed to zero, as 0.1.3 documented for every format. - Discovery reads a VST3 bundle's
moduleinfo.jsonin time and memory proportional to its size.
The JSON5 parser copied a string or identifier again for each escape sequence in it, so a 256 KB
file of escapes allocated 17 GB and took a third of a second; plughost now requires json5 1.3.3,
which fixes it.
v0.1.3
Changed
- Plugins process, reset and flush parameters with subnormal numbers flushed to zero, as inputs
and as results (MXCSR FTZ and DAZ on x86-64, FPCR.FZ on AArch64), in the helper and through the
direct formats API. Denormal arithmetic no longer slows plugins that do not protect themselves,
and output changes only for samples at subnormal level (about −758 dBFS). The calling thread's
mode is restored after each call; out-of-process AUv3 extensions keep their own. - VST3 state saves, tail reads and bus, port and capability queries, and Audio Unit state saves,
bypass, factory preset, bus and capability queries no longer hold the lock the processing thread
takes for each block, as both formats let the owning thread make them while the plugin
processes. A slow state save no longer holds up processing on another thread.
Added
- Isolated realtime processing.
Chain::realtime_endpointcreates aRealtimeEndpointfor the
prepared chain, which the application's audio callback owns: each callback of the configured
quantum hands its input to the helper through shared memory and plays the chain's output for
the quantum it submitted one to three callbacks before, without waiting, locking or allocating.
When that output is missing the callback plays silence or, for effects, the input delayed by the
whole latency (Fallback::Dry), ramping into and out of it over one quantum. Without misses the
output is the offline output,latency * quantumframes later.Chain::start_realtimeand
stop_realtimerun the helper's worker, andrealtime_statusreports misses, overflows,
callbacks of another size, failed quanta and late completions;last_completionsays how long
the helper took for the output a callback just played. After a resync, stop the endpoint,
resetthe chain and start it again. While the endpoint runs,restore_state, presets,
select_factory_preset,reset,prepare_audio,reprepareand processing through the chain
fail withError::RealtimeRunning; parameter edits, state saves and editors go on.
A VST3 or CLAP restart request while the endpoint runs is reported throughtake_changesand
the stream goes on with the plugin's current activation; prepare the chain again after stopping.
The helper's worker runs with the time-constraint policy in a workgroup interval of its own and
keeps App Nap away on macOS, and in the MMCSS "Pro Audio" task on Windows. - The
realtimeexample plays a chain through the default output device with a realtime
endpoint and recovers it after a resynchronization. It asks the device for callbacks of one
quantum, or of the device's nearest callback size, such as WASAPI's shared-mode period. The
realtimebenchmark calls an endpoint at the audio rate from a realtime thread and fails when
the helper completes a quantum late, the endpoint resynchronizes, callbacks at p99.9 take
longer than their limit, or a callback takes longer than its quantum. - The
realtime-auditfeature ofplughostmarks the realtime endpoint's callback for the
real-time audit, andplughost_core::audit::processing_allocationsreturns the allocations and
frees it counted. plughost_formats::BlockProcessor::defer_restartlets blocks go on while a VST3 or CLAP
restart request waits, instead of failing withFailureKind::RestartRequired.plughost_formats::clap::classes_once_loadedlists a CLAP module's classes and reports when
its entry has initialized, before the classes are read and the entry is deinitialized.
Fixed
- VST3
parameter_detailsandparameter_choicesno longer fail with a parameter group error
for plugins that leave the root unit out of their unit list, as the VST3 SDK examples do. The
group path ends below the implicit root. - VST3
set_bypassworks beforeprepare_audio, likebypass,set_parameteron the bypass
parameter and the CLAP and Audio Unit bypass, instead of failing withNotPrepared. audio_configurationsreturns an empty list for CLAP plugins without theaudio-ports-config
extension, as for VST3 and Audio Units, instead of failing with an unsupported-audio error.resetsucceeds for CLAP plugins that are not prepared yet, as for VST3 and Audio Units, so
Chain::resetbeforeprepare_audiono longer fails for chains with a CLAP slot.- A
Timeoutsfield too long to end at a representable instant, such asDuration::MAX, waits
without a deadline instead of panicking in the application. - Preparing a prepared chain again, including
reprepare()after a CLAP restart request, no
longer fails for plugins that cannot save their state, such as CLAP plugins without thestate
extension. Such a slot is prepared again in place after the others are staged; a failure from
then on leaves the chain unprepared instead of preserving it. - A failed reconfiguration keeps the diagnostics of every staged instance it discards, not
only of the instance that failed, and a crash or hang while a reconfiguration releases replaced
or discarded instances names their slot. - Restoring state, importing or loading a preset and selecting a factory preset keep the slot's
native bypass unless the state or preset sets it. Audio Unit bypass, which unit state does not
hold, was lost on restore, and factory preset selection reset bypass in every format. - A
TailPolicy::UntilSilencehold close to the largest representable length no longer
overflows and panics while the tail is scanned in large blocks. RenderError::SessionClosedis classified asFailureKind::Closedinstead ofNotPrepared:
the session finished, was cancelled or failed, and preparing the processor again does not
reopen it.- Failures of
preset_providersanddiscover_presetsname no slot; they reported slot 0 of a
chain that does not exist. - A bundle that changed since it failed reports no failures and is not shown as excluded when its
scan is interrupted or its helper cannot start; the stale cache entry still counted. - The VST3 host reports
INoteExpressionControlleras supported through
IPlugInterfaceSupport, since it reads note expressions from it; plugins that check first no
longer conclude that the host ignores them. - Audio Unit class IDs load whatever the case of their hexadecimal digits, as
ScanPolicyalready
matches them; a lowercase ID failed to instantiate. - Audio Unit
parameter_to_plainandparameter_textround discrete parameters to the whole
value thatset_parameterand automation select, instead of converting a fraction that units
truncate to a different choice. - A VST3 module whose entry succeeded but that provides no factory is exited with
bundleExit
orExitDllbefore it is released, as the VST3 SDK hosts do. - The
RenderError::Eventsmessage no longer calls render events MIDI messages on existing ports;
ports a processor lacks failProcess::validate_eventsinstead. - Scan helpers report
ScanStage::ModuleLoadedas soon as a module has loaded, as documented,
instead of after its classes were read and it was unloaded, so loading and the work after it
each get the stall timeout instead of sharing one. - Direct CLAP use:
HostedPlugin::prepareafter aprepare_audiothat left ports out no longer
fails with an unsupported-audio error for plugins without audio port activation, whose ports
stayed active. - Direct VST3 use: preparing a prepared plugin again holds the values block automation left,
instead of the controller's older values, until the next block's first point. - On macOS, a window size that a resizable editor refuses returns the window to the size the editor
last accepted, as on Windows, instead of keeping the refused size and offering it again on every
tick. - On Windows, an editor smaller or larger than the system lets a window be keeps the window at the
size the system gave it. The window used to be resized on every tick, and a resizable editor
was offered that size each time. - CLAP audio and note ports are scanned only while the plugin is inactive, as CLAP requires. An
active plugin answers bus, event port and capability queries, including the helper's own after
each preparation, with the ports it was activated with.
v0.1.2
Added
- Complete public API rustdoc for the application, core, and format crates, including units,
input limits, errors, and links to the integration guide. HostedPlugin::poll_timingoffers an opportunistic owning-thread timing read. Its default
returnsNone, preserving existing trait implementations;timingremains the fresh read.- The main crate re-exports
ParameterFlags,Sample, the renderProcesstrait, and public
audio, text, preset and event/diagnostic buffer limits, avoiding a separate core dependency. Debugfor chains, scanners, helper monitors, scan controls, render sessions and native
plugins, modules and processors. Formatting does not invoke plugin code or acquire an
engine lock, and render sessions do not require the caller's processor to implementDebug.render::Process::validate_eventslets processors reject unavailable event ports across the
whole render before processing begins; its default preserves existing implementations.InputError::PathEncodingidentifies bundle and preset paths that the helper's Unicode path
protocol cannot represent. Chain loading and preset requests reject them before sending them to
a helper; scans report them individually without caching them or interrupting other bundles.
Changed
- Idle timing polls skip a busy processing engine and send only changed observations to the
worker. CLAP reads tail after change notifications; VST3 and AU poll at a 100 ms cadence,
retrying skipped reads. Explicit timing reads, control edits and render startup stay fresh. - Native automation resolves and validates block edits once. CLAP and AU reuse prepared native
point storage; VST3 resolves parameter queue indices once per assembly and holds queue locks
while filling the block, releasing them before plugin callbacks. Control-only flushes and
invalid-input rejection preserve pending edits, queue ordering and exact sample output. - Unchanged scan cache entries are reused before reading executable headers. Parallel native
scans query the Audio Unit registry within the same worker limit, alongside bundles; cache
write failures also terminate an active registry helper. - Render preflight sends each distinct automation target to the helper once, without fetching
every slot's parameter list. The helper indexes metadata for both preflight and block checks;
invalid targets anywhere in the render still fail before its first DSP call. - Scan helpers initialize AppKit only when needed, and helper startup waits on socket/pipe
readiness instead of sleeping between connection attempts. Interactive retries still
initialize and activate the application before loading the plugin. - Parameter edits, text edits and reset return the updated chain timing in their own reply,
removing the follow-up timing request while preserving native parameter notifications. - State and preset requests borrow the caller's payload until encoding, avoiding a full input
copy. Encoding reserves each frame's exact size, so a large payload is held once. Completed or
rejected writes retain at most 64 KiB of serialization scratch; closing the transport releases
it after any pending Windows write has been cancelled and completed. - IPC readers reuse initialized receive storage and retain bytes from subsequent frames. Large
payloads accumulate received slices without zero-filling the unread body. Partial frames and
deadlines remain resumable;MessageReadermust be kept for the whole connection. - AU completion and notification waits sleep when a non-main thread's run loop has no sources,
instead of spinning. Completion callbacks still wake the wait and run-loop callbacks run. - AU creation looks up its component description directly, avoiding enumeration and name
queries for every unrelated registered component. - VST3 converts each block's input events once into prepared scratch storage and skips copying
held notes for empty input, preserving pre-DSP event expansion checks and automation order. - CLAP output note-offs with an ID remove their matching note directly, preserving explicit
address constraints. CLAP and VST3 skip wildcard note-off scans when no notes are tracked. - Helper channel views are allocated for the negotiated channel counts during preparation
and reused for processing and publication, avoiding repeated 512-channel stack arrays. - Sidechain latency alignment uses bulk ring-buffer transfers while retaining lazy history
initialization, exact samples and existing preparation memory limits. - Offline ramp scheduling visits active ramps and indexes interval validation, preserving
every per-sample point and equal-offset input order while avoiding scans of all ramps. ErrorandPresetSearchErrordisplay their context whilesource()retains the cause,
avoiding duplicate messages when formatting an error chain. Format the source chain to
include the full diagnostic; portable scan failures still retain it.- The internal helper protocol is now 27. Applications and helpers with different protocol
versions fail the handshake; rebuild and distribute the helper with the application.
Fixed
- A finished scan or retry releases the scan cache lock even while a process being started on
another thread still shares the lock file, so the next scan or retry no longer fails with
WouldBlock. - Malformed Unicode in an AU binary preset name returns a state error instead of aborting
during conversion from Foundation strings. - Excessively nested VST3
moduleinfo.jsonmetadata is ignored before recursive parsing can
exhaust the application stack during discovery. - AU parameters marked
NonRealTimeremain editable but reject block automation. - AU input rejects a system exclusive message longer than the 65,535 bytes an AU MIDI event
holds withInputError::EventCapacitybefore scheduling the block, instead of truncating its
length. - Helper OS event processing and editor updates run on a 10 ms deadline even while control
requests keep arriving, so a busy caller does not starve native timers and UI events. - AU Cocoa editor metadata with a null bundle or no non-null class name is treated like other
incomplete Cocoa view metadata, without constructing an invalid retained pointer or indexing an
empty class list. - AU preparation preserves parameter, editor and bypass edits made after an unprepared state
restore or factory preset selection when the unit took that state, and reads timing after
restoring it. A unit whose state did not change still receives the restored state again after
allocation. - Scan caches survive library upgrades when their schema and architecture are unchanged.
Helper startup, handshake and pre-request failures preserve cached plugin failure counts. - Plugin discovery follows each directory only once, preventing repeated scans through
directory aliases or cyclic symlinks while retaining the caller's directory priority. - VST3 inactive parameter flushes receive the same initialized process context as prepared
flushes, and their output parameter changes reach the controller. - VST3 MIDI assignment queries run outside the engine lock, allowing processing to continue
while the controller responds. - The VST3 host attribute list accepts empty binary values with null data and rejects null
IDs, strings, nonempty binary data and output pointers without dereferencing them. - Timing queries deliver pending CLAP and VST3 host edits before reading the tail, so
Chain::tail()and reported-tail renders use parameter edits immediately. - Failed CLAP processing starts preserve queued host edits for retry. MPE-only note ports
advertise note input and output support consistently with their event ports. - A CLAP block consumes its pending flush request, avoiding an extra audio-thread flush on
the next idle tick. - Event routes that exceed the first slot's input budget are rejected with
InputError::EventCapacitybefore processing, allowing a retry without reset. - Helper output events are validated for frame offsets, ordering, ports, note values and total
SysEx bytes before caller audio or events change. Invalid slot reports and loaded plugin
counts also terminate the helper as protocol errors; later requests reportCrashed. - Every unexpected control response terminates the helper. Losing it during any request
invalidates shared processing state, so later processing and rendering reportNotPrepared. - Repreparation uses the load timeout for reloading plugins; reset uses the control timeout.
The timeout documentation now includes editor opening and distinguishes initial preparation. ChainisSend + Syncon Windows as on macOS, including while a timed-out overlapped
pipe write is pending.- A disconnected helper socket does not raise SIGPIPE in a C or Swift embedding process
that retains the signal's default action. - Rendering an unprepared chain reports
NotPreparedbefore checking its input buffers, for
both sample precisions and continuous render sessions. - Invalid event ports anywhere in a render are rejected before DSP state advances, including
segments submitted throughRenderSession. - Automation ramps reach their exact endpoints and stay within their endpoint range despite
floating-point rounding, so fades to zero or one do not fail during rendering.
v0.1.1
Added
plughost_formats::AudioThreadandHostedPlugin::set_audio_thread: an owner that processes
a plugin on a thread of its own makes the plugin's audio-thread calls between blocks there too
(delivering edits to a prepared VST3 processor with a process call without audio, and CLAP
params.flushwhile active,resetandstop_processing). Without one they run on the
calling thread, as before.- The
realtime-auditfeature ofplughost-helperbuilds a helper that audits its processing
thread:scripts/build-helper.sh --auditbuilds it, and the benchmark README describes the
audit. It is not for shipping. InputError::Eventsfor events that are not valid, not in offset order or not inside the
block.
Changed
- The helper processes blocks on a thread of its own instead of the thread that reads the
connection, and that thread owns the chain's processing state; the main thread changes it
between blocks. The helper makes the audio-thread calls a plugin needs between blocks on that
thread instead of its main thread, so a CLAP plugin's thread check sees each call on the
thread CLAP assigns it. - The documentation names macOS 15 as the oldest supported macOS, the oldest one tested.
Fixed
set_parameter,set_parameter_textandresetsucceed while a plugin waits to be
prepared again; they used to apply the change and still fail withRestartRequired. A
RestartRequiredfailure of a block names the slot that asked to be prepared again.- Events that are not valid, in offset order or inside the block fail with
InputError::Eventsinstead ofError::Buffers, in a chain and in the plugins of
plughost-formats. Program change and channel pressure ignore their third byte as
documented; a third byte above 127 used to make them invalid, andto_midi()now gives it
as 0. - The events a slot receives from the previous slot share the block's budget with that slot's
automation. A block that exceeds it, or that a later slot cannot take, stops processing until
reset(), as an output overflow does; the plugin used to refuse it as invalid input while
the earlier slots had already processed it. take_changeswaits for the helper as long as other control requests (Timeouts::control)
instead of the load timeout.- A native scan or retry drops the cache entries of bundles confirmed to be gone from disk,
including when the last bundle was removed. Entries that cannot be checked because of an
I/O error are preserved. Removed bundles used to stay in every saved result. - The development helper app built by
scripts/build-helper.shreports the workspace version;
it reported 0.0.5. - The shared memory between an application and its helper is anonymous shared memory of a
fixed size: a POSIX shared memory object on macOS and a section backed by the paging file on
Windows. It used to be a temporary file, which a process that truncated it could make the
other one crash withSIGBUSwhen it read the mapping. - A VST3 plugin's request for a new bus configuration (
kIoChanged,kReloadComponent) made
beforeresetstays pending after it, so processing keeps failing withRestartRequired
until the plugin is prepared again.resetused to clear it and process on the old buses. - A VST3 block whose note offs and expressions without an ID, sent to every note on their key,
are more native events than one block holds is refused withInputError::EventCapacity
before the plugin processes it. The events beyond that used to be dropped, which could leave
notes sounding. The helper preserves this typed input error for the first chain slot, which
can be retried without a reset; a later slot's overflow still requires a reset. - Rendering a VST3 plugin hosted directly with
plughost-formatsfails with
RenderError::LatencyNotAppliedwhile a latency the plugin announced is not applied yet, as a
chain does. - An Audio Unit's MIDI output keeps a system exclusive message that real-time messages
interrupt, or that arrives over several calls; the rest of the output used to be dropped.
Only complete messages are delivered. An unfinished message that cannot fit the byte budget
with its terminator fails the block immediately, including when real-time bytes follow it. plughost-formatsbuilds without unused audio-thread code warnings when VST3 and CLAP are
disabled, including AU-only builds.
v0.1.0
Added
- Notes with IDs and per-note expression.
EventData::NoteOnandEventData::NoteOffcarry a
Note(channel, key, velocity in 0..=1, and an optional ID that tells overlapping notes on
one key apart), andEventData::ExpressionaNoteExpressionof the volume, pan, tuning,
vibrato, expression, brightness or pressure of a note. CLAP plugins receive and send them as
CLAP note and note expression events, and VST3 plugins as note events with their IDs, note
expression values and poly pressure. Ports and formats that take only MIDI receive the MIDI
1.0 form of notes and pressure (EventData::to_midi); other expressions do not reach them. - Plugin output in a format's note events arrives as notes and expressions, at the next slot and
at the application. It used to be counted in diagnostics as having no MIDI 1.0 form. Process::begin_renderruns once as a render starts, before the latency and tail are read;
its default does nothing.plughostre-exportsRenderError, so applications matchError::Renderwithout depending on
plughost-core.EventPortInfo::note_expressionandEventPortInfo::mpetell whether notes keep their IDs
and expressions arrive at a port (the CLAP dialect, or a VST3 controller's
INoteExpressionController) and whether it takes MPE (CLAP's MIDI dialect with MPE, or an
Audio Unit'ssupportsMPE).
Changed
MidiEventis nowEvent, andMidiDataisEventData.MidiData::Channelis
EventData::MidiandMidiEvent::channelisEvent::midi;note_on,note_off,
control_change,sysexandon_portstill build MIDI 1.0 events.Eventis no longer
Eq, andbytesandbyte_countare gone: match onEventData, or useto_midifor
a channel message's bytes. Move an event withat(offset)instead of struct update syntax.- VST3 plugins' notes and poly pressure arrive as
EventData::NoteOn,NoteOffand pressure
expressions instead of MIDI.to_midigives the MIDI 1.0 messages that used to arrive. - MIDI input reaches plugins as before, and MIDI poly pressure now also reaches CLAP ports that
take no MIDI, as pressure. MPE is MIDI 1.0 and is delivered as MIDI; it is not converted into
notes with IDs or note expressions. - A chain's latency holds until it is prepared again or reset. A latency a VST3 plugin announces
withkLatencyChanged, or an Audio Unit's changed latency, no longer stops processing or a
render: the plugin keeps the latency it was activated with, andtake_changesreports
PluginTiming::latency_changeduntilreprepareorresetapplies it. Many plugins delay by
the new latency at once, so a render does not start while a change is pending: it fails with
the newRenderError::LatencyNotApplied. Renders used to fail with
RenderError::LatencyChangedwhen the read latency changed. - The helper reads VST3 latency and tail, which VST3 assigns to the UI thread, on its main thread
instead of after every block on the processing thread; CLAP tails are read there too, and after
a block in which the plugin reported a change.PluginTiminghas the new field
latency_changed. TailPolicy::Reportedplans the tail reported when the render starts, and a tail reported
later no longer fails the render.RenderError::TailChangedis gone.- Types that later versions extend are
#[non_exhaustive], so additions do not break
applications again. Matches onLayout,PluginFormat,PluginKind,TailPolicy,
RenderStatus,StatePurpose,PresetMetadata,FactoryPresetId,ParameterEventand
DiagnosticSeverityneed a wildcard arm. BuildRenderOptions,Transport,BlockContext,
Timeouts,PluginInfo,EventPortInfo,AudioBusInfo,Capabilities,
CapabilityReport,PluginTiming,ParameterInfo,ParameterFlags,ParameterDetails,
FactoryPresetandPresetInfowith their newnewconstructors (ordefault) and then
set their public fields, instead of struct literals: for example
RenderOptions::new(TailPolicy::Reported, 0.0),Transport::new(position, true)with
transport.tempo = Some(120.0),BlockContext::new(frames).with_transport(transport), and
let mut timeouts = Timeouts::default(); timeouts.process = duration;.Rendered,
RenderProgressandDeliveryare read only. - In
plughost-formats,HostedPlugin::latencyandtailareHostedPlugin::timing, which
reads the timing on the plugin's thread, andBlockProcessor::latencyandtailare
BlockProcessor::timing, which returns what was last read there without calling the plugin.
v0.0.5
Added
- Layouts for LCR, quad, 5.0, 7.0, 5.1.2, 5.1.4, 7.1.2, 7.1.4 and 9.1.6, and first- to
fourth-order ambisonics (ACN order, SN3D normalization). Each has one channel order, listed on
itsLayoutvariant;Layout::ALLlists every layout. - Audio Unit buses take these layouts with the system's channel layout tags. The host maps the
channels of tags that list speakers in another order, such as the Atmos 7.1.4 tag. - CLAP plugins with configurable audio ports are asked for the requested layouts when they are
prepared without a selected port configuration, and ambisonic CLAP ports report their layout. CLAP has no wide speakers, so 9.1.6 is
refused for CLAP plugins.
Changed
Layouthas new variants, so exhaustive matches on it need new arms. Applications and helpers
must use the same plughost version, as before.