BogaudioModules

BogaudioModules for VCV Rack
Log | Files | Refs | README | LICENSE

commit b549c32508c961da09c5eb2b09d7fcf94e0af93f
parent 0c8e551d15ebd326bb8c23a48943f4154ce893f3
Author: Matt Demanett <matt@demanett.net>
Date:   Wed, 20 Jan 2021 22:42:20 -0500

LMTR: add attack/decay controls as on NSGT. #154

Diffstat:
MREADME-prerelease.md | 2+-
Msrc/Lmtr.cpp | 126++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/Lmtr.hpp | 7+++++++
3 files changed, 130 insertions(+), 5 deletions(-)

diff --git a/README-prerelease.md b/README-prerelease.md @@ -817,7 +817,7 @@ _Polyphony:_ <a href="#polyphony">Polyphonic</a>, with polyphony defined by the #### <a name="lmtr"></a> LMTR -LMTR is a compact (6HP) [limiter](https://en.wikipedia.org/wiki/Dynamic_range_compression). Its controls behave the same as the corresponding controls on PRESSOR. +LMTR is a compact (6HP) [limiter](https://en.wikipedia.org/wiki/Dynamic_range_compression). Its controls behave the same as the corresponding controls on PRESSOR. Controls for attack and release times are on the context menu. In contrast to CLPR, LMTR does not distort the signal very much; it just reduces the amplitude of the signal to keep it below the threshold. diff --git a/src/Lmtr.cpp b/src/Lmtr.cpp @@ -1,13 +1,12 @@ #include "Lmtr.hpp" +#define ATTACK_MS "attack_ms" +#define RELEASE_MS "release_ms" #define THRESHOLD_RANGE "threshold_range" void Lmtr::Engine::sampleRateChange() { - float sampleRate = APP->engine->getSampleRate(); - detector.setSampleRate(sampleRate); - attackSL.setParams(sampleRate, 150.0f); - releaseSL.setParams(sampleRate, 600.0f); + detector.setSampleRate(APP->engine->getSampleRate()); } float Lmtr::ThresholdParamQuantity::getDisplayValue() { @@ -41,11 +40,23 @@ void Lmtr::sampleRateChange() { } json_t* Lmtr::toJson(json_t* root) { + json_object_set_new(root, ATTACK_MS, json_real(_attackMs)); + json_object_set_new(root, RELEASE_MS, json_real(_releaseMs)); json_object_set_new(root, THRESHOLD_RANGE, json_real(_thresholdRange)); return root; } void Lmtr::fromJson(json_t* root) { + json_t* a = json_object_get(root, ATTACK_MS); + if (a) { + _attackMs = std::max(0.0f, (float)json_real_value(a)); + } + + json_t* r = json_object_get(root, RELEASE_MS); + if (r) { + _releaseMs = std::max(0.0f, (float)json_real_value(r)); + } + json_t* tr = json_object_get(root, THRESHOLD_RANGE); if (tr) { _thresholdRange = std::max(0.0f, (float)json_real_value(tr)); @@ -94,6 +105,10 @@ void Lmtr::modulateChannel(int c) { e.outGain = outGain; e.outLevel = decibelsToAmplitude(e.outGain); } + + float sr = APP->engine->getSampleRate(); + e.attackSL.setParams(sr, _attackMs); + e.releaseSL.setParams(sr, _releaseMs); } void Lmtr::processChannel(const ProcessArgs& args, int c) { @@ -123,6 +138,106 @@ void Lmtr::processChannel(const ProcessArgs& args, int c) { } } +struct ARQuantity : Quantity { + typedef std::function<float&(Lmtr* m)> ValueRefFN; + + Lmtr* _module; + std::string _label; + float _maxMs; + float _defaultMs; + ValueRefFN _moduleValueRef; + + ARQuantity( + Lmtr* m, + const char* label, + float maxMs, + float defaultMs, + ValueRefFN moduleValueRef + ) + : _module(m) + , _label(label) + , _maxMs(maxMs) + , _defaultMs(defaultMs) + , _moduleValueRef(moduleValueRef) + {} + + void setValue(float value) override { + value = clamp(value, getMinValue(), getMaxValue()); + if (_module) { + _moduleValueRef(_module) = valueToMs(value); + } + } + + float getValue() override { + if (_module) { + return msToValue(_moduleValueRef(_module)); + } + return getDefaultValue(); + } + + float getMinValue() override { return 0.0f; } + float getMaxValue() override { return msToValue(_maxMs); } + float getDefaultValue() override { return msToValue(_defaultMs); } + float getDisplayValue() override { return roundf(valueToMs(getValue())); } + void setDisplayValue(float displayValue) override { setValue(msToValue(displayValue)); } + std::string getLabel() override { return _label; } + std::string getUnit() override { return "ms"; } + float valueToMs(float v) { return v * v * _maxMs; } + float msToValue(float ms) { return sqrtf(ms / _maxMs); }; +}; + +struct ARSlider : ui::Slider { + ARSlider(ARQuantity* q) { + quantity = q; // q now owned. + box.size.x = 200.0f; + } + virtual ~ARSlider() { + delete quantity; + } +}; + +struct AttackMenuItem : MenuItem { + Lmtr* _module; + + AttackMenuItem(Lmtr* m) : _module(m) { + this->text = "Attack time"; + this->rightText = "▸"; + } + + Menu* createChildMenu() override { + Menu* menu = new Menu; + menu->addChild(new ARSlider(new ARQuantity( + _module, + "Attack", + Lmtr::maxAttackMs, + Lmtr::defaultAttackMs, + [](Lmtr* m) -> float& { return m->_attackMs; } + ))); + return menu; + } +}; + +struct ReleaseMenuItem : MenuItem { + Lmtr* _module; + + ReleaseMenuItem(Lmtr* m) : _module(m) { + this->text = "Release time"; + this->rightText = "▸"; + } + + Menu* createChildMenu() override { + Menu* menu = new Menu; + menu->addChild(new ARSlider(new ARQuantity( + _module, + "Release", + Lmtr::maxReleaseMs, + Lmtr::defaultReleaseMs, + [](Lmtr* m) -> float& { return m->_releaseMs; } + ))); + return menu; + } +}; + struct LmtrWidget : BGModuleWidget { static constexpr int hp = 6; @@ -163,6 +278,9 @@ struct LmtrWidget : BGModuleWidget { auto m = dynamic_cast<Lmtr*>(module); assert(m); + menu->addChild(new AttackMenuItem(m)); + menu->addChild(new ReleaseMenuItem(m)); + OptionsMenuItem* tr = new OptionsMenuItem("Threshold range"); tr->addItem(OptionMenuItem("1x (-24dB to 6dB)", [m]() { return m->_thresholdRange == 1.0f; }, [m]() { m->_thresholdRange = 1.0f; })); tr->addItem(OptionMenuItem("2x (-48dB to 12dB)", [m]() { return m->_thresholdRange == 2.0f; }, [m]() { m->_thresholdRange = 2.0f; })); diff --git a/src/Lmtr.hpp b/src/Lmtr.hpp @@ -48,8 +48,15 @@ struct Lmtr : BGModule { void sampleRateChange(); }; + static constexpr float defaultAttackMs = 150.0f; + static constexpr float maxAttackMs = 5000.0f; + static constexpr float defaultReleaseMs = 600.0f; + static constexpr float maxReleaseMs = 20000.0f; + Engine* _engines[maxChannels] {}; bool _softKnee = true; + float _attackMs = defaultAttackMs; + float _releaseMs = defaultReleaseMs; float _thresholdRange = 1.0f; struct ThresholdParamQuantity : ParamQuantity {