From c3c3918e81877c361131b17134c284efdde4d0ac Mon Sep 17 00:00:00 2001 From: Katherine Whitlock Date: Sun, 19 Jan 2025 16:08:33 -0500 Subject: [PATCH] Add new digital reverb model (#3277) --- .../{mutable => }/cosine_oscillator.hpp | 0 src/deluge/dsp/reverb/digital.hpp | 146 ++++++++++++++++++ .../dsp/reverb/{mutable => }/fx_engine.hpp | 0 .../{mutable/reverb.hpp => mutable.hpp} | 17 +- src/deluge/dsp/reverb/reverb.hpp | 14 +- src/deluge/gui/menu_item/reverb/hpf.h | 5 +- src/deluge/gui/menu_item/reverb/lpf.h | 4 +- src/deluge/gui/menu_item/reverb/model.h | 7 +- src/deluge/gui/menu_item/reverb/room_size.h | 1 + src/deluge/gui/menu_item/reverb/width.h | 1 + 10 files changed, 176 insertions(+), 19 deletions(-) rename src/deluge/dsp/reverb/{mutable => }/cosine_oscillator.hpp (100%) create mode 100644 src/deluge/dsp/reverb/digital.hpp rename src/deluge/dsp/reverb/{mutable => }/fx_engine.hpp (100%) rename src/deluge/dsp/reverb/{mutable/reverb.hpp => mutable.hpp} (95%) diff --git a/src/deluge/dsp/reverb/mutable/cosine_oscillator.hpp b/src/deluge/dsp/reverb/cosine_oscillator.hpp similarity index 100% rename from src/deluge/dsp/reverb/mutable/cosine_oscillator.hpp rename to src/deluge/dsp/reverb/cosine_oscillator.hpp diff --git a/src/deluge/dsp/reverb/digital.hpp b/src/deluge/dsp/reverb/digital.hpp new file mode 100644 index 0000000000..bc70ace97e --- /dev/null +++ b/src/deluge/dsp/reverb/digital.hpp @@ -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 . + */ +#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 in, std::span 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(std::numeric_limits::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(left_sum * static_cast(std::numeric_limits::max()) * 0xF); + + q31_t output_right = + static_cast(left_sum * static_cast(std::numeric_limits::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 diff --git a/src/deluge/dsp/reverb/mutable/fx_engine.hpp b/src/deluge/dsp/reverb/fx_engine.hpp similarity index 100% rename from src/deluge/dsp/reverb/mutable/fx_engine.hpp rename to src/deluge/dsp/reverb/fx_engine.hpp diff --git a/src/deluge/dsp/reverb/mutable/reverb.hpp b/src/deluge/dsp/reverb/mutable.hpp similarity index 95% rename from src/deluge/dsp/reverb/mutable/reverb.hpp rename to src/deluge/dsp/reverb/mutable.hpp index 93b47cce47..099baa5783 100644 --- a/src/deluge/dsp/reverb/mutable/reverb.hpp +++ b/src/deluge/dsp/reverb/mutable.hpp @@ -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(wet * static_cast(std::numeric_limits::max()) * 0xF); @@ -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(wet * static_cast(std::numeric_limits::max()) * 0xF); @@ -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 buffer_{}; diff --git a/src/deluge/dsp/reverb/reverb.hpp b/src/deluge/dsp/reverb/reverb.hpp index 2bf84e7a49..1f3a564b7f 100644 --- a/src/deluge/dsp/reverb/reverb.hpp +++ b/src/deluge/dsp/reverb/reverb.hpp @@ -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 #include #include @@ -13,6 +15,7 @@ class [[gnu::hot]] Reverb : reverb::Base { enum class Model { FREEVERB = 0, // Freeverb is the original MUTABLE, + DIGITAL, }; Reverb() @@ -28,6 +31,9 @@ class [[gnu::hot]] Reverb : reverb::Base { case Model::FREEVERB: reverb_.emplace(); break; + case Model::DIGITAL: + reverb_.emplace(); + break; case Model::MUTABLE: reverb_.emplace(); break; @@ -51,6 +57,9 @@ class [[gnu::hot]] Reverb : reverb::Base { case Model::MUTABLE: reverb_as().process(input, output); break; + case Model::DIGITAL: + reverb_as().process(input, output); + break; } } @@ -99,7 +108,8 @@ class [[gnu::hot]] Reverb : reverb::Base { private: std::variant< //< reverb::Freeverb, //< - reverb::Mutable //< + reverb::Mutable, //< + reverb::Digital //< > reverb_{}; diff --git a/src/deluge/gui/menu_item/reverb/hpf.h b/src/deluge/gui/menu_item/reverb/hpf.h index e88b0212ac..f4167be1b7 100644 --- a/src/deluge/gui/menu_item/reverb/hpf.h +++ b/src/deluge/gui/menu_item/reverb/hpf.h @@ -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" @@ -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 diff --git a/src/deluge/gui/menu_item/reverb/lpf.h b/src/deluge/gui/menu_item/reverb/lpf.h index b07996f723..4a6ddcfbe3 100644 --- a/src/deluge/gui/menu_item/reverb/lpf.h +++ b/src/deluge/gui/menu_item/reverb/lpf.h @@ -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" @@ -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 diff --git a/src/deluge/gui/menu_item/reverb/model.h b/src/deluge/gui/menu_item/reverb/model.h index 39c85273d0..c4d4ec5f12 100644 --- a/src/deluge/gui/menu_item/reverb/model.h +++ b/src/deluge/gui/menu_item/reverb/model.h @@ -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 @@ -16,10 +17,8 @@ class Model final : public Selection { deluge::vector 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 diff --git a/src/deluge/gui/menu_item/reverb/room_size.h b/src/deluge/gui/menu_item/reverb/room_size.h index fa11ce06ff..3b8a35e2bf 100644 --- a/src/deluge/gui/menu_item/reverb/room_size.h +++ b/src/deluge/gui/menu_item/reverb/room_size.h @@ -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: diff --git a/src/deluge/gui/menu_item/reverb/width.h b/src/deluge/gui/menu_item/reverb/width.h index b9591993e8..30e6609039 100644 --- a/src/deluge/gui/menu_item/reverb/width.h +++ b/src/deluge/gui/menu_item/reverb/width.h @@ -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: