Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
pulp::format::reload::ProcessorHotSwapSlot Class Reference

#include <processor_hotswap_slot.hpp>

Public Member Functions

 ProcessorHotSwapSlot ()=default
 ProcessorHotSwapSlot (std::unique_ptr< Processor > initial)
void process (audio::BufferView< float > &out, const audio::BufferView< const float > &in, midi::MidiBuffer &midi_in, midi::MidiBuffer &midi_out, const ProcessContext &ctx)
 Audio-thread entry.
std::unique_ptr< Processor > swap (std::unique_ptr< Processor > next)
 Control-thread swap.
void reclaim ()
 Control-thread reclaim of a completed fade-out.
void reprepare_active (const PrepareContext &ctx)
 Control-thread re-prepare of the live processor (e.g.
void set_crossfade_samples (std::size_t samples)
 Set the crossfade length in samples (0 = instantaneous swaps; the default).
void set_crossfade_curve (CrossfadeCurve curve)
 Select the crossfade amplitude law (default Smoothstep = equal-gain).
void prepare_crossfade (std::size_t max_frames, std::size_t max_channels)
 Allocate the scratch the fade-out processor renders into, sized for the worst-case block.
bool has_active () const
 True when a processor is installed. Control-thread/diagnostic use.
template<class Fn>
auto with_active (Fn &&fn) const -> decltype(fn(std::declval< Processor & >()))
 Invoke fn with the ACTIVE processor under the shared lock, returning its result (or a default-constructed result when no processor is installed).
bool crossfade_active () const
 True while a crossfade is in progress (a fade-out is rendering).
std::uint64_t contention_blocks () const
 Number of blocks that passed through due to swap contention / no active processor (dev metric).

Constructor & Destructor Documentation

◆ ProcessorHotSwapSlot() [1/2]

pulp::format::reload::ProcessorHotSwapSlot::ProcessorHotSwapSlot ( )
default

◆ ProcessorHotSwapSlot() [2/2]

pulp::format::reload::ProcessorHotSwapSlot::ProcessorHotSwapSlot ( std::unique_ptr< Processor > initial)
inlineexplicit

Member Function Documentation

◆ contention_blocks()

std::uint64_t pulp::format::reload::ProcessorHotSwapSlot::contention_blocks ( ) const
inline

Number of blocks that passed through due to swap contention / no active processor (dev metric).

◆ crossfade_active()

bool pulp::format::reload::ProcessorHotSwapSlot::crossfade_active ( ) const
inline

True while a crossfade is in progress (a fade-out is rendering).

◆ has_active()

bool pulp::format::reload::ProcessorHotSwapSlot::has_active ( ) const
inline

True when a processor is installed. Control-thread/diagnostic use.

◆ prepare_crossfade()

void pulp::format::reload::ProcessorHotSwapSlot::prepare_crossfade ( std::size_t max_frames,
std::size_t max_channels )
inline

Allocate the scratch the fade-out processor renders into, sized for the worst-case block.

Control thread, off the audio path (called from the host's prepare()). Re-callable (audio stopped); sizes are set exactly to the arguments, not grown — call with the worst-case block.

◆ process()

void pulp::format::reload::ProcessorHotSwapSlot::process ( audio::BufferView< float > & out,
const audio::BufferView< const float > & in,
midi::MidiBuffer & midi_in,
midi::MidiBuffer & midi_out,
const ProcessContext & ctx )
inline

Audio-thread entry.

Forwards to the active processor under a non-blocking shared lock; on swap contention (or no active processor) it passes the input through to the output for this block and counts it. When a previous processor is fading out (a crossfaded swap), it is rendered in parallel into the scratch buffer and mixed under a smoothstep ramp. Never blocks, allocates, or destroys.

◆ reclaim()

void pulp::format::reload::ProcessorHotSwapSlot::reclaim ( )
inline

Control-thread reclaim of a completed fade-out.

Frees the retired processor on THIS (control) thread under the writer lock — which proves no audio reader is inside it. Call periodically (e.g. from the shell's watcher tick). No-op when nothing is pending or a fade is still running.

◆ reprepare_active()

void pulp::format::reload::ProcessorHotSwapSlot::reprepare_active ( const PrepareContext & ctx)
inline

Control-thread re-prepare of the live processor (e.g.

a host sample-rate change). Takes the writer lock — the same one swap() uses — so it cannot race a reader inside process(). No-op when no processor is installed. Call only while audio is stopped, like prepare(): the writer lock guarantees mutual exclusion with process(), not that prepare() (which may allocate) is realtime.

◆ set_crossfade_curve()

void pulp::format::reload::ProcessorHotSwapSlot::set_crossfade_curve ( CrossfadeCurve curve)
inline

Select the crossfade amplitude law (default Smoothstep = equal-gain).

Control thread, before swaps. Takes effect on the next swap; changing it mid-fade is allowed (the running fade keeps its ramp position, only the gain mapping changes) but not typical.

◆ set_crossfade_samples()

void pulp::format::reload::ProcessorHotSwapSlot::set_crossfade_samples ( std::size_t samples)
inline

Set the crossfade length in samples (0 = instantaneous swaps; the default).

Control thread, before swaps. Crossfading also requires prepare_crossfade() to have allocated the parallel-render scratch.

◆ swap()

std::unique_ptr< Processor > pulp::format::reload::ProcessorHotSwapSlot::swap ( std::unique_ptr< Processor > next)
inlinenodiscard

Control-thread swap.

Without a crossfade configured this installs next and RETURNS the displaced instance for the caller to destroy (on the control thread) — acquiring the writer lock proves no audio reader is inside the old instance. With a crossfade configured (and scratch prepared) it instead RETAINS the displaced instance as the fading-out processor, returns nullptr, and the slot reclaims it later (reclaim() / the next swap() / the destructor). The very first install (no prior active processor) never fades — there is nothing to fade from.

◆ with_active()

template<class Fn>
auto pulp::format::reload::ProcessorHotSwapSlot::with_active ( Fn && fn) const->decltype(fn(std::declval< Processor & >()))
inline

Invoke fn with the ACTIVE processor under the shared lock, returning its result (or a default-constructed result when no processor is installed).

Control / UI thread only — fn may allocate (e.g. build a view); it runs concurrently with the audio thread's process() (also a shared reader) and only blocks a swap, which is rare. Kept as a template so the slot needs no dependency on whatever fn touches (e.g. pulp::view); the caller instantiates it where those types are complete.

Lifetime note for view-building callers: the processor passed to fn is the one live RIGHT NOW. If it is later hot-swapped out and destroyed, a view that captured it would dangle — so a caller that keeps the editor across reloads must REBUILD on each swap (see ReloadableShell::set_on_reloaded), or have the logic return a self-contained view.

Concurrency contract: fn runs concurrently with the audio thread's process() on the SAME processor (both shared readers). fn must therefore only READ — a create_view() that races a member process() mutates would be a data race. (Building a self-contained view that touches no mutable DSP state is fine.) Note too that while fn holds the shared lock, a swap waiting on the writer lock makes the audio thread's try-lock fail → one or more passthrough blocks for fn's duration; keep fn off the hot path's critical timing (editor open / post-swap rebuild, not per-block).


The documentation for this class was generated from the following file: