Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
adapter_boundary.hpp File Reference

The shared adapter-boundary core. More...

#include <pulp/format/processor.hpp>
#include <pulp/format/detail/playhead_diff.hpp>
#include <pulp/signal/delay_line.hpp>
#include <pulp/state/parameter_event_queue.hpp>
#include <pulp/state/store.hpp>
#include <array>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <span>
#include <vector>

Go to the source code of this file.

Classes

struct  pulp::format::boundary::HostTransport
 Format-neutral transport snapshot, already decoded from a host playhead into canonical units (quarter-note beats, samples, real BPM). More...
class  pulp::format::boundary::LatencyCompensatedBypassT< MaxChannels >
 Per-channel dry-signal delay used by an adapter's bypass path. More...
struct  pulp::format::boundary::ParamSnapshotState
 Generation-gated pre-process snapshot: which generation it was taken at and whether it can be trusted. More...
struct  pulp::format::boundary::MpeSidecar
 Owns the MPE tracker + expression buffer + inbound-offset cursor an adapter wraps around its MIDI decode, plus the descriptor opt-in flag. More...

Namespaces

namespace  pulp
namespace  pulp::format
 What the product grants a device: the quota table indexed by capability tier and live thermal state.
namespace  pulp::format::boundary

Typedefs

using pulp::format::boundary::LatencyCompensatedBypass = LatencyCompensatedBypassT<kBoundaryMaxChannels>

Functions

void pulp::format::boundary::copy_f32_to_f64 (const float *src, double *dst, std::uint32_t count) noexcept
 Widen count floats src → dst as doubles. No-op on a null pointer.
void pulp::format::boundary::copy_f64_to_f32 (const double *src, float *dst, std::uint32_t count) noexcept
 Narrow count doubles src → dst as floats. No-op on a null pointer.
void pulp::format::boundary::zero_f64 (double *dst, std::uint32_t count) noexcept
 Zero count doubles. No-op on a null pointer.
void pulp::format::boundary::apply_host_transport (ProcessContext &ctx, const HostTransport &transport, detail::PlayheadSnapshot &snapshot) noexcept
 Write transport into ctx's transport fields, derive the bar when the host did not supply one, then diff ctx against snapshot to populate tempo_changed / time_sig_changed / transport_changed / transport_started / transport_jump and advance the snapshot in place.
template<std::size_t MaxChannels>
void pulp::format::boundary::render_bypass_passthrough (LatencyCompensatedBypassT< MaxChannels > &bypass, float *const *out_ptrs, int out_channels, const float *const *in_ptrs, int in_channels, std::uint32_t count)
 Render one bypassed block: copy main input → output per channel, latency- compensated through bypass so the dry signal stays sample-aligned with the host's plugin-delay-compensation, and zero any output channel without a matching input.
bool pulp::format::boundary::apply_param_value (state::ParameterEventQueue &queue, state::StateStore &store, state::ParamID id, std::int32_t sample_offset, float value)
 Apply one incoming host parameter-value event, the way every adapter must.
std::size_t pulp::format::boundary::find_param_index (std::span< const state::ParamInfo > all_params, state::ParamID id) noexcept
 Position of id within all_params, or kParamIndexNotFound.
void pulp::format::boundary::snapshot_param_values (const state::StateStore &store, std::span< const state::ParamInfo > all_params, std::vector< float > &snapshot)
 Capture every parameter's current value into snapshot, positionally aligned with all_params, so a post-process() diff can spot the values the processor changed.
template<typename AfterRead = void (*)()>
void pulp::format::boundary::refresh_param_snapshot (const state::StateStore &store, std::span< const state::ParamInfo > all_params, std::vector< float > &snapshot, ParamSnapshotState &state, std::uint64_t generation, AfterRead &&after_read=[] {})
 Brings snapshot up to date for a block whose store generation is generation, re-reading every value only when a write landed since the last trusted snapshot.
bool pulp::format::boundary::changed_since_snapshot (float current, float snapshotted) noexcept
 True when current differs from snapshotted, compared bitwise.

Variables

constexpr std::size_t pulp::format::boundary::kBoundaryMaxChannels = 8
 Channel ceiling — the single source of truth for the per-bus channel count every adapter caps at.
constexpr std::size_t pulp::format::boundary::kParamIndexNotFound = static_cast<std::size_t>(-1)
 Index returned by find_param_index when the id is not registered.

Detailed Description

The shared adapter-boundary core.

Every per-format plugin adapter (CLAP, VST3, AU v2/v3, AAX, LV2, WAM/WCLAP, standalone) sits between a host ABI and one Processor. The host-ABI glue is genuinely format-specific — the event structs, the pointer layouts, the transport flag bits all differ. But the boundary logic those adapters wrap around the glue is identical, and it was copy-pasted at different fidelity per adapter with nothing that noticed when a copy drifted. This header is where that logic is consolidated as adapters migrate onto it. The adoption is deliberately per-component and still partial:

  1. f64 (double) marshalling — the float⇄double block copies a double-precision host hands the adapter. Consumed by clap_adapter.cpp and vst3_adapter.cpp; the AU adapter still marshals f64 through its own path.
  2. transport → ProcessContext — HostTransport is the neutral, already-decoded transport an adapter fills from its host playhead; a single mapper writes the ProcessContext transport fields, derives the bar when the host did not supply one, and diffs the change-flags. The per-format decode (CLAP fixed-point beattime, VST3 barPositionMusic, AU seconds, LV2 time:Position atom properties, …) stays in the adapter. Consumed by clap_adapter.cpp and lv2_entry.hpp; VST3 and AU drive detail::playhead_diff directly.
  3. latency-compensated bypass — LatencyCompensatedBypass is the per-channel dry delay line that keeps a bypassed signal sample-aligned with the host's plugin-delay-compensation. CLAP and VST3 feed it inline (they also marshal f64 host buffers); AU v2, AU v3, and AAX route their float bypass pass-through through the shared render_bypass_passthrough helper. LV2 has no adapter-level bypass short-circuit (its run() always calls process()), so there is no dry copy to compensate.
  4. parameter dual-write — apply_param_value enqueues an incoming host param event for the sample-accurate DSP cursor and publishes it to the RT-safe listener path, the pair every adapter must keep in sync. Consumed by clap_adapter.cpp and vst3_adapter.cpp; AU applies host params through its own store writes.
  5. output-parameter publication — the bookkeeping around reporting plugin-side parameter changes back to the host for automation recording: resolving a param id to its scratch-vector index, snapshotting values before process(), and the bitwise post-process() diff. The host-ABI emit stays in the adapter. Consumed by clap_adapter.cpp and vst3_adapter.cpp.
  6. MPE per-note-expression sidecar — MpeSidecar owns the MpeVoiceTracker + MpeBuffer + inbound-offset cursor + descriptor opt-in flag, and the enable / bind / per-block clear-run-attach dance an adapter wraps around its MIDI decode. The per-format expression decode (VST3 kNoteExpressionValueEvent, CLAP clap_event_note_expression_t) stays in the adapter — it synthesizes channel-wide MIDI via mpe_expression.hpp into the shared MidiBuffer this consumes. Consumed by clap_adapter.cpp and vst3_adapter.cpp.

Real-time contract. Everything here runs on the audio/render thread and is allocation-, lock-, and syscall-free after prepare(): the only place that touches the heap is LatencyCompensatedBypass::prepare(), which the adapter calls at activate/setup time off the audio thread. The functions are pure and header-only so the parity-matrix test (test_adapter_boundary_ parity.cpp) can drive one processor through every format's boundary profile and assert identical observable behavior without standing up a host SDK.