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 |
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.
| 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).
|
override |
| std::string pulp::gpu_audio::GpuMultiConvolver::backend | ( | ) | const |
The live compute backend ("Metal"/"D3D12"/"Vulkan"), or "" if CPU-only.
|
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.
| 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).
|
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.
|
overridevirtual |
Implements pulp::gpu_audio::GpuAudioNode.
|
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.
|
noexcept |
True before preparation or once the device was lost.
|
inline |
|
inline |
True if the GPU compute device initialized and the multi-IR plan built.
|
inline |
|
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).
|
inline |
|
overridevirtual |
Non-RT. Allocate/compile/upload static data here.
Implements pulp::gpu_audio::GpuAudioNode.
|
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.
|
overridevirtual |
Non-RT worker context. Process exactly one block (n == block_size).
Implements pulp::gpu_audio::GpuAudioNode.
|
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.
|
noexcept |
First latched recovery cause, or None. Same snapshot as status_snapshot().
|
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.
|
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].
|
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.
|
friend |