113 assert((fftSize & (fftSize - 1)) == 0 && fftSize >= kMinFftSize
114 &&
"FFT size must be a power of 2");
118 fftSize = std::clamp(fftSize, kMinFftSize, kMaxFftSize);
119 int pow2 = kMinFftSize;
120 while (pow2 < fftSize) pow2 <<= 1;
122 mask_ = fftSize_ - 1;
124 hopSize_ = (hopSize > 0) ? hopSize : fftSize_ / 2;
125 hopSize_ = std::clamp(hopSize_, 1, fftSize_ / 2);
126 if (fftSize_ % hopSize_ != 0) hopSize_ = fftSize_ / 2;
127 numBins_ = fftSize_ / 2 + 1;
129 fft_ = std::make_unique<FFTReal<T>>(fftSize_);
132 window_.resize(
static_cast<std::size_t
>(fftSize_));
134 for (
auto& w : window_) w = std::sqrt(w);
139 inputRing_.resize(
static_cast<std::size_t
>(nCh));
140 outputAccum_.resize(
static_cast<std::size_t
>(nCh));
141 inputPos_.assign(
static_cast<std::size_t
>(nCh), 0);
142 outputReadPos_.assign(
static_cast<std::size_t
>(nCh), 0);
145 accumMask_ = (fftSize_ * 2) - 1;
147 for (
int ch = 0; ch < nCh; ++ch)
149 inputRing_[
static_cast<std::size_t
>(ch)]
150 .assign(
static_cast<std::size_t
>(fftSize_), T(0));
151 outputAccum_[
static_cast<std::size_t
>(ch)]
152 .assign(
static_cast<std::size_t
>(fftSize_ * 2), T(0));
157 fftIn_.resize(
static_cast<std::size_t
>(fftSize_));
158 fftOut_.resize(
static_cast<std::size_t
>(fftSize_ + 2));
159 fftResult_.resize(
static_cast<std::size_t
>(fftSize_));
178 template <
typename Func>
181 if (!prepared_)
return;
184 const int nCh = std::min(buffer.getNumChannels(),
185 static_cast<int>(inputRing_.size()));
186 const int nS = buffer.getNumSamples();
198 const int chunk = std::min(nS - i, hopSize_ - hopCounter_);
202 for (
int ch = 0; ch < nCh; ++ch)
204 const T* data = buffer.getChannel(ch) + i;
205 auto& ring = inputRing_[
static_cast<std::size_t
>(ch)];
206 const int wp = inputPos_[
static_cast<std::size_t
>(ch)];
207 const int first = std::min(chunk, fftSize_ - wp);
208 std::copy_n(data, first, ring.data() + wp);
209 std::copy_n(data + first, chunk - first, ring.data());
210 inputPos_[
static_cast<std::size_t
>(ch)] = (wp + chunk) & mask_;
215 for (
int ch = 0; ch < nCh; ++ch)
217 T* data = buffer.getChannel(ch) + i;
218 T* acc = outputAccum_[
static_cast<std::size_t
>(ch)].data();
219 const int rp = outputReadPos_[
static_cast<std::size_t
>(ch)];
220 const int first = std::min(chunk, fftSize_ * 2 - rp);
221 for (
int k = 0; k < first; ++k)
223 data[k] = acc[rp + k];
226 for (
int k = first; k < chunk; ++k)
228 data[k] = acc[k - first];
229 acc[k - first] = T(0);
231 outputReadPos_[
static_cast<std::size_t
>(ch)] = (rp + chunk) & accumMask_;
235 hopCounter_ += chunk;
236 if (hopCounter_ >= hopSize_)
239 for (
int ch = 0; ch < nCh; ++ch)
240 processHop(ch, processFunc);
254 [[nodiscard]]
int getLatency() const noexcept {
return fftSize_; }
255 [[nodiscard]]
int getFFTSize() const noexcept {
return fftSize_; }
256 [[nodiscard]]
int getNumBins() const noexcept {
return numBins_; }
257 [[nodiscard]]
int getHopSize() const noexcept {
return hopSize_; }
262 for (
auto& ring : inputRing_) std::fill(ring.begin(), ring.end(), T(0));
263 for (
auto& acc : outputAccum_) std::fill(acc.begin(), acc.end(), T(0));
264 std::fill(inputPos_.begin(), inputPos_.end(), 0);
265 std::fill(outputReadPos_.begin(), outputReadPos_.end(), 0);
278 template <
typename Func>
279 void processHop(
int ch, Func& processFunc)
noexcept
283 const T* ring = inputRing_[
static_cast<std::size_t
>(ch)].data();
284 const T* win = window_.data();
285 const int readPos = inputPos_[
static_cast<std::size_t
>(ch)];
286 const int head = fftSize_ - readPos;
287 for (
int k = 0; k < head; ++k)
288 fftIn_[
static_cast<std::size_t
>(k)] = ring[readPos + k] * win[k];
289 for (
int k = head; k < fftSize_; ++k)
290 fftIn_[
static_cast<std::size_t
>(k)] = ring[k - head] * win[k];
293 fft_->forward(fftIn_.data(), fftOut_.data());
296 processFunc(fftOut_.data(), numBins_);
301 fft_->inverse(fftOut_.data(), fftResult_.data());
304 T* acc = outputAccum_[
static_cast<std::size_t
>(ch)].data();
305 const T* res = fftResult_.data();
306 const int writePos = outputReadPos_[
static_cast<std::size_t
>(ch)];
307 const int headOut = std::min(fftSize_, fftSize_ * 2 - writePos);
308 for (
int k = 0; k < headOut; ++k)
309 acc[writePos + k] += res[k] * win[k] * wolaNorm_;
310 for (
int k = headOut; k < fftSize_; ++k)
311 acc[k - headOut] += res[k] * win[k] * wolaNorm_;
322 void computeWolaNorm() noexcept
324 const int numOverlaps = fftSize_ / hopSize_;
326 for (
int pos = 0; pos < hopSize_; ++pos)
329 for (
int hop = 0; hop < numOverlaps; ++hop)
331 const int idx = pos + hop * hopSize_;
334 const T w = window_[
static_cast<std::size_t
>(idx)];
338 if (sumSq > maxSum) maxSum = sumSq;
340 wolaNorm_ = (maxSum > T(1e-10)) ? (T(1) / maxSum) : T(1);
344 static constexpr int kMinFftSize = 4;
345 static constexpr int kMaxFftSize = 1 << 20;
348 bool prepared_ =
false;
354 int accumMask_ = 4095;
356 std::unique_ptr<FFTReal<T>> fft_;
357 std::vector<T> window_;
360 std::vector<std::vector<T>> inputRing_;
361 std::vector<std::vector<T>> outputAccum_;
362 std::vector<int> inputPos_;
363 std::vector<int> outputReadPos_;
367 std::vector<T> fftIn_, fftOut_, fftResult_;