Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
overlap_add_convolver.hpp
Go to the documentation of this file.
1#pragma once
2
5
6#include <pulp/signal/fft.hpp>
7
8#include <algorithm>
9#include <memory>
10#include <vector>
11
12namespace pulp::signal {
13
14// Simple frequency-domain convolver using overlap-add. Output is delayed by
15// one block: a block is transformed once it has filled, and its result is
16// read out while the next one fills.
17//
18// RT contract: load_ir() allocates FFT and overlap-add buffers and is not
19// audio-thread safe. process(), buffer process(), and reset() are
20// allocation-free after a valid impulse response has been loaded.
21template <typename SampleType = float> class ConvolverT {
22 public:
23 // Load an impulse response
24 void load_ir(const SampleType* ir, int ir_length, int block_size = 0) {
25 if (ir == nullptr || ir_length <= 0) {
26 fft_.reset();
27 ir_spectrum_.clear();
28 input_buf_.clear();
29 output_buf_.clear();
30 overlap_.clear();
31 spectrum_.clear();
32 time_.clear();
33 fft_size_ = 0;
34 block_size_ = 0;
35 pos_ = 0;
36 return;
37 }
38
39 block_size_ = block_size > 0 ? block_size : 256;
40
41 // FFT size: next power of 2 >= block_size + ir_length - 1
42 // The real transform needs at least two points.
43 fft_size_ = 2;
44 while (fft_size_ < block_size_ + ir_length)
45 fft_size_ <<= 1;
46
47 fft_ = std::make_unique<FftT<SampleType>>(fft_size_);
48
49 // Transform the zero-padded IR into packed split form. Every block
50 // takes its input transform at scale 2 and inverts at scale 1 (both
51 // free on vDSP, whose real transform has a gain of 2), so the IR
52 // carries the remaining 1/(2N) that makes the round trip exact.
53 time_.assign(fft_size_, SampleType{0.0f});
54 std::copy_n(ir, ir_length, time_.begin());
55 ir_spectrum_.assign(fft_size_, SampleType{0.0f});
56 const int half = fft_size_ / 2;
57 detail::FftRealSplit::forward(*fft_, time_.data(), ir_spectrum_.data(),
58 ir_spectrum_.data() + half,
59 SampleType{1} / static_cast<SampleType>(2 * fft_size_));
60
61 // Buffers
62 input_buf_.assign(fft_size_, SampleType{0.0f});
63 output_buf_.assign(fft_size_, SampleType{0.0f});
64 overlap_.assign(fft_size_, SampleType{0.0f});
65 spectrum_.assign(fft_size_, SampleType{0.0f});
66 pos_ = 0;
67 }
68
69 // Process a single sample
70 SampleType process(SampleType input) {
71 if (!fft_ || block_size_ <= 0)
72 return input;
73
74 input_buf_[pos_] = input;
75 SampleType output = output_buf_[pos_];
76 ++pos_;
77
78 if (pos_ >= block_size_) {
79 process_block();
80 pos_ = 0;
81 }
82
83 return output;
84 }
85
86 // Process a buffer
87 void process(const SampleType* input, SampleType* output, int num_samples) {
88 for (int i = 0; i < num_samples; ++i)
89 output[i] = process(input[i]);
90 }
91
92 void reset() {
93 std::fill(input_buf_.begin(), input_buf_.end(), SampleType{0.0f});
94 std::fill(output_buf_.begin(), output_buf_.end(), SampleType{0.0f});
95 std::fill(overlap_.begin(), overlap_.end(), SampleType{0.0f});
96 pos_ = 0;
97 }
98
99 private:
100 std::unique_ptr<FftT<SampleType>> fft_;
101 int fft_size_ = 0;
102 int block_size_ = 0;
103 int pos_ = 0;
104
105 // Packed split spectra (detail::FftRealSplit): N/2 real parts, then
106 // N/2 imaginary parts.
107 std::vector<SampleType> ir_spectrum_;
108 std::vector<SampleType> spectrum_;
109 std::vector<SampleType> input_buf_;
110 std::vector<SampleType> output_buf_;
111 std::vector<SampleType> overlap_;
112 std::vector<SampleType> time_;
113
114 void process_block() {
115 // input_buf_ is already zero past block_size_ (cleared after every
116 // block), so it is the zero-padded real frame as it stands.
117 // Both spectra are Hermitian, so only DC..Nyquist carries
118 // information, and they stay in split form from transform to inverse.
119 const int half = fft_size_ / 2;
120 SampleType* re = spectrum_.data();
121 SampleType* im = re + half;
122 detail::FftRealSplit::forward(*fft_, input_buf_.data(), re, im, SampleType{2});
123 detail::multiply_packed_split(re, im, ir_spectrum_.data(), ir_spectrum_.data() + half, re,
124 im, half);
125 SampleType* time = time_.data();
126 detail::FftRealSplit::inverse(*fft_, re, im, time, SampleType{1});
127
128 // Overlap-add. The tail of an IR longer than one block reaches several
129 // blocks ahead, so the pending overlap is accumulated and shifted down
130 // by one block rather than replaced by this block's tail alone.
131 for (int i = 0; i < block_size_; ++i)
132 output_buf_[i] = time[i] + overlap_[i];
133 for (int i = block_size_; i < fft_size_; ++i)
134 overlap_[i - block_size_] = time[i] + overlap_[i];
135 std::fill(overlap_.end() - block_size_, overlap_.end(), SampleType{0.0f});
136
137 // Clear input buffer
138 std::fill(input_buf_.begin(), input_buf_.begin() + block_size_, SampleType{0.0f});
139 }
140};
141
144
145} // namespace pulp::signal
Definition overlap_add_convolver.hpp:21
void reset()
Definition overlap_add_convolver.hpp:92
void load_ir(const SampleType *ir, int ir_length, int block_size=0)
Definition overlap_add_convolver.hpp:24
SampleType process(SampleType input)
Definition overlap_add_convolver.hpp:70
void process(const SampleType *input, SampleType *output, int num_samples)
Definition overlap_add_convolver.hpp:87
void multiply_packed_split(const SampleType *a_re, const SampleType *a_im, const SampleType *b_re, const SampleType *b_im, SampleType *out_re, SampleType *out_im, int half) noexcept
Product of two packed split spectra of half = size() / 2 slots each (see FftRealSplit): bin k of out ...
Definition fft.hpp:1111
Definition additive_bank.hpp:218
ConvolverT< double > Convolver64
Definition overlap_add_convolver.hpp:143
@ i
Definition formant_filter_bank.hpp:20
ConvolverT< float > Convolver
Definition overlap_add_convolver.hpp:142
static void inverse(const FftT< SampleType > &fft, const SampleType *re, const SampleType *im, SampleType *output, SampleType scale)
Definition fft.hpp:1099
static void forward(const FftT< SampleType > &fft, const SampleType *input, SampleType *re, SampleType *im, SampleType scale)
Definition fft.hpp:1093