Repository navigation
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 preset is written
again, where it was dropped. An empty VST3 controller state is still not passed to the
controller when restoring.- An Audio Unit state over
MAX_STATE_BYTESfails to save, as the other formats' states do. PluginRefholds aPluginIdand the bundle, both private: build it with
PluginRef::vst3(bundle, class_id),PluginRef::clap(bundle, class_id)or
PluginRef::audio_unit(class_id)and read it withid()andbundle(), so a VST3 or CLAP
reference without a bundle can no longer be made; the error for one is removed.
PluginInfo::formatandPluginInfo::class_idarePluginInfo::id: read
info.id.format()andinfo.id.class_id().as_str(), and build an ID read from storage with
PluginId::new(format, class_id).PluginInfo::newtakes aPluginId, and
Diagnostic::pluginis anOption<PluginId>.- Class IDs compare in their canonical form everywhere. A state or preset whose VST3 or Audio
Unit class ID differs from the plugin's only in case restores (an Audio Unit state failed with
FailureKind::StateMismatch). - The scan cache schema is 3: caches written by 0.1 are read as empty and their bundles
inspected again. - The internal helper protocol is now 34. Applications and helpers with different protocol
versions fail the handshake; rebuild and distribute the helper with the application. plughost_formats::Erroris one opaque struct for every format instead of an enum with a variant
per format, andVst3Error,ClapErrorandAuErrorare no longer public; the format modules'
functions, such asvst3::Module::loadandclap::Entry::load, returnplughost_formats::Error.
Matchkind()for theFailureKindandinput_error()for rejected caller input instead of
variants;format()names the format whose host failed or that this build does not support (none
for rejected caller input),native_code()gives the VST3tresultor the Audio UnitOSStatus
(orNSErrorcode), andis_output_event_overflow()andfailure()remain. It is no longer
CloneorPartialEq; comparekind().- Errors keep their causes. A plughost-formats error and a
RenderErrordisplay only their own
context, andsource()gives the input or render error, the module loader's error (VST3 on
Windows, CLAP), the CLAP instance or GUI error, the I/O error of a CLAP preset location, or the
error an Audio Unit returned behind it,
which were dropped or turned into text before.plughost_formats::Error::failure()puts the
whole chain in its message. - An Audio Unit class ID that no registered unit has fails with
FailureKind::NotFound, as a
VST3 or CLAP class that a module does not have does, instead ofFailureKind::Load. - Each input contract has one error. Render events that are not valid, not in offset order or not
before the end of the input areRenderError::Input(InputError::Events);RenderError::Eventsis
removed. plughost reports input that a render rejects asError::Inputwith no slot, never as
Error::Render. Audio buffers of the wrong channel count or length, or a block longer than the
prepared maximum, areInputError::AudioBuffersin every crate and in rendering:
Error::Buffers,RenderError::InputChannelsandRenderError::InputLengthare removed, so
matchError::Input { error: InputError::AudioBuffers, .. }or
RenderError::Input(InputError::AudioBuffers). Buffers of another precision than the prepared one
areInputError::AudioSampleFormatin the in-process VST3 and CLAPprocesscalls too, where
they were a VST3 sample format mismatch and a CLAP unsupported sample format. Parameter changes
that the in-processprocesscalls receive out of offset order, outside the block or outside
0..=1fail withInputError::Automation, as automation does in a chain, rather than as a buffer
error (or, for a VST3 value,InputError::ParameterValue). plughost_formats::preset::supports(format)issupported(format), which returns the
unsupported error instead offalse.- The types an application builds are
#[non_exhaustive], so later versions can add fields:
buildRoutedChainConfig,SlotAudioConfig,AudioBusConfig,AudioInputRoute,
SlotEventConfig,EventInputRoute,AudioConfig,EventConfig,HostIdentity,
ParameterChange,AutomationEvent,ProcessConfig,RenderInput,RenderSchedule,
AutomationRamp,TransportChange,TimeSignature,LoopRegion,BarPosition,
PresetDirectory,PresetFile,PresetDiscoveryTargetandDiscoveredPresetwith their
constructors, or start frommain_bus_configordefault(), and set public fields instead of
writing struct literals. The output-only typesChangeBatch,SlotChange,Catalog,
BundleScan,Failure,Diagnostic,DiagnosticBatch,ParameterGroup,ParameterChoice,
ParameterChoicePage,ParameterEventBatch,AudioConfiguration,BypassState,
DecodedPreset,vst3::Presetand the preset discovery results are#[non_exhaustive]too,
as are the enumsMessage,AudioBusRole,AudioDirection,AudioSource,EventSource,
ChannelAdaptation,Support,SampleFormat,ProcessMode,Tail,SessionState,
PresetLocation,EditorRequestandEditorView, which need a wildcard arm in amatch. - plughost-core no longer exports from its root the buffers and checks the plughost crates
share among themselves:DiagnosticBuffer,ChangeBuffer,ParameterEventBuffer,
events_fit,validate_event_budget,changes_fit,validate_normalized,
validate_parameter_textandvalidate_choice_pageare internal, andvalidate_event_count
is no longer public. The methodsRoutedChainConfig::slot_config,SlotEventConfig::native
andParameterInfo::validate_edit, which only those crates call, are internal too. - The plughost-formats functions that load a plugin's code into the calling process are
unsafe fn, since that code can do anything the process can and nothing checks it:
plughost_formats::load,vst3::Module::loadandload_with_host,au::Plugin::newand
clap::Entry::load. The functions that use a loaded plugin stay safe, as does all of the
plughostcrate, which loads plugins in its helper processes.clap::Plugin::new,
clap::preset_providersandclap::discover_presetstake aclap::Entryinstead of a bundle
path,clap::classes(bundle)isEntry::classes, andclap::classes_once_loadedis removed:
load theEntry, then read its classes. - Public constructors and builders that return their value (
new,with_*, theEvent,
PluginRefandScannerbuilders,Event::atandon_port),RenderSession,
DiagnosticBatch,ParameterEventBatch,ChangeBatchandHostedPlugin::take_editor_request
are#[must_use]: discarding one, as atake_*drain does, is a warning, so writelet _ =
where a drain is intended. HostedPlugin,BlockProcessorandSampleare sealed: the format plugins and processors
andf32andf64implement them, and other crates cannot.render::Processand
AudioThreadremain for applications to implement. WithSamplesealed, processing no longer
compares type IDs to refuse a sample type claiming another precision.
HostedPlugin::set_editor_scaleis a default method on every platform, returningNone
outside Windows, where it was only declared on Windows.- CLAP preset discovery flags are a
PresetFlagsinstead of au32
(PresetLocationInfo::flags,PresetSoundpack::flags,DiscoveredPreset::flags); read the
bits withbits(). Chain::parametersandHostedPlugin::parametersgive each value as anOption<f64>, and
vst3::Plugin::parameter_valuereturns one: a value the plugin does not give, that is not
finite, or whose range is not, isNonewhere it read as 0, and so is a VST3 ID the plugin
does not have. A CLAPget_valuethat fails and anAudioUnitGetParameterthat a v2 unit
refuses are no longer read as 0 or, for the Audio Unit, as theAUParameter's value. A CLAP
or VST3 bypass whose value is unavailable or not finite reads as unknown rather than off, so
recovery and slot replacement no longer switch such a bypass off. Values a little outside
their range are still clamped into it.- A NaN or infinite output sample fails a render with
RenderError::NonFiniteOutput { frame },
the first output frame holding one, whatever the tail policy. Such samples were delivered
and counted inRendered::non_finite_samplesandRenderProgress::non_finite_samples, which
are removed, and as they are never silent they kept anUntilSilencetail going to its
maximum.
Fixed
- An Audio Unit v2 parameter edit that
AudioUnitSetParameterrefuses fails with
FailureKind::Stateand theOSStatusasnative_code(), and is neither kept as the host's
edit nor announced to the unit's parameter listeners. It succeeded before, whatever the status. - An Audio Unit that renders into other buffers than it was given fails the block with
FailureKind::Processing; it was reported as invalid caller input, after the unit had
processed the block.