Skip to content

Commit

Permalink
Add new digital reverb model (#3277)
Browse files Browse the repository at this point in the history
  • Loading branch information
stellar-aria authored Jan 19, 2025
1 parent d37eb1f commit c3c3918
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 19 deletions.
File renamed without changes.
146 changes: 146 additions & 0 deletions src/deluge/dsp/reverb/digital.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* Copyright © 2024 Katherine Whitlock
*
* This file is part of The Synthstrom Audible Deluge Firmware.
*
* The Synthstrom Audible Deluge Firmware is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
#include "definitions_cxx.hpp"
#include "mutable.hpp"

namespace deluge::dsp::reverb {

/// @brief The Griesinger topology model from Part 1 of Effect Design by John Dattorro,
/// classically based on the famous Lexicon 224 Digital Reverb
class Digital : public Mutable {
constexpr static float kRatio = 29761.f / kSampleRate; // Lexicon sample rate to Deluge sample rate

constexpr static size_t max_excursion = 16.f * kRatio;

public:
void Process(std::span<q31_t> in, std::span<StereoSample> output) {
typename FxEngine::Context c;

typename FxEngine::AllPass ap1(142 * kRatio);
typename FxEngine::AllPass ap2(107 * kRatio);
typename FxEngine::AllPass ap3(379 * kRatio);
typename FxEngine::AllPass ap4(277 * kRatio);

typename FxEngine::AllPass dap1a((672 * kRatio) + max_excursion);
typename FxEngine::DelayLine del1a(4453 * kRatio);
typename FxEngine::AllPass dap1b(1800 * kRatio);
typename FxEngine::DelayLine del1b(3720 * kRatio);

typename FxEngine::AllPass dap2a((908 * kRatio) + max_excursion);
typename FxEngine::DelayLine del2a(4217 * kRatio);
typename FxEngine::AllPass dap2b(2656 * kRatio);
typename FxEngine::DelayLine del2b(3163 * kRatio);

FxEngine::ConstructTopology(engine_, {&ap1, &ap2, &ap3, &ap4, //<
&dap1a, &del1a, &dap1b, &del1b, //<
&dap2a, &del2a, &dap2b, &del2b});

const float kdecay = reverb_time_; // 0.5f
const float kid1 = 0.750f; // input diffusion 1
const float kid2 = 0.625f; // input diffusion 2
const float kdd1 = 0.70f; // decay diffusion 1
const float kdd2 = std::clamp(kdecay + 0.15f, 0.25f, 0.5f); // decay diffusion 2

const float kdamp = lp_; // 1.f - 0.0005f; // damping
const float kbandwidth = 0.9995f;

const float gain = input_gain_;

float lp_1 = lp_decay_1_;
float lp_2 = lp_decay_2_;
float lp_band = lp_band_;

for (size_t frame = 0; frame < in.size(); ++frame) {
engine_.Advance();

const float input_sample = in[frame] / static_cast<float>(std::numeric_limits<int32_t>::max());
c.Set(input_sample); // * gain);

c.Lp(lp_band, kbandwidth);

// Diffuse through 4 allpasses.
ap1.Process(c, kid1);
ap2.Process(c, kid1);
ap3.Process(c, kid2);
ap4.Process(c, kid2);
float apout = c.Get();

// Main reverb loop.
c.Set(apout);
dap1a.Interpolate(c, 672.0f * kRatio, LFO_2, max_excursion, -kdd1);
del1a.Process(c);
c.Lp(lp_1, kdamp); // damping
c.Multiply(kdecay);
dap1b.Process(c, kdd2);
del1b.Process(c);
c.Multiply(kdecay);
c.Add(apout);
dap2a.Write(c, kdd2);

c.Set(apout);
dap2a.Interpolate(c, 908.0f * kRatio, LFO_1, max_excursion, -kdd1);
del2a.Process(c);
c.Lp(lp_1, kdamp); // damping
c.Multiply(kdecay);
dap2b.Process(c, kdd2);
del2b.Process(c);
c.Multiply(kdecay);
c.Add(apout);
dap1a.Write(c, kdd1);

float left_sum = 0;
left_sum += 0.6f * del2a.at(266 * kRatio);
left_sum += 0.6f * del2a.at(2974 * kRatio);
left_sum -= 0.6f * dap2b.at(1913 * kRatio);
left_sum += 0.6f * del2b.at(1996 * kRatio);
left_sum -= 0.6f * del1a.at(1990 * kRatio);
left_sum -= 0.6f * dap1b.at(187 * kRatio);
left_sum -= 0.6f * del1b.at(1066 * kRatio);
left_sum = left_sum - dsp::OnePole(hp_l_, left_sum, hp_cutoff_);
left_sum = dsp::OnePole(lp_l_, left_sum, lp_cutoff_);

float right_sum = 0;
right_sum += 0.6f * del1a.at(353 * kRatio);
right_sum += 0.6f * del1a.at(3627 * kRatio);
right_sum -= 0.6f * dap1b.at(1228 * kRatio);
right_sum += 0.6f * del1b.at(2673 * kRatio);
right_sum -= 0.6f * del2a.at(2111 * kRatio);
right_sum -= 0.6f * dap2b.at(335 * kRatio);
right_sum -= 0.6f * del2b.at(121 * kRatio);
right_sum = right_sum - dsp::OnePole(hp_l_, right_sum, hp_cutoff_);
right_sum = dsp::OnePole(lp_l_, right_sum, lp_cutoff_);

q31_t output_left =
static_cast<int32_t>(left_sum * static_cast<float>(std::numeric_limits<uint32_t>::max()) * 0xF);

q31_t output_right =
static_cast<int32_t>(left_sum * static_cast<float>(std::numeric_limits<uint32_t>::max()) * 0xF);

// Mix
output[frame].l += multiply_32x32_rshift32_rounded(output_left, getPanLeft());
output[frame].r += multiply_32x32_rshift32_rounded(output_right, getPanRight());
}

lp_decay_1_ = lp_1;
lp_decay_2_ = lp_2;
lp_band_ = lp_band;
}

private:
float lp_band_;
};
} // namespace deluge::dsp::reverb
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,9 @@ class Mutable : public Base {
dap1b.Process(c, kap);
del1.Write(c, 2.0f);
wet = c.Get();
dsp::OnePole(hp_r_, wet, hp_cutoff_);
wet = wet - hp_r_;
dsp::OnePole(lp_r_, wet, lp_cutoff_);
wet = lp_r_;
wet = wet - dsp::OnePole(hp_r_, wet, hp_cutoff_);
;
wet = dsp::OnePole(lp_r_, wet, lp_cutoff_);

auto output_right =
static_cast<int32_t>(wet * static_cast<float>(std::numeric_limits<uint32_t>::max()) * 0xF);
Expand All @@ -99,10 +98,10 @@ class Mutable : public Base {
dap2b.Process(c, kap);
del2.Write(c, 2.0f);
wet = c.Get();
dsp::OnePole(hp_l_, wet, hp_cutoff_);
wet = wet - hp_l_;
dsp::OnePole(lp_l_, wet, lp_cutoff_);
wet = lp_l_;
wet = wet - dsp::OnePole(hp_l_, wet, hp_cutoff_);
;
wet = dsp::OnePole(lp_l_, wet, lp_cutoff_);
;

auto output_left =
static_cast<int32_t>(wet * static_cast<float>(std::numeric_limits<uint32_t>::max()) * 0xF);
Expand Down Expand Up @@ -154,7 +153,7 @@ class Mutable : public Base {

[[nodiscard]] float getLPF() const override { return lp_cutoff_val_; }

private:
protected:
static constexpr float sample_rate = kSampleRate;

std::array<float, kBufferSize> buffer_{};
Expand Down
14 changes: 12 additions & 2 deletions src/deluge/dsp/reverb/reverb.hpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#pragma once
#include "base.hpp"
#include "deluge/dsp/reverb/reverb.hpp"
#include "digital.hpp"
#include "freeverb/freeverb.hpp"
#include "mutable/reverb.hpp"
#include "mutable.hpp"
#include <algorithm>
#include <cstdint>
#include <variant>
Expand All @@ -13,6 +15,7 @@ class [[gnu::hot]] Reverb : reverb::Base {
enum class Model {
FREEVERB = 0, // Freeverb is the original
MUTABLE,
DIGITAL,
};

Reverb()
Expand All @@ -28,6 +31,9 @@ class [[gnu::hot]] Reverb : reverb::Base {
case Model::FREEVERB:
reverb_.emplace<reverb::Freeverb>();
break;
case Model::DIGITAL:
reverb_.emplace<reverb::Digital>();
break;
case Model::MUTABLE:
reverb_.emplace<reverb::Mutable>();
break;
Expand All @@ -51,6 +57,9 @@ class [[gnu::hot]] Reverb : reverb::Base {
case Model::MUTABLE:
reverb_as<Mutable>().process(input, output);
break;
case Model::DIGITAL:
reverb_as<Digital>().process(input, output);
break;
}
}

Expand Down Expand Up @@ -99,7 +108,8 @@ class [[gnu::hot]] Reverb : reverb::Base {
private:
std::variant< //<
reverb::Freeverb, //<
reverb::Mutable //<
reverb::Mutable, //<
reverb::Digital //<
>
reverb_{};

Expand Down
5 changes: 3 additions & 2 deletions src/deluge/gui/menu_item/reverb/hpf.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/
#pragma once
#include "definitions_cxx.hpp"
#include "dsp/reverb/mutable/reverb.hpp"
#include "dsp/reverb/mutable.hpp"
#include "dsp/reverb/reverb.hpp"
#include "gui/l10n/strings.h"
#include "gui/menu_item/integer.h"
Expand All @@ -33,7 +33,8 @@ class HPF final : public Integer {
[[nodiscard]] int32_t getMaxValue() const override { return kMaxMenuValue; }

bool isRelevant(ModControllableAudio* modControllable, int32_t whichThing) override {
return (AudioEngine::reverb.getModel() == dsp::Reverb::Model::MUTABLE);
auto model = AudioEngine::reverb.getModel();
return (model == dsp::Reverb::Model::MUTABLE) || (model == dsp::Reverb::Model::DIGITAL);
}
};
} // namespace deluge::gui::menu_item::reverb
4 changes: 2 additions & 2 deletions src/deluge/gui/menu_item/reverb/lpf.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*/
#pragma once
#include "definitions_cxx.hpp"
#include "dsp/reverb/mutable/reverb.hpp"
#include "dsp/reverb/reverb.hpp"
#include "gui/l10n/strings.h"
#include "gui/menu_item/integer.h"
Expand All @@ -33,7 +32,8 @@ class LPF final : public Integer {
[[nodiscard]] int32_t getMaxValue() const override { return kMaxMenuValue; }

bool isRelevant(ModControllableAudio* modControllable, int32_t whichThing) override {
return (AudioEngine::reverb.getModel() == dsp::Reverb::Model::MUTABLE);
auto model = AudioEngine::reverb.getModel();
return (model == dsp::Reverb::Model::MUTABLE) || (model == dsp::Reverb::Model::DIGITAL);
}
};
} // namespace deluge::gui::menu_item::reverb
7 changes: 3 additions & 4 deletions src/deluge/gui/menu_item/reverb/model.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include "dsp/reverb/reverb.hpp"
#include "gui/l10n/l10n.h"
#include "gui/menu_item/selection.h"
#include "processing/engines/audio_engine.h"
#include <string_view>
Expand All @@ -16,10 +17,8 @@ class Model final : public Selection {

deluge::vector<std::string_view> getOptions(OptType optType) override {
using enum l10n::String;
return {
l10n::getView(STRING_FOR_FREEVERB),
l10n::getView(STRING_FOR_MUTABLE),
};
return {l10n::getView(STRING_FOR_FREEVERB), l10n::getView(STRING_FOR_MUTABLE),
l10n::getView(STRING_FOR_DIGITAL)};
}
};
} // namespace deluge::gui::menu_item::reverb
1 change: 1 addition & 0 deletions src/deluge/gui/menu_item/reverb/room_size.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class RoomSize final : public Integer {
[[nodiscard]] std::string_view getName() const override {
using enum l10n::String;
switch (AudioEngine::reverb.getModel()) {
case dsp::Reverb::Model::DIGITAL:
case dsp::Reverb::Model::MUTABLE:
return l10n::getView(STRING_FOR_TIME);
default:
Expand Down
1 change: 1 addition & 0 deletions src/deluge/gui/menu_item/reverb/width.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class Width final : public Integer {
[[nodiscard]] std::string_view getName() const override {
using enum l10n::String;
switch (AudioEngine::reverb.getModel()) {
case dsp::Reverb::Model::DIGITAL:
case dsp::Reverb::Model::MUTABLE:
return l10n::getView(STRING_FOR_DIFFUSION);
default:
Expand Down

0 comments on commit c3c3918

Please sign in to comment.