ambivalence/Source/DSP/DelayMemory.h

134 lines
4.5 KiB
C
Raw Permalink Normal View History

2026-08-15 15:36:28 +02:00
#pragma once
#include <vector>
#include <cmath>
#include <algorithm>
#include <cstdint>
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<int>(powerOfTwoSize - 1);
allocOffset += powerOfTwoSize;
return ptr;
}
void clear() { std::fill(buffer.begin(), buffer.end(), 0.0f); }
private:
std::vector<float> 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<int>(delayInSamples);
float frac = delayInSamples - static_cast<float>(id);
// bitwise ops undefined behavior completely , uint32_t
uint32_t uWrite = static_cast<uint32_t>(writeIndex);
uint32_t uId = static_cast<uint32_t>(id);
uint32_t uMask = static_cast<uint32_t>(mask);
int readIdx1 = static_cast<int>((uWrite - uId) & uMask);
int readIdx2 = static_cast<int>((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<int>(delayInSamples);
float frac = delayInSamples - static_cast<float>(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<uint32_t>(writeIndex);
uint32_t uId = static_cast<uint32_t>(id);
uint32_t uMask = static_cast<uint32_t>(mask);
float xn = buffer[static_cast<int>((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