Deluge Firmware
1.3.0
Build date: 2026.08.29
Toggle main menu visibility
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
12
namespace
deluge::dsp {
13
constexpr
float
OnePole(
float
& out,
float
in,
float
coefficient) {
14
out += (coefficient) * ((in)-out);
15
return
out;
16
}
17
18
template
<
typename
T =
float
>
19
constexpr
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
24
namespace
deluge::dsp::reverb {
25
constexpr
static
int32_t TAIL = -1;
26
27
enum
LFOIndex { LFO_1, LFO_2 };
28
29
class
FxEngine {
30
public
:
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
73
private
:
74
int32_t write_ptr_ = 0;
75
std::span<float> buffer_;
76
DualCosineOscillator
lfo_;
77
78
size_t
mask;
79
80
public
:
/******************** 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
DualCosineOscillator
Definition
cosine_oscillator.hpp:10
deluge::dsp::reverb::FxEngine::Context
Definition
fx_engine.hpp:81
deluge::dsp::reverb::FxEngine::AllPass::Interpolate
float Interpolate(Context &c, float offset, float scale)
Can be used in place of any AllPass::Read calls.
Definition
fx_engine.hpp:174
deluge::dsp::reverb::FxEngine::DelayLine::Write
void Write(int32_t offset, float value)
writes to the delayline
Definition
fx_engine.hpp:134