79 void prepare(
const T* irData,
int irLength,
int headSize = 128)
82 if (irData ==
nullptr || irLength <= 0)
return;
85 while (headSize_ < std::clamp(headSize, 32, 512)) headSize_ <<= 1;
88 headLen_ = std::min(irLength, headSize_);
89 headRev_.assign(
static_cast<size_t>(headLen_), T(0));
90 for (
int i = 0; i < headLen_; ++i)
91 headRev_[
static_cast<size_t>(i)] = irData[headLen_ - 1 - i];
96 const int midEnd = std::min(irLength, kTailStart);
97 hasMid_ = irLength > headSize_;
100 mid_.prepare(headSize_, irData + headSize_, midEnd - headSize_);
101 midScratch_.assign(
static_cast<size_t>(headSize_), T(0));
105 hasTail_ = irLength > kTailStart;
109 const int tailLen = irLength - kTailStart;
110 numTailParts_ = (tailLen + kTailBlock - 1) / kTailBlock;
111 totalUnits_ = numTailParts_ + 3;
113 tailFft_ = std::make_unique<FFTReal<T>>(
static_cast<size_t>(2 * kTailBlock));
115 tailIR_.assign(
static_cast<size_t>(numTailParts_) * kTailSpec, T(0));
116 tailFdl_.assign(
static_cast<size_t>(numTailParts_) * kTailSpec, T(0));
117 tailAccum_.assign(
static_cast<size_t>(kTailSpec), T(0));
118 tailScratch_.assign(
static_cast<size_t>(2 * kTailBlock), T(0));
119 tailOut_.assign(
static_cast<size_t>(kTailOutSize), T(0));
121 std::vector<T> padded(
static_cast<size_t>(2 * kTailBlock), T(0));
122 for (
int p = 0; p < numTailParts_; ++p)
124 const int offset = kTailStart + p * kTailBlock;
125 const int len = std::min(kTailBlock, irLength - offset);
126 std::fill(padded.begin(), padded.end(), T(0));
127 std::copy_n(irData + offset, len, padded.begin());
128 tailFft_->forward(padded.data(), &tailIR_[
static_cast<size_t>(p) * kTailSpec]);
133 int ringSize = 2 * headSize_;
134 if (hasTail_) ringSize = std::max(ringSize, 4 * kTailBlock);
136 while (pow2 < ringSize) pow2 <<= 1;
137 inRing_.assign(
static_cast<size_t>(pow2), T(0));
147 if (!prepared_)
return;
148 std::fill(inRing_.begin(), inRing_.end(), T(0));
149 std::fill(tailFdl_.begin(), tailFdl_.end(), T(0));
150 std::fill(tailOut_.begin(), tailOut_.end(), T(0));
151 if (hasMid_) mid_.reset();
152 absPos_ =
static_cast<int64_t
>(inRing_.size());
155 taskPhase_ = totalUnits_;
165 void process(
const T* input, T* output,
int numSamples)
noexcept
170 std::memmove(output, input,
static_cast<size_t>(numSamples) *
sizeof(T));
175 while (i < numSamples)
177 int chunk = std::min(numSamples - i, headSize_);
179 chunk = std::min(chunk, kTailBlock - cyclePos_);
183 mid_.process(input + i, midScratch_.data(), chunk);
185 for (
int k = 0; k < chunk; ++k)
187 inRing_[
static_cast<size_t>(absPos_ & inMask_)] = input[i + k];
190 if (hasMid_) y += midScratch_[
static_cast<size_t>(k)];
193 auto& slot = tailOut_[
static_cast<size_t>(absPos_ & kTailOutMask)];
207 runTaskUnits(
static_cast<int>(
208 (
static_cast<int64_t
>(totalUnits_) * cyclePos_ + kTailBlock - 1) / kTailBlock));
209 if (cyclePos_ >= kTailBlock)
222 process(data, data, numSamples);
231 if (buffer.getNumChannels() > 0 && buffer.getNumSamples() > 0)
236 [[nodiscard]]
static constexpr int getLatency() noexcept {
return 0; }
239 [[nodiscard]]
int getHeadSize() const noexcept {
return headSize_; }
245 static constexpr int kTailBlock = 1024;
246 static constexpr int kTailStart = 2 * kTailBlock;
247 static constexpr int kTailSpec = 2 * kTailBlock + 2;
248 static constexpr int kTailOutSize = 8 * kTailBlock;
249 static constexpr int kTailOutMask = kTailOutSize - 1;
252 [[nodiscard]] T headDot() const noexcept
254 const int64_t start = absPos_ - headLen_ + 1;
255 const int s0 =
static_cast<int>(start & inMask_);
256 const int ringSize = inMask_ + 1;
257 const int first = std::min(headLen_, ringSize - s0);
259 T y =
simd::dotProduct(headRev_.data(), &inRing_[
static_cast<size_t>(s0)], first);
260 if (first < headLen_)
261 y +=
simd::dotProduct(headRev_.data() + first, inRing_.data(), headLen_ - first);
266 void launchTask() noexcept
268 taskStart_ = absPos_;
269 fdlIndex_ = (fdlIndex_ + 1) % numTailParts_;
281 void runTaskUnits(
int target)
noexcept
283 target = std::min(target, totalUnits_);
284 while (taskPhase_ < target)
286 const int unit = taskPhase_++;
289 const int64_t blockStart = taskStart_ - kTailBlock;
290 const int s0 =
static_cast<int>(blockStart & inMask_);
291 const int ringSize = inMask_ + 1;
292 const int first = std::min(kTailBlock, ringSize - s0);
293 std::copy_n(&inRing_[
static_cast<size_t>(s0)], first, tailScratch_.begin());
294 if (first < kTailBlock)
295 std::copy_n(inRing_.data(), kTailBlock - first, tailScratch_.begin() + first);
296 std::fill(tailScratch_.begin() + kTailBlock, tailScratch_.end(), T(0));
298 tailFft_->forward(tailScratch_.data(),
299 &tailFdl_[
static_cast<size_t>(fdlIndex_) * kTailSpec]);
300 std::fill(tailAccum_.begin(), tailAccum_.end(), T(0));
302 else if (unit <= numTailParts_)
304 const int p = unit - 1;
305 const int slot = (fdlIndex_ - p + numTailParts_) % numTailParts_;
307 &tailFdl_[
static_cast<size_t>(slot) * kTailSpec],
308 &tailIR_[
static_cast<size_t>(p) * kTailSpec],
311 else if (unit == numTailParts_ + 1)
313 tailFft_->inverse(tailAccum_.data(), tailScratch_.data());
317 const int64_t dst = taskStart_ + kTailBlock;
318 for (
int m = 0; m < 2 * kTailBlock; ++m)
319 tailOut_[
static_cast<size_t>((dst + m) & kTailOutMask)]
320 += tailScratch_[
static_cast<size_t>(m)];
326 bool prepared_ =
false;
330 std::vector<T> headRev_;
332 bool hasMid_ =
false;
334 std::vector<T> midScratch_;
336 bool hasTail_ =
false;
337 int numTailParts_ = 0;
339 std::unique_ptr<FFTReal<T>> tailFft_;
340 std::vector<T> tailIR_;
341 std::vector<T> tailFdl_;
342 std::vector<T> tailAccum_;
343 std::vector<T> tailScratch_;
344 std::vector<T> tailOut_;
346 std::vector<T> inRing_;
352 int64_t taskStart_ = 0;
void complexMulAccum(float *DSPARK_RESTRICT accum, const float *DSPARK_RESTRICT a, const float *DSPARK_RESTRICT b, int bins) noexcept
Complex multiply-accumulate over interleaved [re, im, ...] spectra.