mirror of
https://codeberg.org/armin/ambivalence.git
synced 2026-09-01 12:20:48 +02:00
134 lines
4.5 KiB
C
134 lines
4.5 KiB
C
|
|
#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
|