// nisps/dsp/delay.hpp — static-buffer delay lines. // // Delay — fixed-length feedback delay (maximilian's maxiDelayline) // DynamicDelay — power-of-two ring with fractional-tap read + smoothed // delay-time (used by VerbFX's three delay lanes). // // Sample-rate is opaque to these primitives; delay sizes are in samples. // Convert from milliseconds at the engine layer (`samples = ms * sr / 1000`). #pragma once #include #include #include #include "../core/perf.hpp" namespace nisps { // Fixed-length feedback delay. `play()` reads the head, mixes feedback into the // slot, and advances. Behavior matches maxiDelayline::play(). template class Delay { public: Delay() noexcept { clear(); } void clear() noexcept { std::memset(memory_.data(), 0, N * sizeof(float)); phase_ = 0u; } NISPS_HOT NISPS_FORCE_INLINE float play(float input, std::size_t size, float feedback) noexcept { if (size >= N) [[unlikely]] return 0.f; if (phase_ >= size) [[unlikely]] phase_ = 0u; const float out = memory_[phase_]; memory_[phase_] = (memory_[phase_] * feedback) + input; ++phase_; return out; } static constexpr std::size_t capacity() noexcept { return N; } private: alignas(16) std::array memory_{}; std::size_t phase_ = 0u; }; // Power-of-two ring with fractional-tap read; smooths the read offset between // frames so changes in `target_size` glitch-freely. Mirrors maximilian's // `DynamicDelay`. template class DynamicDelay { static_assert((N & (N - 1u)) == 0u, "DynamicDelay capacity must be power of two"); static constexpr std::size_t kMask = N - 1u; public: DynamicDelay() noexcept : smoothed_size_(static_cast(N)) {} void clear() noexcept { std::memset(line_.data(), 0, N * sizeof(float)); write_index_ = 0u; smoothed_size_ = static_cast(N); } void set_smooth_coeff(float c) noexcept { smooth_coeff_ = c; } NISPS_HOT NISPS_FORCE_INLINE float read(float target_size) noexcept { smoothed_size_ = smoothed_size_ * smooth_coeff_ + target_size * (1.f - smooth_coeff_); float read_pos = static_cast(write_index_) - smoothed_size_; if (read_pos < 0.f) read_pos += static_cast(N); const std::size_t i1 = static_cast(read_pos); const float frac = read_pos - static_cast(i1); const std::size_t i2 = (i1 + 1u) & kMask; return line_[i1] + frac * (line_[i2] - line_[i1]); } NISPS_HOT NISPS_FORCE_INLINE void write(float input) noexcept { line_[write_index_] = input; write_index_ = (write_index_ + 1u) & kMask; } static constexpr std::size_t capacity() noexcept { return N; } private: std::array line_{}; std::size_t write_index_ = 0u; float smoothed_size_; float smooth_coeff_ = 0.997f; }; } // namespace nisps