Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
pulp::gpu_audio::GpuMultiConvolver Class Reference

Massive multi-IR ("multi-room") convolution: convolves one mono input against MANY distinct impulse responses at once and pans the results into a stereo field — the whole bank in ONE batched GPU submit per block. More...

#include <gpu_multi_convolver.hpp>

Public Member Functions

 GpuMultiConvolver (uint32_t block_size, uint32_t sample_rate, std::vector< std::vector< float > > irs)
 irs is the bank of impulse responses (each a mono IR).
 ~GpuMultiConvolver () override
GpuAudioNodeDescriptor descriptor () const override
bool prepare () override
 Non-RT. Allocate/compile/upload static data here.
void process_block (const audio::BufferView< const float > &input, audio::BufferView< float > &output, uint32_t n) override
 Non-RT worker context. Process exactly one block (n == block_size).
void process_cpu_fallback (const audio::BufferView< const float > &input, audio::BufferView< float > &output, uint32_t n) noexcept override
 RT-safe fallback.
bool gpu_available () const
 True if the GPU compute device initialized and the multi-IR plan built.
bool device_lost () const noexcept
 True once the prepared device reports loss.
std::string backend () const
 The live compute backend ("Metal"/"D3D12"/"Vulkan"), or "" if CPU-only.
GpuAudioStatus status_snapshot () const noexcept override
 Allocation-free lifecycle projection, safe from a host or UI thread while a transport runs this node (not concurrently with prepare()).
GpuAudioRecoveryReason recovery_reason () const noexcept
 First latched recovery cause, or None. Same snapshot as status_snapshot().
bool fenced () const noexcept
 True before preparation or once the device was lost.
uint32_t num_ir () const
uint32_t fft_size () const
const std::vector< float > & pan_l () const
 Per-room constant-power pan gains (linear), available after prepare().
const std::vector< float > & pan_r () const
void set_flow (float depth, float spread=1.0f) noexcept
 Flow: 0 = static field (bit-for-bit the pan_l()/pan_r() layout); >0 makes each room's pan drift per block on its own rate, turning the reverb into a moving, irreducible field.
bool convolve_stereo (const float *mono_in, float *out_l, float *out_r, uint32_t n)
 Convolve one mono input block against all rooms and write the panned stereo result (out_l / out_r, each n samples).
virtual bool cpu_fallback_prepared () const noexcept
 Host-thread witness that the realtime CPU fallback is already built before prepare() returns.
virtual void prime_fallback (const audio::BufferView< const float > &, uint32_t) noexcept
 RT-safe.
virtual bool release_bounded (std::uint64_t) noexcept
 Host thread, audio stopped.
virtual bool bind_realtime_path (detail::RealtimeGpuNodeBinding &) noexcept
 Private transport hook, not for node authors: a node that the GPU audio transport drives through a stamped realtime path describes that path here.

Friends

bool detail::prepare_gpu_multi_convolver_with (GpuMultiConvolver &, std::unique_ptr< render::GpuCompute >) noexcept

Detailed Description

Massive multi-IR ("multi-room") convolution: convolves one mono input against MANY distinct impulse responses at once and pans the results into a stereo field — the whole bank in ONE batched GPU submit per block.

This is the regime the batched GPU path exists FOR. On the CPU each room is an independent convolution; doing N of them per block scales the cost linearly. On the GPU the forward FFT runs once and is shared, every room's complex-multiply + inverse FFT is batched, and the num_ir results are reduced to a stereo pair ON the GPU — so only one stereo block is read back regardless of how many rooms there are.

It is NOT a speed claim, and it must not be written as one. A spike measured on 2026-06-29 (planning/2026-06-29-superconvolver-irreducible-rooms-and-multi-ir.md) found a competent real-FFT CPU convolver matching or beating this path at every musically plausible setting measured (<= 256 rooms); the earlier "5x GPU win" was an artifact of a naive CPU baseline. What the batch buys is a different COST SHAPE — one shared forward transform, one readback, N resident IR spectra — and the headroom that comes with it. Where the crossover actually falls is a matter for measurement, per machine, not for a comment.

Reducibility caveat: with CONSTANT per-room pans this batch is mathematically collapsible. Every room convolves the same input and is summed with fixed weights, so the whole bank folds to two convolutions — one combined IR per stereo channel (A·x + B·x = (A+B)·x) — and a folding CPU implementation wins. So a static-pan N-room batch measures raw N-way parallel-convolution throughput, not a result that musically requires the GPU. The genuinely irreducible regime is TIME-VARYING per-room weights (pan/level that change per block): the combined IR would change every block, so folding costs O(N·IR_len) per block and the CPU can no longer pre-sum. That is where the batched GPU — N resident IR spectra, one flat MAC, a cheap per-block reweight — structurally beats the CPU. Pass fresh pans per block to drive it.

Implements GpuAudioNode so it can run RT-safe behind GpuAudioTransport (GPU work on the non-RT worker; the audio thread reads a fixed-latency result). The miss policy is Silence: the multi-room mode is by design beyond what the CPU can sustain at scale, so there is no real-time CPU fallback to fall to — a (rare) worker miss outputs a silent block rather than pretending otherwise.

Constructor & Destructor Documentation

◆ GpuMultiConvolver()

pulp::gpu_audio::GpuMultiConvolver::GpuMultiConvolver ( uint32_t block_size,
uint32_t sample_rate,
std::vector< std::vector< float > > irs )

irs is the bank of impulse responses (each a mono IR).

They may differ in length; all are zero-padded to the shared FFT size. block_size is the fixed processing block (power of two recommended).

◆ ~GpuMultiConvolver()

pulp::gpu_audio::GpuMultiConvolver::~GpuMultiConvolver ( )
override

Member Function Documentation

◆ backend()

std::string pulp::gpu_audio::GpuMultiConvolver::backend ( ) const

The live compute backend ("Metal"/"D3D12"/"Vulkan"), or "" if CPU-only.

◆ bind_realtime_path()

virtual bool pulp::gpu_audio::GpuAudioNode::bind_realtime_path ( detail::RealtimeGpuNodeBinding & )
inlinevirtualnoexceptinherited

Private transport hook, not for node authors: a node that the GPU audio transport drives through a stamped realtime path describes that path here.

Returns false for a node the transport drives through its staged worker path, which is the default.

Reimplemented in pulp::gpu_audio::GpuConvolver, pulp::gpu_audio::GpuNeuralRealtimeNode, and pulp::gpu_audio::GpuWaveNetRealtimeNode.

◆ convolve_stereo()

bool pulp::gpu_audio::GpuMultiConvolver::convolve_stereo ( const float * mono_in,
float * out_l,
float * out_r,
uint32_t n )

Convolve one mono input block against all rooms and write the panned stereo result (out_l / out_r, each n samples).

RT-UNSAFE (GPU readback); call from the transport worker or offline. Returns false on GPU failure (output left untouched by the GPU path — caller decides the miss policy).

◆ cpu_fallback_prepared()

virtual bool pulp::gpu_audio::GpuAudioNode::cpu_fallback_prepared ( ) const
inlinevirtualnoexceptinherited

Host-thread witness that the realtime CPU fallback is already built before prepare() returns.

A descriptor's supports_cpu_fallback bit is only a declaration; the transport admits MissPolicy::CpuFallback after both the declaration and this prepared witness are true. Nodes with no verified fallback remain fail-closed and must use silence on a miss.

Reimplemented in pulp::gpu_audio::GpuConvolver, and pulp::gpu_audio::GpuNeuralRealtimeNode.

◆ descriptor()

GpuAudioNodeDescriptor pulp::gpu_audio::GpuMultiConvolver::descriptor ( ) const
overridevirtual

◆ device_lost()

bool pulp::gpu_audio::GpuMultiConvolver::device_lost ( ) const
noexcept

True once the prepared device reports loss.

Every later block is a silent miss (the declared Silence policy) until the node is prepared again on a new device.

◆ fenced()

bool pulp::gpu_audio::GpuMultiConvolver::fenced ( ) const
noexcept

True before preparation or once the device was lost.

◆ fft_size()

uint32_t pulp::gpu_audio::GpuMultiConvolver::fft_size ( ) const
inline

◆ gpu_available()

bool pulp::gpu_audio::GpuMultiConvolver::gpu_available ( ) const
inline

True if the GPU compute device initialized and the multi-IR plan built.

◆ num_ir()

uint32_t pulp::gpu_audio::GpuMultiConvolver::num_ir ( ) const
inline

◆ pan_l()

const std::vector< float > & pulp::gpu_audio::GpuMultiConvolver::pan_l ( ) const
inline

Per-room constant-power pan gains (linear), available after prepare().

Exposed so a reference/golden can reproduce the exact stereo combination. These are the STATIC (Flow=0) pans; with Flow>0 the live per-block pans drift around them (see set_flow).

◆ pan_r()

const std::vector< float > & pulp::gpu_audio::GpuMultiConvolver::pan_r ( ) const
inline

◆ prepare()

bool pulp::gpu_audio::GpuMultiConvolver::prepare ( )
overridevirtual

Non-RT. Allocate/compile/upload static data here.

Implements pulp::gpu_audio::GpuAudioNode.

◆ prime_fallback()

virtual void pulp::gpu_audio::GpuAudioNode::prime_fallback ( const audio::BufferView< const float > & ,
uint32_t  )
inlinevirtualnoexceptinherited

RT-safe.

Called by the transport on EVERY block (not just misses) for a CpuFallback node, BEFORE the output-ring read, so the node's CPU fallback can be kept continuously fed and its convolution history is always current the moment a miss makes it take over. A node whose fallback needs no live priming (the default) does nothing. MUST NOT allocate, lock, or block.

Reimplemented in pulp::gpu_audio::GpuConvolver.

◆ process_block()

void pulp::gpu_audio::GpuMultiConvolver::process_block ( const audio::BufferView< const float > & input,
audio::BufferView< float > & output,
uint32_t n )
overridevirtual

Non-RT worker context. Process exactly one block (n == block_size).

Implements pulp::gpu_audio::GpuAudioNode.

◆ process_cpu_fallback()

void pulp::gpu_audio::GpuMultiConvolver::process_cpu_fallback ( const audio::BufferView< const float > & input,
audio::BufferView< float > & output,
uint32_t n )
overridevirtualnoexcept

RT-safe fallback.

MUST NOT allocate, lock, or block — it runs on the audio thread for the CpuFallback miss policy (hence noexcept). The default copies the current dry input to the output. The transport's output is delayed by latency_blocks, so that dry block breaks the timeline; it is not a safe fallback. A CpuFallback node should override this with a primed, latency-aligned fallback, or declare Silence. When paired with prime_fallback(), this emits the latency-aligned block the continuously-fed fallback has already computed.

Reimplemented from pulp::gpu_audio::GpuAudioNode.

◆ recovery_reason()

GpuAudioRecoveryReason pulp::gpu_audio::GpuMultiConvolver::recovery_reason ( ) const
noexcept

First latched recovery cause, or None. Same snapshot as status_snapshot().

◆ release_bounded()

virtual bool pulp::gpu_audio::GpuAudioNode::release_bounded ( std::uint64_t )
inlinevirtualnoexceptinherited

Host thread, audio stopped.

Release provider resources within budget_ns, returning false if teardown had to be abandoned to stay in budget. The default holds nothing to release.

Reimplemented in pulp::gpu_audio::GpuWaveNetRealtimeNode.

◆ set_flow()

void pulp::gpu_audio::GpuMultiConvolver::set_flow ( float depth,
float spread = 1.0f )
inlinenoexcept

Flow: 0 = static field (bit-for-bit the pan_l()/pan_r() layout); >0 makes each room's pan drift per block on its own rate, turning the reverb into a moving, irreducible field.

Thread-safe (atomic); call from any thread — the transport worker applies it on the next block. spread shapes the wander depth. Both clamped to [0,1].

◆ status_snapshot()

GpuAudioStatus pulp::gpu_audio::GpuMultiConvolver::status_snapshot ( ) const
overridevirtualnoexcept

Allocation-free lifecycle projection, safe from a host or UI thread while a transport runs this node (not concurrently with prepare()).

The staged GpuCompute device does not identify its provider, so provider and engine stay Unknown. The declared miss policy is Silence with no fallback; a lost device reports Unavailable with ProviderLost, which is why blocks go silent.

Reimplemented from pulp::gpu_audio::GpuAudioNode.

◆ detail::prepare_gpu_multi_convolver_with


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