45template <
typename SampleType =
float>
60 history_ = state_ ? std::move(state_->history) :
nullptr;
88 if (fading_ && !fade_mixer_.done())
100 (void)swapper.
retire(fading_);
101 (void)fading_.release();
108 auto previous = std::move(state_);
109 state_ = std::move(next);
117 auto spare = std::move(state_->history);
118 const bool ring_serves_new =
119 history_ !=
nullptr &&
120 history_->block_size == state_->block_size &&
121 history_->fft_size == state_->fft_size &&
122 history_->spectra.size() >= state_->num_partitions;
123 if (!ring_serves_new) {
131 history_.swap(spare);
144 const bool can_fade =
146 previous->block_size == state_->block_size &&
147 fade_scratch_.size() >=
static_cast<std::size_t
>(state_->block_size);
153 assert(!previous->history);
154 previous->history = std::move(spare);
156 fading_ = std::move(previous);
157 fade_mixer_.configure(fade_samples_, fade_curve_);
161 const bool ok = swapper.
retire(previous);
163 (void)previous.release();
196 fade_samples_ = samples;
199 fade_scratch_.assign(
static_cast<std::size_t
>(state_->block_size),
233 std::size_t num_samples) {
238 if (!state_ || state_->ir_spectra.empty() || !history_ ||
239 history_->block_size != state_->block_size ||
240 history_->fft_size != state_->fft_size ||
241 history_->spectra.size() < state_->num_partitions) {
242 std::copy_n(input, num_samples, output);
246 if (
static_cast<int>(num_samples) != state_->block_size) {
247 std::fill_n(output, num_samples, SampleType{0.0f});
248 ++block_size_violations_;
249 history_torn_ =
true;
253 if (history_torn_)
reset();
257 capture_input(*history_, *state_->fft, input);
259 render_ir(*state_, *history_, output);
265 if (fading_ && !fade_mixer_.done()) {
270 const bool can_render =
271 fade_scratch_.size() >= num_samples &&
272 static_cast<std::size_t
>(fading_->block_size) == num_samples &&
273 history_->spectra.size() >= fading_->num_partitions;
275 render_ir(*fading_, *history_, fade_scratch_.data());
276 const std::size_t base = fade_mixer_.position();
277 for (std::size_t
i = 0;
i < num_samples; ++
i) {
278 SampleType old_gain, new_gain;
279 fade_mixer_.gains_at(base +
i, old_gain, new_gain);
280 output[
i] = output[
i] * new_gain + fade_scratch_[
i] * old_gain;
288 fade_mixer_.advance(num_samples);
295 advance_input(*history_);
304 clear_history(history_.get());
305 history_torn_ =
false;
306 block_size_violations_ = 0;
323 return state_ ?
static_cast<std::size_t
>(state_->block_size) : 0;
336 return state_ ? state_->num_partitions : 0;
340 return state_ && !state_->ir_spectra.empty();
351 const SampleType* input) {
352 SampleType* window = overlap_samples(h);
353 std::copy_n(input, h.block_size, window + h.block_size);
362 static void advance_input(ConvolverInputHistoryT<SampleType>& h) {
363 SampleType* window = overlap_samples(h);
364 std::copy_n(window + h.block_size, h.block_size, window);
365 h.write_pos = (h.write_pos + 1) % h.spectra.size();
371 static SampleType* overlap_samples(ConvolverInputHistoryT<SampleType>& h) {
372 return reinterpret_cast<SampleType*
>(h.overlap.data());
380 static void render_ir(ConvolverIrStateT<SampleType>& s,
381 const ConvolverInputHistoryT<SampleType>& h,
382 SampleType* output) {
387 const int half =
s.fft_size / 2;
388 const std::size_t capacity =
h.spectra.size();
389 std::complex<SampleType>* accum =
s.accum.data();
390 const auto spectrum = [&](std::size_t
p) {
393 return h.spectra[(
h.write_pos + capacity -
p) % capacity].data();
403 const std::complex<SampleType>* in =
spectrum(0);
404 const std::complex<SampleType>* ir =
s.ir_spectra[0].data();
405 for (
int i = 0;
i <=
half; ++
i)
406 accum[
i] = cmul(in[
i], ir[
i]);
409 for (;
p + 4 <=
s.num_partitions;
p += 4) {
410 const std::complex<SampleType>* in0 =
spectrum(p);
411 const std::complex<SampleType>* in1 =
spectrum(p + 1);
412 const std::complex<SampleType>* in2 =
spectrum(p + 2);
413 const std::complex<SampleType>* in3 =
spectrum(p + 3);
414 const std::complex<SampleType>* ir0 =
s.ir_spectra[
p].data();
415 const std::complex<SampleType>* ir1 =
s.ir_spectra[
p + 1].data();
416 const std::complex<SampleType>* ir2 =
s.ir_spectra[
p + 2].data();
417 const std::complex<SampleType>* ir3 =
s.ir_spectra[
p + 3].data();
418 for (
int i = 0;
i <=
half; ++
i)
419 accum[
i] += (cmul(in0[
i], ir0[
i]) + cmul(in1[
i], ir1[
i])) +
420 (cmul(in2[
i], ir2[
i]) + cmul(in3[
i], ir3[
i]));
422 for (;
p <
s.num_partitions; ++
p) {
423 const std::complex<SampleType>* in =
spectrum(p);
424 const std::complex<SampleType>* ir =
s.ir_spectra[
p].data();
425 for (
int i = 0;
i <=
half; ++
i)
426 accum[
i] += cmul(in[
i], ir[
i]);
433 auto* time =
reinterpret_cast<SampleType*
>(accum);
434 s.fft->inverse_real_half(accum, time);
435 std::copy_n(time +
s.block_size,
s.block_size, output);
438 static std::complex<SampleType> cmul(std::complex<SampleType>
a,
439 std::complex<SampleType> b)
noexcept {
440 return {
a.real() *
b.real() -
a.imag() *
b.imag(),
441 a.real() *
b.imag() +
a.imag() *
b.real()};
446 static void clear_history(ConvolverInputHistoryT<SampleType>* h) {
448 constexpr std::complex<SampleType> kZero{SampleType{0.0f}, SampleType{0.0f}};
449 h->spectra.fill_zero();
450 std::fill(
h->overlap.begin(),
h->overlap.end(), kZero);
454 std::unique_ptr<ConvolverIrStateT<SampleType>> state_;
460 std::unique_ptr<ConvolverInputHistoryT<SampleType>> history_;
463 bool history_torn_ =
false;
464 std::uint64_t block_size_violations_ = 0;
467 std::unique_ptr<ConvolverIrStateT<SampleType>> fading_;
468 signal::TransitionMixerT<SampleType> fade_mixer_;
469 std::size_t fade_samples_ = 0;
471 std::vector<SampleType> fade_scratch_;
Lock-free background-IR shuttle for PartitionedConvolver.
Definition convolver_messages.hpp:314
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 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_retire_capacity() const
True if there's room in the retire ring for one more displaced IR.
Definition convolver_messages.hpp:368
void forward_real_half(const SampleType *input, std::complex< SampleType > *output, SampleType scale) const
Real-input forward transform.
Definition fft.hpp:433
Uniform partitioned convolution engine.
Definition convolver.hpp:46
bool try_swap_ir(ConvolverIrSwapperT< SampleType > &swapper)
Try to consume the most recently staged IR from a swapper.
Definition convolver.hpp:72
std::size_t num_partitions() const
Definition convolver.hpp:335
void load_ir(const SampleType *ir, std::size_t ir_length, std::size_t block_size)
Load an impulse response.
Definition convolver.hpp:55
PartitionedConvolverT()=default
bool is_loaded() const
Definition convolver.hpp:339
void reset()
Drop the convolver's overlap-save input history so the next block starts from silence rather than con...
Definition convolver.hpp:303
void process(const SampleType *input, SampleType *output, std::size_t num_samples)
Process a block of audio.
Definition convolver.hpp:231
std::uint64_t block_size_violations() const
How many times process() was called on a loaded convolver with num_samples != block_size().
Definition convolver.hpp:333
std::size_t latency() const
Returns the algorithmic latency in samples.
Definition convolver.hpp:313
void set_crossfade(std::size_t samples, TransitionCurve curve=TransitionCurve::Smoothstep)
Enable a click-free crossfade on IR swaps (opt-in — default 0 = instantaneous swap).
Definition convolver.hpp:195
std::size_t block_size() const
Definition convolver.hpp:322
@ b
Definition pitch.hpp:31
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
@ half
Definition units.hpp:290
Definition additive_bank.hpp:218
PartitionedConvolverT< double > PartitionedConvolver64
Definition convolver.hpp:475
PartitionedConvolverT< float > PartitionedConvolver
Definition convolver.hpp:474
@ a
Definition formant_filter_bank.hpp:20
@ i
Definition formant_filter_bank.hpp:20
@ ok
Definition breakpoint_envelope.hpp:36
TransitionCurve
Definition transition_mixer.hpp:34
@ Smoothstep
Definition transition_mixer.hpp:34
@ spectrum
Definition design_ir.hpp:265
@ s
Definition input_events.hpp:41
@ h
Definition input_events.hpp:39
@ p
Definition input_events.hpp:40
The convolver's input-side overlap-save history.
Definition convolver_messages.hpp:116
Shared click-free crossfade primitive.