Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
convolver_messages.hpp
Go to the documentation of this file.
1#pragma once
2
4#include <pulp/signal/fft.hpp>
5
6#include <algorithm>
7#include <atomic>
8#include <complex>
9#include <cstddef>
10#include <cstdint>
11#include <memory>
12#include <span>
13#include <vector>
14
15namespace pulp::signal {
16
28template <typename SampleType = float> class ConvolverSpectrumArenaT {
29 public:
30 using value_type = std::complex<SampleType>;
31
34 static constexpr std::size_t stride_for(std::size_t bins) noexcept {
35 constexpr std::size_t per_line = 64 / sizeof(value_type);
36 return (bins + per_line - 1) / per_line * per_line;
37 }
38
40 ConvolverSpectrumArenaT(std::size_t count, std::size_t bins) {
41 assign(count, bins);
42 }
43
44 // The alignment offset is relative to the storage address, which a copy
45 // would change; a move keeps the buffer, so moving is safe.
49 ConvolverSpectrumArenaT& operator=(ConvolverSpectrumArenaT&&) noexcept = default;
50
52 void assign(std::size_t count, std::size_t bins) {
53 constexpr std::size_t per_line = 64 / sizeof(value_type);
54 count_ = count;
55 bins_ = bins;
56 stride_ = stride_for(bins);
57 storage_.assign(count_ * stride_ + per_line, value_type{});
58 const auto address = reinterpret_cast<std::uintptr_t>(storage_.data());
59 offset_ = ((64u - address % 64u) % 64u) / sizeof(value_type);
60 }
61
62 std::size_t size() const noexcept {
63 return count_;
64 }
65 bool empty() const noexcept {
66 return count_ == 0;
67 }
68 std::size_t bins() const noexcept {
69 return bins_;
70 }
71 std::size_t stride() const noexcept {
72 return stride_;
73 }
74
75 std::size_t footprint_bytes() const noexcept {
76 return storage_.size() * sizeof(value_type);
77 }
78
79 std::span<value_type> operator[](std::size_t i) noexcept {
80 return {storage_.data() + offset_ + i * stride_, bins_};
81 }
82 std::span<const value_type> operator[](std::size_t i) const noexcept {
83 return {storage_.data() + offset_ + i * stride_, bins_};
84 }
85
87 void fill_zero() noexcept {
88 std::fill(storage_.begin(), storage_.end(), value_type{});
89 }
90
91 private:
92 std::vector<value_type> storage_;
93 std::size_t offset_ = 0, count_ = 0, bins_ = 0, stride_ = 0;
94};
95
115template <typename SampleType = float>
117 int block_size = 0;
118 int fft_size = 0;
119
136 std::vector<std::complex<SampleType>> overlap;
137 std::size_t write_pos = 0;
138};
139
142
165template <typename SampleType = float>
167 int block_size = 0;
168 int fft_size = 0;
169 std::size_t num_partitions = 0;
170
171 std::unique_ptr<FftT<SampleType>> fft;
175 std::vector<std::complex<SampleType>> accum;
176 std::unique_ptr<ConvolverInputHistoryT<SampleType>> history;
177};
178
181
182namespace detail {
183
188constexpr std::size_t convolver_block_size(std::size_t block_size) noexcept {
189 std::size_t pot = 1;
190 while (pot < block_size && pot <= (static_cast<std::size_t>(-1) >> 1))
191 pot <<= 1;
192 return pot;
193}
194
197template <typename SampleType = float>
198inline std::unique_ptr<ConvolverIrStateT<SampleType>>
199build_convolver_ir_state(const SampleType* ir,
200 std::size_t ir_length,
201 std::size_t block_size) {
202 if (ir == nullptr || ir_length == 0)
203 return nullptr;
204
205 block_size = convolver_block_size(block_size);
206
207 auto state = std::make_unique<ConvolverIrStateT<SampleType>>();
208 state->block_size = static_cast<int>(block_size);
209 state->fft_size = state->block_size * 2;
210 state->fft = std::make_unique<FftT<SampleType>>(state->fft_size);
211 state->num_partitions = (ir_length + block_size - 1) / block_size;
212
213 const auto bins = static_cast<std::size_t>(state->fft_size / 2 + 1);
214 state->ir_spectra.assign(state->num_partitions, bins);
215 std::vector<std::complex<SampleType>> padded(
216 state->fft_size, {SampleType{0.0f}, SampleType{0.0f}});
217
218 for (std::size_t p = 0; p < state->num_partitions; ++p) {
219 const std::size_t offset = p * block_size;
220 const std::size_t count = std::min(block_size, ir_length - offset);
221
222 std::fill(padded.begin(), padded.end(),
223 std::complex<SampleType>{SampleType{0.0f}, SampleType{0.0f}});
224 for (std::size_t i = 0; i < count; ++i)
225 padded[i] = {ir[offset + i], SampleType{0.0f}};
226
227 state->fft->forward(padded.data());
228 std::copy_n(padded.begin(), bins, state->ir_spectra[p].begin());
229 }
230
231 state->accum.assign(state->fft_size,
232 {SampleType{0.0f}, SampleType{0.0f}});
233 state->history = std::make_unique<ConvolverInputHistoryT<SampleType>>();
234 state->history->block_size = state->block_size;
235 state->history->fft_size = state->fft_size;
236 state->history->overlap.assign(state->fft_size, {0.0f, 0.0f});
237 state->history->spectra.assign(state->num_partitions, bins);
238
239 return state;
240}
241
264template <typename SampleType = float>
267 if (from.fft_size != to.fft_size || from.block_size != to.block_size)
268 return;
269 const std::size_t old_capacity = from.spectra.size();
270 const std::size_t new_capacity = to.spectra.size();
271 if (old_capacity == 0 || new_capacity == 0) return;
272 const std::size_t carried = std::min(old_capacity, new_capacity);
273 // Both rings share the FFT size, so their spectra have the same length.
274 // Each `a` maps to a distinct slot in each of two distinct rings, so a copy
275 // never overwrites a slot a later iteration reads.
276 for (std::size_t a = 0; a < carried; ++a) {
277 const std::size_t oldi = (from.write_pos + old_capacity - 1 - a) % old_capacity;
278 const std::size_t newi = (new_capacity - 1 - a) % new_capacity;
279 const auto source = from.spectra[oldi];
280 std::copy(source.begin(), source.end(), to.spectra[newi].begin());
281 }
282 // The overlap buffer's lower half holds the previous block's samples (the
283 // overlap the next block's FFT needs); it is IR-independent, so carry it too.
284 to.overlap.swap(from.overlap);
285 to.write_pos = 0;
286}
287
288} // namespace detail
289
313template <typename SampleType = float>
315public:
317
320
328 bool stage_ir(const SampleType* ir,
329 std::size_t ir_length,
330 std::size_t block_size) {
331 auto next = detail::build_convolver_ir_state(ir, ir_length, block_size);
332 if (!next)
333 return false;
334 return stage_ir(std::move(next));
335 }
336
338 bool stage_ir(std::unique_ptr<ConvolverIrStateT<SampleType>> next) {
339 return handoff_.publish(std::move(next));
340 }
341
345 std::unique_ptr<ConvolverIrStateT<SampleType>> try_consume() {
346 return handoff_.try_consume();
347 }
348
360 [[nodiscard]] bool retire(std::unique_ptr<ConvolverIrStateT<SampleType>>& displaced) {
361 return handoff_.retire(displaced);
362 }
363
368 bool has_retire_capacity() const { return handoff_.has_retire_capacity(); }
369
374 bool has_retire_capacity(std::size_t n) const { return handoff_.has_retire_capacity(n); }
375
379 std::size_t drain_old() { return handoff_.drain_retired(); }
380
383 std::unique_ptr<ConvolverIrStateT<SampleType>> drain_old_one() {
384 return handoff_.drain_retired_one();
385 }
386
388 bool has_pending() const { return handoff_.has_pending(); }
389
391 bool has_retired() const { return handoff_.has_retired(); }
392
396 static constexpr std::size_t retire_capacity() { return kRetireRingCapacity; }
397
398private:
399 static constexpr std::size_t kRetireRingCapacity = 8;
400
401 // The publish/consume/retire machinery is `runtime::Handoff` — the shared
402 // real-time ownership-transfer primitive. It owns the pending slot, the
403 // retire ring, and teardown reclamation, so this class only adds the
404 // IR-specific `stage_ir` build step and the `drain_old` naming its callers
405 // already use.
406 pulp::runtime::Handoff<ConvolverIrStateT<SampleType>, kRetireRingCapacity> handoff_;
407};
408
411
412} // namespace pulp::signal
Lock-free transfer of ownership to a reader that must never block, plus a ring for handing the displa...
Definition slot.hpp:254
Lock-free background-IR shuttle for PartitionedConvolver.
Definition convolver_messages.hpp:314
std::unique_ptr< ConvolverIrStateT< SampleType > > drain_old_one()
UI / worker thread: pop ONE retired IR for test inspection.
Definition convolver_messages.hpp:383
std::unique_ptr< ConvolverIrStateT< SampleType > > try_consume()
Audio-thread-callable: atomically claim the most recently staged IR, if any.
Definition convolver_messages.hpp:345
bool has_retire_capacity(std::size_t n) const
Room to park at least n IRs — for a caller that must retire more than one in a single audio-thread st...
Definition convolver_messages.hpp:374
bool retire(std::unique_ptr< ConvolverIrStateT< SampleType > > &displaced)
Audio-thread-callable: park the IR that the audio thread is displacing so a non-RT thread can free it...
Definition convolver_messages.hpp:360
bool has_pending() const
True if a freshly-staged IR is awaiting consumption.
Definition convolver_messages.hpp:388
std::size_t drain_old()
UI / worker thread: reclaim ALL retired IRs the audio thread has parked.
Definition convolver_messages.hpp:379
bool stage_ir(std::unique_ptr< ConvolverIrStateT< SampleType > > next)
Publish a pre-built state. Same semantics as the raw overload.
Definition convolver_messages.hpp:338
bool has_retire_capacity() const
True if there's room in the retire ring for one more displaced IR.
Definition convolver_messages.hpp:368
ConvolverIrSwapperT(const ConvolverIrSwapperT &)=delete
static constexpr std::size_t retire_capacity()
Compile-time capacity of the retired-IR ring.
Definition convolver_messages.hpp:396
bool stage_ir(const SampleType *ir, std::size_t ir_length, std::size_t block_size)
Build a fresh IR state off the audio thread and publish it for the audio thread to pick up.
Definition convolver_messages.hpp:328
ConvolverIrSwapperT & operator=(const ConvolverIrSwapperT &)=delete
bool has_retired() const
True if any retired IR is awaiting drain.
Definition convolver_messages.hpp:391
A fixed number of equal-length spectra in one contiguous, 64-byte-aligned block, each starting on a 6...
Definition convolver_messages.hpp:28
std::span< const value_type > operator[](std::size_t i) const noexcept
Definition convolver_messages.hpp:82
void fill_zero() noexcept
Zero every spectrum. Allocation-free.
Definition convolver_messages.hpp:87
bool empty() const noexcept
Definition convolver_messages.hpp:65
std::size_t footprint_bytes() const noexcept
Bytes this arena holds, including its alignment slack.
Definition convolver_messages.hpp:75
static constexpr std::size_t stride_for(std::size_t bins) noexcept
Distance between consecutive spectra in value_types: bins rounded up to a whole number of 64-byte lin...
Definition convolver_messages.hpp:34
ConvolverSpectrumArenaT(std::size_t count, std::size_t bins)
Definition convolver_messages.hpp:40
ConvolverSpectrumArenaT(ConvolverSpectrumArenaT &&) noexcept=default
ConvolverSpectrumArenaT & operator=(const ConvolverSpectrumArenaT &)=delete
ConvolverSpectrumArenaT(const ConvolverSpectrumArenaT &)=delete
std::size_t stride() const noexcept
Definition convolver_messages.hpp:71
std::complex< SampleType > value_type
Definition convolver_messages.hpp:30
std::span< value_type > operator[](std::size_t i) noexcept
Definition convolver_messages.hpp:79
void assign(std::size_t count, std::size_t bins)
Allocate count zeroed spectra of bins values each.
Definition convolver_messages.hpp:52
std::size_t bins() const noexcept
Definition convolver_messages.hpp:68
std::size_t size() const noexcept
Definition convolver_messages.hpp:62
Round-2 effect-wave vocabulary.
Definition analysis_frontends.hpp:50
constexpr std::size_t convolver_block_size(std::size_t block_size) noexcept
The block size a convolver prepared for block_size accepts: the request rounded up to the next power ...
Definition convolver_messages.hpp:188
void migrate_input_history(ConvolverInputHistoryT< SampleType > &from, ConvolverInputHistoryT< SampleType > &to)
Migrate the recorded input history from from into to, age-aligned.
Definition convolver_messages.hpp:265
std::unique_ptr< ConvolverIrStateT< SampleType > > build_convolver_ir_state(const SampleType *ir, std::size_t ir_length, std::size_t block_size)
Build a ConvolverIrState from a raw IR.
Definition convolver_messages.hpp:199
Definition additive_bank.hpp:218
ConvolverIrStateT< double > ConvolverIrState64
Definition convolver_messages.hpp:180
ConvolverInputHistoryT< float > ConvolverInputHistory
Definition convolver_messages.hpp:140
ConvolverInputHistoryT< double > ConvolverInputHistory64
Definition convolver_messages.hpp:141
ConvolverIrSwapperT< float > ConvolverIrSwapper
Definition convolver_messages.hpp:409
@ i
Definition formant_filter_bank.hpp:20
ConvolverIrSwapperT< double > ConvolverIrSwapper64
Definition convolver_messages.hpp:410
ConvolverIrStateT< float > ConvolverIrState
Definition convolver_messages.hpp:179
SequencerStateChannel is the audio-safe transport for a plugin's structured NON-parameter state: the ...
Definition audio_device_manager.hpp:48
STL namespace.
Publishing a value built off the audio thread, and reclaiming the old one without ever calling a dest...
The convolver's input-side overlap-save history.
Definition convolver_messages.hpp:116
ConvolverSpectrumArenaT< float > spectra
Definition convolver_messages.hpp:132
std::vector< std::complex< float > > overlap
Definition convolver_messages.hpp:136
int block_size
Definition convolver_messages.hpp:117
int fft_size
Definition convolver_messages.hpp:118
std::size_t write_pos
Definition convolver_messages.hpp:137
Pre-computed, audio-thread-ready state for a single impulse response.
Definition convolver_messages.hpp:166
std::size_t num_partitions
Definition convolver_messages.hpp:169
int fft_size
Definition convolver_messages.hpp:168
std::vector< std::complex< float > > accum
Definition convolver_messages.hpp:175
std::unique_ptr< FftT< float > > fft
Definition convolver_messages.hpp:171
std::unique_ptr< ConvolverInputHistoryT< float > > history
Definition convolver_messages.hpp:176
ConvolverSpectrumArenaT< float > ir_spectra
Definition convolver_messages.hpp:174
int block_size
Definition convolver_messages.hpp:167