31 void prepare(std::size_t capacity_samples) { line_.prepare(capacity_samples); }
34 double process(
double x,
double delay_samples,
double gain)
noexcept {
35 const double delayed = line_.read(delay_samples);
36 const double y = -gain * x + delayed;
37 line_.push(x + gain * y);
41 double max_delay() const noexcept {
return line_.max_delay(); }
67 delay_slew_coefficient_ =
71 const auto capacity =
static_cast<std::size_t
>(
74 for (
auto& line : delays_) line.prepare(capacity);
75 for (
auto& ap : allpasses_) ap.prepare(capacity);
80 for (
auto& line : delays_) line.reset();
81 for (
auto& ap : allpasses_) ap.reset();
82 for (
auto& d : damping_) d.reset();
83 for (
auto& g : low_guard_) g.reset();
84 feedback_ = {0.0, 0.0};
85 mod_phase_ = {0.0, 0.0};
86 snap_delays_on_update_ =
true;
89 void update(
double character_amount)
noexcept {
96 const double damp_hz =
98 for (
auto& d : damping_) d.set_cutoff(damp_hz, sample_rate_);
112 output_scale_ = kTankHeadroom *
113 std::sqrt(std::max(1.0 - decay_ * decay_, 1
e-4)) /
114 std::sqrt(1.0 + kTapGain * kTapGain *
115 static_cast<double>(kTapCount));
117 const double to_samples = 0.001 * sample_rate_ * size;
126 if (snap_delays_on_update_) {
127 delay_len_ = delay_len_target_;
128 ap_len_ = ap_len_target_;
129 snap_delays_on_update_ =
false;
136 bool active() const noexcept {
return mix_ > 0.0; }
137 double mix() const noexcept {
return mix_; }
139 double process(
double x,
double decorrelation)
noexcept {
140 advance_delay_slew();
143 const double in_a = x + feedback_[1] * decay_;
144 const double in_b = x + feedback_[0] * decay_;
147 const double a = run_branch(0, in_a, decorrelation, taps);
148 const double b = run_branch(1, in_b, decorrelation, taps);
153 return output_scale_ * (0.5 * (
a + b) + kTapGain * taps);
157 for (std::size_t m = 0; m < mod_phase_.size(); ++m) {
159 if (mod_phase_[m] >= 1.0) mod_phase_[m] -= 1.0;
164 static constexpr double kTapGain = 0.30;
165 static constexpr std::size_t kTapCount = 4;
166 static constexpr double kTankHeadroom = 1.0;
168 void advance_delay_slew() noexcept {
169 for (std::size_t
i = 0;
i < delay_len_.size(); ++
i) {
170 delay_len_[
i] = delay_len_target_[
i] +
171 delay_slew_coefficient_ * (delay_len_[
i] - delay_len_target_[
i]);
173 ap_len_target_[
i] + delay_slew_coefficient_ * (ap_len_[
i] - ap_len_target_[
i]);
179 double run_branch(std::size_t branch,
double x,
double decorrelation,
180 double& taps)
noexcept {
181 const std::size_t i0 = branch * 2u;
182 const std::size_t i1 = i0 + 1u;
184 const double sweep = mod_depth_ * decorrelation *
185 std::sin(2.0 *
kPi * mod_phase_[branch]);
186 double y = allpasses_[i0].process(
187 x, clamp_delay(ap_len_[i0] + sweep, allpasses_[i0].max_delay()),
191 const double len0 = clamp_delay(delay_len_[i0], delays_[i0].max_delay());
194 delays_[i0].max_delay()));
195 y = delays_[i0].read(len0);
197 y = damping_[branch].lowpass(y);
200 y = low_guard_[branch].highpass(y);
201 y = allpasses_[i1].process(y, clamp_delay(ap_len_[i1], allpasses_[i1].max_delay()),
204 return delays_[i1].read(clamp_delay(delay_len_[i1], delays_[i1].max_delay()));
207 static double clamp_delay(
double d,
double max_delay)
noexcept {
208 return std::clamp(d, 1.0, max_delay);
211 std::array<FractionalDelayLine, 4> delays_{};
212 std::array<ModulatedAllpass, 4> allpasses_{};
213 std::array<OnePole, 2> damping_{};
214 std::array<OnePole, 2> low_guard_{};
215 std::array<double, 4> delay_len_ = {1.0, 1.0, 1.0, 1.0};
216 std::array<double, 4> ap_len_ = {1.0, 1.0, 1.0, 1.0};
217 std::array<double, 4> delay_len_target_ = {1.0, 1.0, 1.0, 1.0};
218 std::array<double, 4> ap_len_target_ = {1.0, 1.0, 1.0, 1.0};
219 std::array<double, 2> feedback_ = {0.0, 0.0};
220 std::array<double, 2> mod_phase_ = {0.0, 0.0};
221 double sample_rate_ = 48000.0;
224 double output_scale_ = 0.0;
225 double mod_depth_ = 0.0;
226 double delay_slew_coefficient_ = 0.0;
227 bool snap_delays_on_update_ =
true;
236 for (std::size_t
i = 0;
i < kStageCount; ++
i) {
240 const auto capacity =
static_cast<std::size_t
>(
242 stages_[
i].prepare(capacity);
247 for (
auto& s : stages_) s.reset();
249 mod_phase_ = {0.0, 0.0};
255 void update(
double character_amount)
noexcept {
256 const double size_scale =
258 const double gain_scale =
260 for (std::size_t
i = 0;
i < kStageCount; ++
i) {
262 delays_[
i] = round_to_odd(samples);
265 tank_.update(character_amount);
273 for (std::size_t
i = 0;
i < kStageCount; ++
i) {
274 double delay = delays_[
i];
275 if (
i == 0 ||
i == 2) {
276 const std::size_t m = (
i == 0) ? 0
u : 1u;
278 std::sin(2.0 *
kPi * mod_phase_[m]);
280 delay = std::clamp(
delay, 1.0, stages_[
i].max_delay());
281 x = stages_[
i].process(x,
delay, gains_[
i]);
295 if (!tank_.active())
return x;
296 const double cloud = tank_.process(x, decorrelation);
297 return x + tank_.mix() * (cloud - x);
301 double process(
double x,
double decorrelation)
noexcept {
307 tank_.tick_modulation();
308 for (std::size_t m = 0; m < mod_phase_.size(); ++m) {
310 if (mod_phase_[m] >= 1.0) mod_phase_[m] -= 1.0;
315 static constexpr std::size_t kStageCount = 4;
317 static double round_to_odd(
double samples)
noexcept {
318 auto n =
static_cast<long long>(std::llround(samples));
320 if ((n & 1LL) == 0LL) n += 1;
321 return static_cast<double>(n);
325 std::array<ModulatedAllpass, kStageCount> stages_{};
326 std::array<double, kStageCount> delays_ = {1.0, 1.0, 1.0, 1.0};
327 std::array<double, kStageCount> gains_ = {0.0, 0.0, 0.0, 0.0};
328 std::array<double, 2> mod_phase_ = {0.0, 0.0};
329 double sample_rate_ = 48000.0;
The four-stage chain for one channel.
Definition diffusion.hpp:231
double process_diffuser(double x, double decorrelation) noexcept
The bare allpass diffuser — the only part safe to RECIRCULATE.
Definition diffusion.hpp:272
void prepare(double fs)
Definition diffusion.hpp:233
double process(double x, double decorrelation) noexcept
One-pass smear for the cross-character output blend: diffuser + cloud.
Definition diffusion.hpp:301
void reset()
Definition diffusion.hpp:246
void update(double character_amount) noexcept
Recompute stage delays and gains for a character amount.
Definition diffusion.hpp:255
void tick_modulation() noexcept
Advance the two anti-metallic LFOs by one sample.
Definition diffusion.hpp:306
double process_cloud(double x, double decorrelation) noexcept
The reverb tank, crossfaded in — OUTPUT ONLY, never in the loop.
Definition diffusion.hpp:294
Definition primitives.hpp:177
One modulated Schroeder allpass: y[n] = −g·x[n] + v[n−d], v[n] = x[n] + g·y[n].
Definition diffusion.hpp:29
void reset()
Definition diffusion.hpp:32
double max_delay() const noexcept
Definition diffusion.hpp:41
double process(double x, double delay_samples, double gain) noexcept
Definition diffusion.hpp:34
void prepare(std::size_t capacity_samples)
Definition diffusion.hpp:31
Dattorro's figure-of-eight reverb tank, one per channel.
Definition diffusion.hpp:63
void prepare(double fs)
Definition diffusion.hpp:65
double process(double x, double decorrelation) noexcept
Definition diffusion.hpp:139
bool active() const noexcept
Zero mix means the tank contributes nothing at all — the character is then the bare allpass diffuser,...
Definition diffusion.hpp:136
void update(double character_amount) noexcept
Definition diffusion.hpp:89
void tick_modulation() noexcept
Definition diffusion.hpp:156
double mix() const noexcept
Definition diffusion.hpp:137
void reset()
Definition diffusion.hpp:79
constexpr std::array< double, 4 > kDiffusionStageGain
Definition tables.hpp:248
constexpr std::array< double, 3 > kDiffusionTankAxis
Definition tables.hpp:279
constexpr std::array< double, 3 > kDiffusionGainScale
Definition tables.hpp:252
constexpr std::array< double, 3 > kDiffusionTankModDepth
Tank modulation — this is what makes the cloud granular rather than static.
Definition tables.hpp:311
constexpr std::array< double, 3 > kDiffusionTankSizeScale
Tank size scaler and recirculation decay by character amount.
Definition tables.hpp:295
constexpr double kDiffusionGainMax
Stability / metallicity guard.
Definition tables.hpp:256
constexpr std::array< double, 2 > kDiffusionTankTapFraction
Extra scattered taps read at their own modulated offsets, as a fraction of the branch delay.
Definition tables.hpp:315
constexpr std::array< double, 3 > kDiffusionTankMix
How much of the character's output is the tank rather than the bare diffuser.
Definition tables.hpp:301
constexpr double kPi
Definition primitives.hpp:38
constexpr std::array< double, 4 > kDiffusionStageMs
Definition tables.hpp:246
constexpr double kDiffusionTankDelaySlewMs
Live character automation moves tank taps toward their new positions over a short continuous glide in...
Definition tables.hpp:326
constexpr std::array< double, 2 > kDiffusionTankApAMs
The two decay-diffuser allpasses per branch. [design parameters].
Definition tables.hpp:286
constexpr std::array< double, 3 > kDiffusionTankDampHz
In-tank damping.
Definition tables.hpp:305
constexpr std::array< double, 2 > kDiffusionTankApBMs
Definition tables.hpp:287
constexpr double kDiffusionTankApGain1
Definition tables.hpp:288
double interpolate_knots(const std::array< double, N > &axis, const std::array< double, N > &values, double c) noexcept
Definition primitives.hpp:125
constexpr std::array< double, 2 > kDiffusionTankDelayBMs
Definition tables.hpp:284
constexpr double kDiffusionTankApGain2
Definition tables.hpp:289
constexpr std::array< double, 2 > kDiffusionTankModRatesHz
Definition tables.hpp:312
constexpr std::array< double, 3 > kDiffusionSizeScale
Definition tables.hpp:251
constexpr std::array< double, 2 > kDiffusionModRatesHz
Definition tables.hpp:261
constexpr std::array< double, 3 > kDiffusionAxis
Definition tables.hpp:250
constexpr double kDiffusionTankLowGuardHz
In-tank low cut.
Definition tables.hpp:322
constexpr double kDiffusionModDepthSamples
Anti-metallic modulation of stages 1 and 3 (Dattorro modulates his diffusers).
Definition tables.hpp:260
constexpr std::array< double, 3 > kDiffusionTankDecay
Definition tables.hpp:296
constexpr std::array< double, 2 > kDiffusionTankDelayAMs
Branch delays in ms, mutually prime-ish so their comb nulls never align.
Definition tables.hpp:283
@ delay
Definition envelope.hpp:57
@ a
Definition formant_filter_bank.hpp:20
@ u
Definition formant_filter_bank.hpp:20
@ i
Definition formant_filter_bank.hpp:20
@ e
Definition formant_filter_bank.hpp:20