Deluge Firmware 1.3.0
Build date: 2025.04.16
Loading...
Searching...
No Matches
fx_engine.hpp
1// Copyright 2023 Katherine Whitlock
2// Heavily based on Mutable Instrument's code, copyright 2014 Emilie Gillet
3// Base class for building reverb.
4
5#pragma once
6#include "cosine_oscillator.hpp"
7#include <algorithm>
8#include <array>
9#include <cstdint>
10#include <span>
11
12namespace deluge::dsp {
13constexpr float OnePole(float& out, float in, float coefficient) {
14 out += (coefficient) * ((in)-out);
15 return out;
16}
17
18template <typename T = float>
19constexpr T Interpolate(const T x0, const T x1, float fractional) {
20 return static_cast<T>(x0 + (x1 - x0) * fractional);
21}
22} // namespace deluge::dsp
23
24namespace deluge::dsp::reverb {
25constexpr static int32_t TAIL = -1;
26
27enum LFOIndex { LFO_1, LFO_2 };
28
29class FxEngine {
30public:
31 FxEngine(std::span<float> signal, std::array<float, 2> lfo_freqs)
32 : buffer_(signal), mask(buffer_.size() - 1), lfo_{lfo_freqs} {};
33 ~FxEngine() = default;
34
35 void Clear() {
36 std::fill(buffer_.begin(), buffer_.end(), 0);
37 write_ptr_ = 0;
38 }
39
40 //[gnu::always_inline]
41 void SetLFOFrequency(LFOIndex index, float frequency) { lfo_.SetFrequency(index, frequency * 32.0f); }
42
43 //[gnu::always_inline]
44 void Advance() {
45 --write_ptr_;
46 if (write_ptr_ < 0) {
47 write_ptr_ += buffer_.size();
48 }
49 }
50
51 //[gnu::always_inline]
52 float& at(size_t index) { return buffer_[(write_ptr_ + index) & mask]; }
53
54 //[gnu::always_inline]
55 void StepLFO() {
56 if ((write_ptr_ & 31) == 0) {
57 lfo_.Next();
58 }
59 }
60
61 //[gnu::always_inline]
62 float LFO(LFOIndex lfo) {
63 StepLFO();
64 switch (lfo) {
65 case LFO_1:
66 return lfo_.values()[0];
67 case LFO_2:
68 return lfo_.values()[1];
69 }
70 __builtin_unreachable();
71 }
72
73private:
74 int32_t write_ptr_ = 0;
75 std::span<float> buffer_;
77
78 size_t mask;
79
80public: /******************** INNER CLASSES ****************/
81 class Context {
82 public:
83 [[nodiscard]] constexpr float Get() const { return accumulator_; }
84 constexpr void Set(float value) { accumulator_ = value; }
85 constexpr void Add(float value) { accumulator_ += value; }
86 constexpr void Multiply(float value) { accumulator_ *= value; }
87 constexpr void Reset() { Set(0); }
88
89 constexpr void Lp(float& state, float coefficient) { accumulator_ = OnePole(state, accumulator_, coefficient); }
90
91 constexpr void Hp(float& state, float coefficient) {
92 accumulator_ -= OnePole(state, accumulator_, coefficient);
93 }
94
95 private:
96 float accumulator_ = 0.f;
97 };
98
99 struct DelayLine {
100 DelayLine(size_t length) : length(length) {};
101
102 // Store and Fetch
103 //[gnu::always_inline]
104 void Process(Context& c, size_t offset = 0) {
105 this->at(offset) = c.Get();
106 c.Set(this->at(length - offset));
107 }
108
109 //[gnu::always_inline]
110 float& at(int32_t index) {
111 if (index == TAIL) {
112 index = length - 1;
113 }
114 return engine_->at(this->base + index);
115 }
116
117 //[gnu::always_inline]
118 float Read(int32_t offset) {
119 // STATIC_ASSERT(d.base + d.length <= size, delay_memory_full);
120 return this->at(offset);
121 }
122
123 //[gnu::always_inline]
124 float Interpolate(float offset) {
125 auto offset_integral = static_cast<int32_t>(offset);
126 float offset_fractional = offset - static_cast<float>(offset_integral);
127 const float a = this->at(offset_integral);
128 const float b = this->at(offset_integral + 1);
129 return dsp::Interpolate(a, b, offset_fractional);
130 }
131
133 //[gnu::always_inline]
134 void Write(int32_t offset, float value) { this->at(offset) = value; }
135
136 public:
137 const size_t length = 0;
138 size_t base = 0;
139 FxEngine* engine_ = nullptr;
140 };
141
142 struct AllPass : public DelayLine {
143 AllPass(size_t length) : DelayLine(length) {};
144
145 //[gnu::always_inline]
146 float Read(Context& c, int32_t offset, float scale) {
147 const float r = DelayLine::Read(offset);
148 c.Add(r * scale);
149 return r;
150 }
151
152 // inline float Read(Context& c, float scale) { return Read(c, scale); }
153
154 //[gnu::always_inline]
155 void Write(Context& c, int32_t offset, float scale) {
156 DelayLine::Write(offset, c.Get());
157 c.Multiply(scale);
158 }
159
160 //[gnu::always_inline]
161 void Write(Context& c, float scale) { Write(c, (int32_t)0, scale); }
162
163 //[gnu::always_inline]
164 void Write(Context& c, int32_t offset, float scale, float input) {
165 Write(c, offset, scale);
166 c.Add(input);
167 }
168
169 //[gnu::always_inline]
170 void Write(Context& c, float scale, float input) { Write(c, 0, scale, input); }
171
173 //[gnu::always_inline]
174 float Interpolate(Context& c, float offset, float scale) {
175 // STATIC_ASSERT(d.base + d.length <= size, delay_memory_full);
176 const float r = DelayLine::Interpolate(offset);
177 c.Add(r * scale);
178 return r;
179 }
180
181 //[gnu::always_inline]
182 float Interpolate(Context& c, float offset, LFOIndex index, float amplitude, float scale) {
183 offset += amplitude * this->engine_->LFO(index);
184 return Interpolate(c, offset, scale);
185 }
186
187 //[gnu::always_inline]
188 void ProcessInterpolate(Context& c, float offset, LFOIndex index, float amplitude, float scale) {
189 const float read = this->Interpolate(c, offset, index, amplitude, scale);
190 this->Write(c, 0, -scale, read);
191 }
192
193 // Simple Schroeder allpass section
194 //
195 // ------[*-scale]-----,
196 // | ----------- ∨
197 // ---+-'-->| Delayline |--,-+--->
198 // ∧ ----------- |
199 // '------[*scale]------
200 //
201 //[gnu::always_inline]
202 void Process(Context& c, float scale) {
203 const float head = c.Get();
204 const float tail = this->at(TAIL);
205
206 const float feedback = head + (tail * scale);
207 this->at(0) = feedback; // feedback into delayline
208
209 const float feedforward = (feedback * -scale) + tail;
210 c.Set(feedforward); // output into pipeline
211
212 // c.Add(tail * scale);
213 // this->at(0) = c.Get();
214
215 // c.Multiply(-scale);
216 // c.Add(tail);
217 }
218 };
219
220 static void ConstructTopology(FxEngine& e, std::initializer_list<DelayLine*> delays) {
221 size_t base = 0;
222 for (DelayLine* d : delays) {
223 d->engine_ = &e;
224 d->base = base;
225 base += d->length + 1;
226 }
227 }
228};
229
230} // namespace deluge::dsp::reverb
Definition cosine_oscillator.hpp:10
Definition fx_engine.hpp:81
float Interpolate(Context &c, float offset, float scale)
Can be used in place of any AllPass::Read calls.
Definition fx_engine.hpp:174
void Write(int32_t offset, float value)
writes to the delayline
Definition fx_engine.hpp:134