#pragma once #include #include #include #include namespace FDNReverb { // ----------------------------------------------------------------------------- // memory pool (Single-Large Buffer) // ----------------------------------------------------------------------------- class DelayMemoryPool { public: void allocate(size_t totalSamples) { buffer.assign(totalSamples, 0.0f); allocOffset = 0; } // pointer sized up to the next power of two (also outputs an index mask) float* requestMemory(size_t samplesNeeded, int& outMask) { size_t powerOfTwoSize = 1; while (powerOfTwoSize < samplesNeeded) powerOfTwoSize *= 2; if (allocOffset + powerOfTwoSize > buffer.size()) return nullptr; float* ptr = buffer.data() + allocOffset; outMask = static_cast(powerOfTwoSize - 1); allocOffset += powerOfTwoSize; return ptr; } void clear() { std::fill(buffer.begin(), buffer.end(), 0.0f); } private: std::vector buffer; size_t allocOffset{ 0 }; }; // ----------------------------------------------------------------------------- // interpolation // ----------------------------------------------------------------------------- class LinearDelayLine { public: void init(float* memory, int bitmask) { buffer = memory; mask = bitmask; writeIndex = 0; } // linear interpolation ( high band natural Air Absorption ) inline float read(float delayInSamples) const noexcept { int id = static_cast(delayInSamples); float frac = delayInSamples - static_cast(id); // bitwise ops undefined behavior completely , uint32_t uint32_t uWrite = static_cast(writeIndex); uint32_t uId = static_cast(id); uint32_t uMask = static_cast(mask); int readIdx1 = static_cast((uWrite - uId) & uMask); int readIdx2 = static_cast((uWrite - uId - 1) & uMask); return buffer[readIdx1] + frac * (buffer[readIdx2] - buffer[readIdx1]); } inline void write(float input) noexcept { buffer[writeIndex] = input; writeIndex = (writeIndex + 1) & mask; } private: float* buffer{ nullptr }; int mask{ 0 }; int writeIndex{ 0 }; }; // ----------------------------------------------------------------------------- // Thiran allpass interpolation (preserves the phase response) // linear interpolation would dull high-band decay (sinc(pi*f) rolloff), so use a Thiran allpass // which keeps |H(w)| = 1, preserving high-band clarity in the FDN feedback loops. // ----------------------------------------------------------------------------- class ThiranDelayLine { public: void init(float* memory, int bitmask) { buffer = memory; mask = bitmask; writeIndex = 0; thiranX1 = 0.0f; thiranY1 = 0.0f; } void resetState() noexcept { thiranX1 = 0.0f; thiranY1 = 0.0f; } // Thiran first-order allpass: y[n] = a*x[n] + x[n-1] - a*y[n-1] // a = (1-D)/(1+D), D = fractional delay inline float read(float delayInSamples) noexcept { int id = static_cast(delayInSamples); float frac = delayInSamples - static_cast(id); // clamp below to avoid instability as frac->0, a->1 frac = std::max(frac, 0.1f); const float a = (1.0f - frac) / (1.0f + frac); uint32_t uWrite = static_cast(writeIndex); uint32_t uId = static_cast(id); uint32_t uMask = static_cast(mask); float xn = buffer[static_cast((uWrite - uId) & uMask)]; float yn = a * xn + thiranX1 - a * thiranY1; thiranX1 = xn; thiranY1 = yn; return yn; } inline void write(float input) noexcept { buffer[writeIndex] = input; writeIndex = (writeIndex + 1) & mask; } private: float* buffer{ nullptr }; int mask{ 0 }; int writeIndex{ 0 }; float thiranX1{ 0.0f }; float thiranY1{ 0.0f }; }; } // namespace FDNReverb