Skip to content

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 24 Oct 09:47
· 13 commits to main since this release

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 PluginId and states as PluginState::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 through id(), component() and the other accessors. Build references with
    PluginRef::vst3, PluginRef::clap or PluginRef::audio_unit, read PluginInfo::id, and
    read Diagnostic::plugin as an Option<PluginId>.
  • Spawn chains with Chain::spawn(helper, &[plugin.into()], &SpawnOptions::default()), and
    recover them from ChainSnapshot::slots. recover(&[]) no longer means the initial states:
    it fails with InputError::EmptyChain, so pass a ChainSlot per chain.references().
  • Pass events as &Events and produce them into a reused Events::for_block();
    Event::sysex takes a slice, and an Event borrows its system exclusive bytes from the
    Events it was read from, so keep the Events to 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 a PresetFlags.
  • Read parameter values as Option<f64>; None is a value the plugin does not give.
  • Expect RenderError::NonFiniteOutput instead of non_finite_samples, and
    RenderStatus::TailLimit for a tail that reached its maximum.
  • Match rejected render input as Error::Input { error, .. }, which plughost now reports
    instead of Error::Render(RenderError::Input(..)), and buffer mismatches as
    InputError::AudioBuffers: Error::Buffers, RenderError::Events, InputChannels and
    InputLength are removed.
  • Match plughost_formats::Error by kind() and input_error(). Errors display only their own
    context; walk source() (or use failure()) to show their causes.
  • Wrap the plughost-formats loaders in unsafe with the reason the plugin is trusted, and load a
    CLAP module as a clap::Entry. HostedPlugin, BlockProcessor and Sample are sealed, so
    implement render::Process for a processor of your own. preset::supports is supported,
    and vst3::Preset::controller and DecodedPreset::controller are an Option.
  • Stop using the buffers and checks plughost-core exported for its own crates
    (DiagnosticBuffer, events_fit, validate_normalized and the like) and sysex_bytes.
  • Write let _ = where a take_* drain discards its batch.

Added

  • PluginId names a plugin class independently of where it is installed: its PluginFormat
    and a ClassId kept 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 a PluginRef, from the highest priority
    directory, and BundleScan::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::new and
    DiscoveredPreset::new, with with_* methods for their optional parts (mode, named
    configuration, events, event inputs, channel adaptation, render schedule) and
    RenderSchedule::with_transport, with_automation and with_ramps.
  • ChainSlot (a PluginRef with an optional state and native bypass) and SpawnOptions (host
    identity and timeouts) describe what Chain::spawn loads, and Chain::references() gives the
    references a chain loaded.
  • PresetFlags names the CLAP preset discovery flags.
  • PresetDiscoveryTarget::validate checks a target's provider ID and location, failing with the
    new InputError::PresetDiscoveryTarget. plughost::discover_presets calls it before starting
    a helper, and the helper before loading the module, so invalid input never runs plugin code.
  • plughost_formats::clap::Entry is a loaded CLAP module, as vst3::Module is for VST3:
    Entry::load(bundle) loads it once, and classes(), clap::Plugin::new,
    clap::preset_providers and clap::discover_presets use it instead of loading the module
    again on each call.
  • PluginState::to_bytes and PluginState::from_bytes store 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 against MAX_STATE_BYTES before allocating; bytes it cannot read fail with the new
    InputError::StateBytes.
  • Events holds 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's MAX_BLOCK_EVENTS events and
    MAX_BLOCK_SYSEX_BYTES bytes, within which push never allocates and beyond which it fails
    with InputError::EventCapacity, and Events::try_for_block() reports a failed reservation
    instead of aborting; Events::new() grows without that limit. It is built with push or by
    collecting Events, read with iter (an EventsIter), get and len, and compares equal
    to a slice or array of Event. A clone of a block's Events reserves a block's room again.
  • RenderStatus::TailLimit and Rendered::status tell a TailPolicy::UntilSilence tail that
    reached max_tail_seconds before the output stayed silent apart from one that ended in
    silence. RenderProgress::status from render_stream and RenderSession::finish reports it
    too, where such a render reported Complete; Rendered had no status.

Changed

  • Events are passed as &Events and produced into &mut Events instead of &[Event] and &mut Vec<Event>: in Chain::process_audio_*, RealtimeEndpoint::process_audio_*, render,
    render_with_schedule, RenderInput, Delivery, the Process trait, BlockProcessor and the
    format plugins' processing calls, and Rendered::events is an Events.
    plughost_core::sysex_bytes is removed; read Events::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
    an Events::for_block() for produced. Event and EventData are Copy with a lifetime:
    EventData::SysEx borrows its bytes, from the Events it 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_events now counts only events beyond the
    room produced has reserved, so pass it an Events::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 Serialize and Deserialize, apart from
    PluginId: persist states with PluginState::to_bytes and plugins by PluginId, 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 a ChainSlot per plugin, a PluginRef with an
    optional project state and native bypass, and a SpawnOptions with the host identity and the
    timeouts: write Chain::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 a restore_state after spawning does, and then takes
    its bypass. recover and recover_realtime take the same slots, which ChainSnapshot::slots
    holds in place of states and bypass, and recover now applies the recorded bypass too;
    recovering from states alone pairs each with its slot's plugin in a ChainSlot.
    recover(&[]), which recovered the initial states, fails with InputError::EmptyChain; pass
    a slot per Chain::references(), the new accessor for the references a chain loaded.
    InputError::StateCount is removed, since a slot carries its own state.
  • PluginState's fields are private and it has no serde form: store to_bytes() instead of
    serializing it. id() replaces format and class_id, and name(), vendor() and
    version() the fields of those names. A restore checks only the PluginId, not the plugin
    version. vst3::Preset::controller and DecodedPreset::controller are an Option, None
    for a preset without a Cont chunk; an empty Cont chunk 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_BYTES fails to save, as the other formats' states do.
  • PluginRef holds a PluginId and 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 with id() and bundle(), so a VST3 or CLAP
    reference without a bundle can no longer be made; the error for one is removed.
    PluginInfo::format and PluginInfo::class_id are PluginInfo::id: read
    info.id.format() and info.id.class_id().as_str(), and build an ID read from storage with
    PluginId::new(format, class_id). PluginInfo::new takes a PluginId, and
    Diagnostic::plugin is an Option<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::Error is one opaque struct for every format instead of an enum with a variant
    per format, and Vst3Error, ClapError and AuError are no longer public; the format modules'
    functions, such as vst3::Module::load and clap::Entry::load, return plughost_formats::Error.
    Match kind() for the FailureKind and input_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 VST3 tresult or the Audio Unit OSStatus
    (or NSError code), and is_output_event_overflow() and failure() remain. It is no longer
    Clone or PartialEq; compare kind().
  • Errors keep their causes. A plughost-formats error and a RenderError display only their own
    context, and source() 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 of FailureKind::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 are RenderError::Input(InputError::Events); RenderError::Events is
    removed. plughost reports input that a render rejects as Error::Input with no slot, never as
    Error::Render. Audio buffers of the wrong channel count or length, or a block longer than the
    prepared maximum, are InputError::AudioBuffers in every crate and in rendering:
    Error::Buffers, RenderError::InputChannels and RenderError::InputLength are removed, so
    match Error::Input { error: InputError::AudioBuffers, .. } or
    RenderError::Input(InputError::AudioBuffers). Buffers of another precision than the prepared one
    are InputError::AudioSampleFormat in the in-process VST3 and CLAP process calls too, where
    they were a VST3 sample format mismatch and a CLAP unsupported sample format. Parameter changes
    that the in-process process calls receive out of offset order, outside the block or outside
    0..=1 fail with InputError::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) is supported(format), which returns the
    unsupported error instead of false.
  • The types an application builds are #[non_exhaustive], so later versions can add fields:
    build RoutedChainConfig, SlotAudioConfig, AudioBusConfig, AudioInputRoute,
    SlotEventConfig, EventInputRoute, AudioConfig, EventConfig, HostIdentity,
    ParameterChange, AutomationEvent, ProcessConfig, RenderInput, RenderSchedule,
    AutomationRamp, TransportChange, TimeSignature, LoopRegion, BarPosition,
    PresetDirectory, PresetFile, PresetDiscoveryTarget and DiscoveredPreset with their
    constructors, or start from main_bus_config or default(), and set public fields instead of
    writing struct literals. The output-only types ChangeBatch, SlotChange, Catalog,
    BundleScan, Failure, Diagnostic, DiagnosticBatch, ParameterGroup, ParameterChoice,
    ParameterChoicePage, ParameterEventBatch, AudioConfiguration, BypassState,
    DecodedPreset, vst3::Preset and the preset discovery results are #[non_exhaustive] too,
    as are the enums Message, AudioBusRole, AudioDirection, AudioSource, EventSource,
    ChannelAdaptation, Support, SampleFormat, ProcessMode, Tail, SessionState,
    PresetLocation, EditorRequest and EditorView, which need a wildcard arm in a match.
  • 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_text and validate_choice_page are internal, and validate_event_count
    is no longer public. The methods RoutedChainConfig::slot_config, SlotEventConfig::native
    and ParameterInfo::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::load and load_with_host, au::Plugin::new and
    clap::Entry::load. The functions that use a loaded plugin stay safe, as does all of the
    plughost crate, which loads plugins in its helper processes. clap::Plugin::new,
    clap::preset_providers and clap::discover_presets take a clap::Entry instead of a bundle
    path, clap::classes(bundle) is Entry::classes, and clap::classes_once_loaded is removed:
    load the Entry, then read its classes.
  • Public constructors and builders that return their value (new, with_*, the Event,
    PluginRef and Scanner builders, Event::at and on_port), RenderSession,
    DiagnosticBatch, ParameterEventBatch, ChangeBatch and HostedPlugin::take_editor_request
    are #[must_use]: discarding one, as a take_* drain does, is a warning, so write let _ =
    where a drain is intended.
  • HostedPlugin, BlockProcessor and Sample are sealed: the format plugins and processors
    and f32 and f64 implement them, and other crates cannot. render::Process and
    AudioThread remain for applications to implement. With Sample sealed, processing no longer
    compares type IDs to refuse a sample type claiming another precision.
    HostedPlugin::set_editor_scale is a default method on every platform, returning None
    outside Windows, where it was only declared on Windows.
  • CLAP preset discovery flags are a PresetFlags instead of a u32
    (PresetLocationInfo::flags, PresetSoundpack::flags, DiscoveredPreset::flags); read the
    bits with bits().
  • Chain::parameters and HostedPlugin::parameters give each value as an Option<f64>, and
    vst3::Plugin::parameter_value returns one: a value the plugin does not give, that is not
    finite, or whose range is not, is None where it read as 0, and so is a VST3 ID the plugin
    does not have. A CLAP get_value that fails and an AudioUnitGetParameter that a v2 unit
    refuses are no longer read as 0 or, for the Audio Unit, as the AUParameter'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 in Rendered::non_finite_samples and RenderProgress::non_finite_samples, which
    are removed, and as they are never silent they kept an UntilSilence tail going to its
    maximum.

Fixed

  • An Audio Unit v2 parameter edit that AudioUnitSetParameter refuses fails with
    FailureKind::State and the OSStatus as native_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.