From 2c35f4dcb74d4166f20750d1756572b6b84ef1a4 Mon Sep 17 00:00:00 2001 From: Paul Walker Date: Wed, 10 Apr 2024 22:20:48 -0400 Subject: [PATCH 1/2] Add the "SUrgeBiquad" voice effect --- .../sst/voice-effects/filter/SurgeBiquads.h | 219 ++++++++++++++++++ tests/create-voice-effect.cpp | 6 + 2 files changed, 225 insertions(+) create mode 100644 include/sst/voice-effects/filter/SurgeBiquads.h diff --git a/include/sst/voice-effects/filter/SurgeBiquads.h b/include/sst/voice-effects/filter/SurgeBiquads.h new file mode 100644 index 0000000..d6ad2e6 --- /dev/null +++ b/include/sst/voice-effects/filter/SurgeBiquads.h @@ -0,0 +1,219 @@ +/* + * sst-effects - an open source library of audio effects + * built by Surge Synth Team. + * + * Copyright 2018-2023, various authors, as described in the GitHub + * transaction log. + * + * sst-effects is released under the GNU General Public Licence v3 + * or later (GPL-3.0-or-later). The license is found in the "LICENSE" + * file in the root of this repository, or at + * https://www.gnu.org/licenses/gpl-3.0.en.html + * + * The majority of these effects at initiation were factored from + * Surge XT, and so git history prior to April 2023 is found in the + * surge repo, https://github.com/surge-synthesizer/surge + * + * All source in sst-effects available at + * https://github.com/surge-synthesizer/sst-effects + */ + +#ifndef INCLUDE_SST_VOICE_EFFECTS_FILTER_SURGEBIQUADS_H +#define INCLUDE_SST_VOICE_EFFECTS_FILTER_SURGEBIQUADS_H + +#include "sst/basic-blocks/params/ParamMetadata.h" +#include "sst/basic-blocks/dsp/QuadratureOscillators.h" + +#include "../VoiceEffectCore.h" + +#include +#include + +#include "sst/basic-blocks/mechanics/block-ops.h" + +namespace sst::voice_effects::filter +{ +template struct SurgeBiquads : core::VoiceEffectTemplateBase +{ + static constexpr const char *effectName{"Surge Biquads"}; + + static constexpr int numFloatParams{2}; + static constexpr int numIntParams{1}; + + SurgeBiquads() : core::VoiceEffectTemplateBase(), bq(this) + { + std::fill(mLastIParam.begin(), mLastIParam.end(), -1); + std::fill(mLastParam.begin(), mLastParam.end(), -188888.f); + bq.suspend(); + } + + ~SurgeBiquads() {} + + enum Modes + { + LP, + LP2B, + HP, + BP, + BP2A, + PKA, + NOTCH, + ALL + }; + + basic_blocks::params::ParamMetaData paramAt(int idx) const + { + assert(idx >= 0 && idx < numFloatParams); + using pmd = basic_blocks::params::ParamMetaData; + + switch (idx) + { + case 0: + if (keytrackOn) + { + return pmd() + .asFloat() + .withRange(-48, 48) + .withName("Offset") + .withDefault(0) + .withLinearScaleFormatting("semitones"); + } + return pmd().asAudibleFrequency().withName("Cutoff").withDefault(0); + + case 1: + return pmd() + .asPercent() + .withDefault(0.7f) + .withName("Resonance") + .withLinearScaleFormatting(""); + + default: + break; + } + + return pmd().withName("Unknown " + std::to_string(idx)).asPercent(); + } + + basic_blocks::params::ParamMetaData intParamAt(int idx) const + { + using pmd = basic_blocks::params::ParamMetaData; + + using md = sst::filters::CytomicSVF::Mode; + + return pmd() + .asInt() + .withRange(0, 7) + .withName("Mode") + .withUnorderedMapFormatting({ + + {Modes::LP, "Low Pass"}, + {Modes::LP2B, "Low Pass 2B"}, + {Modes::HP, "High Pass"}, + {Modes::BP, "Band Pass"}, + {Modes::BP2A, "Band Pass 2A"}, + {Modes::PKA, "PKA"}, + {Modes::NOTCH, "Notch"}, + {Modes::ALL, "All Pass"}}) + .withDefault(Modes::LP2B); + } + + void initVoiceEffect() {} + void initVoiceEffectParams() { this->initToParamMetadataDefault(this); } + + void processStereo(float *datainL, float *datainR, float *dataoutL, float *dataoutR, + float pitch) + { + calc_coeffs(pitch); + bq.process_block_to(datainL, datainR, dataoutL, dataoutR); + } + + void processMonoToMono(float *datainL, float *dataoutL, float pitch) + { + calc_coeffs(pitch); + bq.process_block_to(datainL, dataoutL); + } + + void calc_coeffs(float pitch = 0.f) + { + std::array param; + std::array iparam; + bool diff{false}, idiff{false}; + for (int i = 0; i < numFloatParams; i++) + { + param[i] = this->getFloatParam(i); + if (i == 0 && keytrackOn) + { + param[i] += pitch; + } + diff = diff || (mLastParam[i] != param[i]); + } + for (int i = 0; i < numIntParams; ++i) + { + iparam[i] = this->getIntParam(i); + idiff = idiff || (mLastIParam[i] != iparam[i]); + } + idiff |= (wasKeytrackOn != keytrackOn); + wasKeytrackOn = keytrackOn; + + if (diff || idiff) + { + if (idiff) + { + bq.suspend(); + } + + auto Q = std::clamp(param[1], 0.001f, 0.999f); + auto omega = bq.calc_omega(param[0] / 12.f); + switch ((Modes)iparam[0]) + { + case LP: + bq.coeff_LP(omega, Q); + break; + case LP2B: + bq.coeff_LP2B(omega, Q); + break; + case HP: + bq.coeff_HP(omega, Q); + break; + case BP: + bq.coeff_BP(omega, Q); + break; + case BP2A: + bq.coeff_BP2A(omega, Q); + break; + case PKA: + bq.coeff_PKA(omega, Q); + break; + case NOTCH: + bq.coeff_NOTCH(omega, Q); + break; + case ALL: + bq.coeff_APF(omega, Q); + break; + } + + mLastParam = param; + mLastIParam = iparam; + } + } + + float getFrequencyGraph(float f) { return bq.plot_magnitude(f); } + + bool enableKeytrack(bool b) + { + auto res = (b != keytrackOn); + keytrackOn = b; + return res; + } + bool getKeytrack() const { return keytrackOn; } + + protected: + bool keytrackOn{false}, wasKeytrackOn{false}; + std::array mLastParam{}; + std::array mLastIParam{}; + + typename core::VoiceEffectTemplateBase::BiquadFilterType bq; +}; + +} // namespace sst::voice_effects::filter +#endif // SHORTCIRCUITXT_CYTOMICSVF_H diff --git a/tests/create-voice-effect.cpp b/tests/create-voice-effect.cpp index b28f1b5..d281f42 100644 --- a/tests/create-voice-effect.cpp +++ b/tests/create-voice-effect.cpp @@ -35,6 +35,7 @@ #include "sst/voice-effects/eq/EqGraphic6Band.h" #include "sst/voice-effects/delay/FauxStereo.h" #include "sst/voice-effects/filter/CytomicSVF.h" +#include "sst/voice-effects/filter/SurgeBiquads.h" struct VTestConfig { @@ -119,4 +120,9 @@ TEST_CASE("Can Create Voice FX") { VTester>::TestVFX(); } + + SECTION("SurgeBiquads") + { + VTester>::TestVFX(); + } } \ No newline at end of file From 9fc714b0737f5c2e1a29982d5ebb689b5a826068 Mon Sep 17 00:00:00 2001 From: Paul Walker Date: Wed, 10 Apr 2024 22:25:27 -0400 Subject: [PATCH 2/2] Also, correct BitCrusher filter --- include/sst/voice-effects/distortion/BitCrusher.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/sst/voice-effects/distortion/BitCrusher.h b/include/sst/voice-effects/distortion/BitCrusher.h index 482f22c..ea6bdf8 100644 --- a/include/sst/voice-effects/distortion/BitCrusher.h +++ b/include/sst/voice-effects/distortion/BitCrusher.h @@ -65,9 +65,9 @@ template struct BitCrusher : core::VoiceEffectTemplateBase< case bc_zeropoint: return pmd().asPercent().withName("zeropoint").withDefault(0.f); case bc_cutoff: - return pmd().asAudibleFrequency().withName("cutoff").withDefault(5.f); + return pmd().asAudibleFrequency().withName("cutoff"); case bc_resonance: - return pmd().asPercent().withName("resonance").withDefault(0.f); + return pmd().asPercent().withName("resonance").withDefault(0.707f); default: break; } @@ -113,8 +113,8 @@ template struct BitCrusher : core::VoiceEffectTemplateBase< dataoutR[k] = level[1]; } - lp.coeff_LP2B(lp.calc_omega(this->getFloatParam(bc_cutoff)), - lp.calc_v1_Q(this->getFloatParam(bc_resonance))); + lp.coeff_LP2B(lp.calc_omega(this->getFloatParam(bc_cutoff) / 12.f), + std::clamp(this->getFloatParam(bc_resonance), 0.001f, 0.999f)); lp.process_block(dataoutL, dataoutR); }