commit b7f6859beafd6a3d7ad83c322249e74d29306ac4
parent 0074730a82229d1d8d6ad6aa6ec750cfa592ebcc
Author: Matt Demanett <matt@demanett.net>
Date: Tue, 19 Jan 2021 00:12:51 -0500
NSGT: add controls for attack/release times on the context menu. #154
Diffstat:
3 files changed, 144 insertions(+), 5 deletions(-)
diff --git a/README-prerelease.md b/README-prerelease.md
@@ -825,7 +825,7 @@ _Polyphony:_ <a href="#polyphony">Polyphonic</a>, with polyphony defined by the
#### <a name="nsgt"></a> NSGT
-NSGT is a compact (6HP) [noise gate](https://en.wikipedia.org/wiki/Noise_gate). Its controls behave the same as the corresponding controls on PRESSOR.
+NSGT is a compact (6HP) [noise gate](https://en.wikipedia.org/wiki/Noise_gate). Its controls behave the same as the corresponding controls on PRESSOR. Controls for attack and release times are on the context menu.
_Polyphony:_ <a href="#polyphony">Polyphonic</a>, with polyphony defined by the L input.
diff --git a/src/Nsgt.cpp b/src/Nsgt.cpp
@@ -1,11 +1,11 @@
#include "Nsgt.hpp"
+#define ATTACK_MS "attack_ms"
+#define RELEASE_MS "release_ms"
+
void Nsgt::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());
}
void Nsgt::sampleRateChange() {
@@ -14,6 +14,24 @@ void Nsgt::sampleRateChange() {
}
}
+json_t* Nsgt::toJson(json_t* root) {
+ json_object_set_new(root, ATTACK_MS, json_real(_attackMs));
+ json_object_set_new(root, RELEASE_MS, json_real(_releaseMs));
+ return root;
+}
+
+void Nsgt::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));
+ }
+}
+
bool Nsgt::active() {
return outputs[LEFT_OUTPUT].isConnected() || outputs[RIGHT_OUTPUT].isConnected();
}
@@ -60,6 +78,10 @@ void Nsgt::modulateChannel(int c) {
ratio = 1.0f / ratio;
e.ratio = ratio;
}
+
+ float sr = APP->engine->getSampleRate();
+ e.attackSL.setParams(sr, _attackMs);
+ e.releaseSL.setParams(sr, _releaseMs);
}
void Nsgt::processChannel(const ProcessArgs& args, int c) {
@@ -89,6 +111,106 @@ void Nsgt::processChannel(const ProcessArgs& args, int c) {
}
}
+struct ARQuantity : Quantity {
+ typedef std::function<float&(Nsgt* m)> ValueRefFN;
+
+ Nsgt* _module;
+ std::string _label;
+ float _maxMs;
+ float _defaultMs;
+ ValueRefFN _moduleValueRef;
+
+ ARQuantity(
+ Nsgt* 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 {
+ Nsgt* _module;
+
+ AttackMenuItem(Nsgt* m) : _module(m) {
+ this->text = "Attack time";
+ this->rightText = "▸";
+ }
+
+ Menu* createChildMenu() override {
+ Menu* menu = new Menu;
+ menu->addChild(new ARSlider(new ARQuantity(
+ _module,
+ "Attack",
+ Nsgt::maxAttackMs,
+ Nsgt::defaultAttackMs,
+ [](Nsgt* m) -> float& { return m->_attackMs; }
+ )));
+ return menu;
+ }
+};
+
+struct ReleaseMenuItem : MenuItem {
+ Nsgt* _module;
+
+ ReleaseMenuItem(Nsgt* m) : _module(m) {
+ this->text = "Release time";
+ this->rightText = "▸";
+ }
+
+ Menu* createChildMenu() override {
+ Menu* menu = new Menu;
+ menu->addChild(new ARSlider(new ARQuantity(
+ _module,
+ "Release",
+ Nsgt::maxReleaseMs,
+ Nsgt::defaultReleaseMs,
+ [](Nsgt* m) -> float& { return m->_releaseMs; }
+ )));
+ return menu;
+ }
+};
+
struct NsgtWidget : BGModuleWidget {
static constexpr int hp = 6;
@@ -124,6 +246,14 @@ struct NsgtWidget : BGModuleWidget {
addOutput(createOutput<Port24>(leftOutputPosition, module, Nsgt::LEFT_OUTPUT));
addOutput(createOutput<Port24>(rightOutputPosition, module, Nsgt::RIGHT_OUTPUT));
}
+
+ void contextMenu(Menu* menu) override {
+ auto m = dynamic_cast<Nsgt*>(module);
+ assert(m);
+
+ menu->addChild(new AttackMenuItem(m));
+ menu->addChild(new ReleaseMenuItem(m));
+ }
};
Model* modelNsgt = bogaudio::createModel<Nsgt, NsgtWidget>("Bogaudio-Nsgt", "NSGT", "Noise gate", "Dynamics", "Polyphonic");
diff --git a/src/Nsgt.hpp b/src/Nsgt.hpp
@@ -48,8 +48,15 @@ struct Nsgt : BGModule {
void sampleRateChange();
};
+ static constexpr float defaultAttackMs = 150.0f;
+ static constexpr float maxAttackMs = 500.0f;
+ static constexpr float defaultReleaseMs = 600.0f;
+ static constexpr float maxReleaseMs = 2000.0f;
+
Engine* _engines[maxChannels] {};
bool _softKnee = true;
+ float _attackMs = defaultAttackMs;
+ float _releaseMs = defaultReleaseMs;
Nsgt() {
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS);
@@ -59,6 +66,8 @@ struct Nsgt : BGModule {
}
void sampleRateChange() override;
+ json_t* toJson(json_t* root) override;
+ void fromJson(json_t* root) override;
bool active() override;
int channels() override;
void addChannel(int c) override;