BogaudioModules

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

Lmtr.cpp (8111B)


      1 
      2 #include "Lmtr.hpp"
      3 
      4 #define ATTACK_MS "attack_ms"
      5 #define RELEASE_MS "release_ms"
      6 #define THRESHOLD_RANGE "threshold_range"
      7 
      8 void Lmtr::Engine::sampleRateChange() {
      9 	detector.setSampleRate(APP->engine->getSampleRate());
     10 }
     11 
     12 float Lmtr::ThresholdParamQuantity::getDisplayValue() {
     13 	float v = getValue();
     14 	if (!module) {
     15 		return v;
     16 	}
     17 
     18 	v *= 30.0f;
     19 	v -= 24.0f;
     20 	v *= dynamic_cast<Lmtr*>(module)->_thresholdRange;
     21 	return v;
     22 }
     23 
     24 void Lmtr::ThresholdParamQuantity::setDisplayValue(float v) {
     25 	if (!module) {
     26 		return;
     27 	}
     28 	Lmtr* m = dynamic_cast<Lmtr*>(module);
     29 	v /= m->_thresholdRange;
     30 	v = clamp(v, -24.0f, 6.0f);
     31 	v += 24.0f;
     32 	v /= 30.0f;
     33 	setValue(v);
     34 }
     35 
     36 void Lmtr::sampleRateChange() {
     37 	for (int c = 0; c < _channels; ++c) {
     38 		_engines[c]->sampleRateChange();
     39 	}
     40 }
     41 
     42 json_t* Lmtr::saveToJson(json_t* root) {
     43 	json_object_set_new(root, ATTACK_MS, json_real(_attackMs));
     44 	json_object_set_new(root, RELEASE_MS, json_real(_releaseMs));
     45 	json_object_set_new(root, THRESHOLD_RANGE, json_real(_thresholdRange));
     46 	return root;
     47 }
     48 
     49 void Lmtr::loadFromJson(json_t* root) {
     50 	json_t* a = json_object_get(root, ATTACK_MS);
     51 	if (a) {
     52 		_attackMs = std::max(0.0f, (float)json_real_value(a));
     53 	}
     54 
     55 	json_t* r = json_object_get(root, RELEASE_MS);
     56 	if (r) {
     57 		_releaseMs = std::max(0.0f, (float)json_real_value(r));
     58 	}
     59 
     60 	json_t* tr = json_object_get(root, THRESHOLD_RANGE);
     61 	if (tr) {
     62 		_thresholdRange = std::max(0.0f, (float)json_real_value(tr));
     63 	}
     64 }
     65 
     66 bool Lmtr::active() {
     67 	return outputs[LEFT_OUTPUT].isConnected() || outputs[RIGHT_OUTPUT].isConnected();
     68 }
     69 
     70 int Lmtr::channels() {
     71 	return inputs[LEFT_INPUT].getChannels();
     72 }
     73 
     74 void Lmtr::addChannel(int c) {
     75 	_engines[c] = new Engine();
     76 	_engines[c]->sampleRateChange();
     77 }
     78 
     79 void Lmtr::removeChannel(int c) {
     80 	delete _engines[c];
     81 	_engines[c] = NULL;
     82 }
     83 
     84 void Lmtr::modulate() {
     85 	_softKnee = params[KNEE_PARAM].getValue() > 0.5f;
     86 }
     87 
     88 void Lmtr::modulateChannel(int c) {
     89 	Engine& e = *_engines[c];
     90 
     91 	e.thresholdDb = params[THRESHOLD_PARAM].getValue();
     92 	if (inputs[THRESHOLD_INPUT].isConnected()) {
     93 		e.thresholdDb *= clamp(inputs[THRESHOLD_INPUT].getPolyVoltage(c) / 10.0f, 0.0f, 1.0f);
     94 	}
     95 	e.thresholdDb *= 30.0f;
     96 	e.thresholdDb -= 24.0f;
     97 	e.thresholdDb *= _thresholdRange;
     98 
     99 	float outGain = params[OUTPUT_GAIN_PARAM].getValue();
    100 	if (inputs[OUTPUT_GAIN_INPUT].isConnected()) {
    101 		outGain = clamp(outGain + inputs[OUTPUT_GAIN_INPUT].getPolyVoltage(c) / 5.0f, 0.0f, 1.0f);
    102 	}
    103 	outGain *= 24.0f;
    104 	if (e.outGain != outGain) {
    105 		e.outGain = outGain;
    106 		e.outLevel = decibelsToAmplitude(e.outGain);
    107 	}
    108 
    109 	float sr = APP->engine->getSampleRate();
    110 	e.attackSL.setParams(sr, _attackMs);
    111 	e.releaseSL.setParams(sr, _releaseMs);
    112 }
    113 
    114 void Lmtr::processChannel(const ProcessArgs& args, int c) {
    115 	Engine& e = *_engines[c];
    116 
    117 	float leftInput = inputs[LEFT_INPUT].getPolyVoltage(c);
    118 	float rightInput = inputs[RIGHT_INPUT].getPolyVoltage(c);
    119 	float env = e.detector.next(leftInput + rightInput);
    120 	if (env > e.lastEnv) {
    121 		env = e.attackSL.next(env, e.lastEnv);
    122 	}
    123 	else {
    124 		env = e.releaseSL.next(env, e.lastEnv);
    125 	}
    126 	e.lastEnv = env;
    127 
    128 	float detectorDb = amplitudeToDecibels(env / 5.0f);
    129 	float compressionDb = e.compressor.compressionDb(detectorDb, e.thresholdDb, Compressor::maxEffectiveRatio, _softKnee);
    130 	e.amplifier.setLevel(-compressionDb);
    131 	if (outputs[LEFT_OUTPUT].isConnected()) {
    132 		outputs[LEFT_OUTPUT].setChannels(_channels);
    133 		outputs[LEFT_OUTPUT].setVoltage(e.saturator.next(e.amplifier.next(leftInput) * e.outLevel), c);
    134 	}
    135 	if (outputs[RIGHT_OUTPUT].isConnected()) {
    136 		outputs[RIGHT_OUTPUT].setChannels(_channels);
    137 		outputs[RIGHT_OUTPUT].setVoltage(e.saturator.next(e.amplifier.next(rightInput) * e.outLevel), c);
    138 	}
    139 }
    140 
    141 struct ARQuantity : Quantity {
    142 	typedef std::function<float&(Lmtr* m)> ValueRefFN;
    143 
    144 	Lmtr* _module;
    145 	std::string _label;
    146 	float _maxMs;
    147 	float _defaultMs;
    148 	ValueRefFN _moduleValueRef;
    149 
    150 	ARQuantity(
    151 		Lmtr* m,
    152 		const char* label,
    153 		float maxMs,
    154 		float defaultMs,
    155 		ValueRefFN moduleValueRef
    156 	)
    157 	: _module(m)
    158 	, _label(label)
    159 	, _maxMs(maxMs)
    160 	, _defaultMs(defaultMs)
    161 	, _moduleValueRef(moduleValueRef)
    162 	{}
    163 
    164 	void setValue(float value) override {
    165 		value = clamp(value, getMinValue(), getMaxValue());
    166 		if (_module) {
    167 			_moduleValueRef(_module) = valueToMs(value);
    168 		}
    169 	}
    170 
    171 	float getValue() override {
    172 		if (_module) {
    173 			return msToValue(_moduleValueRef(_module));
    174 		}
    175 		return getDefaultValue();
    176 	}
    177 
    178 	float getMinValue() override { return 0.0f; }
    179 	float getMaxValue() override { return msToValue(_maxMs); }
    180 	float getDefaultValue() override { return msToValue(_defaultMs); }
    181 	float getDisplayValue() override { return roundf(valueToMs(getValue())); }
    182 	void setDisplayValue(float displayValue) override { setValue(msToValue(displayValue)); }
    183 	std::string getLabel() override { return _label; }
    184 	std::string getUnit() override { return "ms"; }
    185 	float valueToMs(float v) { return v * v * _maxMs; }
    186 	float msToValue(float ms) { return sqrtf(ms / _maxMs); };
    187 };
    188 
    189 struct ARSlider : ui::Slider {
    190 	ARSlider(ARQuantity* q) {
    191 		quantity = q; // q now owned.
    192 		box.size.x = 200.0f;
    193 	}
    194 	virtual ~ARSlider() {
    195 		delete quantity;
    196 	}
    197 };
    198 
    199 struct AttackMenuItem : MenuItem {
    200 	Lmtr* _module;
    201 
    202 	AttackMenuItem(Lmtr* m) : _module(m) {
    203 		this->text = "Attack time";
    204 		this->rightText = "▸";
    205 	}
    206 
    207 	Menu* createChildMenu() override {
    208 		Menu* menu = new Menu;
    209 		menu->addChild(new ARSlider(new ARQuantity(
    210 			_module,
    211 			"Attack",
    212 			Lmtr::maxAttackMs,
    213 			Lmtr::defaultAttackMs,
    214 			[](Lmtr* m) -> float& { return m->_attackMs; }
    215 		)));
    216 		return menu;
    217 	}
    218 };
    219 
    220 struct ReleaseMenuItem : MenuItem {
    221 	Lmtr* _module;
    222 
    223 	ReleaseMenuItem(Lmtr* m) : _module(m) {
    224 		this->text = "Release time";
    225 		this->rightText = "▸";
    226 	}
    227 
    228 	Menu* createChildMenu() override {
    229 		Menu* menu = new Menu;
    230 		menu->addChild(new ARSlider(new ARQuantity(
    231 			_module,
    232 			"Release",
    233 			Lmtr::maxReleaseMs,
    234 			Lmtr::defaultReleaseMs,
    235 			[](Lmtr* m) -> float& { return m->_releaseMs; }
    236 		)));
    237 		return menu;
    238 	}
    239 };
    240 
    241 struct LmtrWidget : BGModuleWidget {
    242 	static constexpr int hp = 6;
    243 
    244 	LmtrWidget(Lmtr* module) {
    245 		setModule(module);
    246 		box.size = Vec(RACK_GRID_WIDTH * hp, RACK_GRID_HEIGHT);
    247 		setPanel(box.size, "Lmtr");
    248 		createScrews();
    249 
    250 		// generated by svg_widgets.rb
    251 		auto thresholdParamPosition = Vec(26.0, 52.0);
    252 		auto outputGainParamPosition = Vec(26.0, 134.0);
    253 		auto kneeParamPosition = Vec(39.5, 199.5);
    254 
    255 		auto leftInputPosition = Vec(16.0, 244.0);
    256 		auto rightInputPosition = Vec(50.0, 244.0);
    257 		auto thresholdInputPosition = Vec(16.0, 280.0);
    258 		auto outputGainInputPosition = Vec(50.0, 280.0);
    259 
    260 		auto leftOutputPosition = Vec(16.0, 320.0);
    261 		auto rightOutputPosition = Vec(50.0, 320.0);
    262 		// end generated by svg_widgets.rb
    263 
    264 		addParam(createParam<Knob38>(thresholdParamPosition, module, Lmtr::THRESHOLD_PARAM));
    265 		addParam(createParam<Knob38>(outputGainParamPosition, module, Lmtr::OUTPUT_GAIN_PARAM));
    266 		addParam(createParam<SliderSwitch2State14>(kneeParamPosition, module, Lmtr::KNEE_PARAM));
    267 
    268 		addInput(createInput<Port24>(leftInputPosition, module, Lmtr::LEFT_INPUT));
    269 		addInput(createInput<Port24>(rightInputPosition, module, Lmtr::RIGHT_INPUT));
    270 		addInput(createInput<Port24>(thresholdInputPosition, module, Lmtr::THRESHOLD_INPUT));
    271 		addInput(createInput<Port24>(outputGainInputPosition, module, Lmtr::OUTPUT_GAIN_INPUT));
    272 
    273 		addOutput(createOutput<Port24>(leftOutputPosition, module, Lmtr::LEFT_OUTPUT));
    274 		addOutput(createOutput<Port24>(rightOutputPosition, module, Lmtr::RIGHT_OUTPUT));
    275 	}
    276 
    277 	void contextMenu(Menu* menu) override {
    278 		auto m = dynamic_cast<Lmtr*>(module);
    279 		assert(m);
    280 
    281 		menu->addChild(new AttackMenuItem(m));
    282 		menu->addChild(new ReleaseMenuItem(m));
    283 
    284 		OptionsMenuItem* tr = new OptionsMenuItem("Threshold range");
    285 		tr->addItem(OptionMenuItem("1x (-24dB to 6dB)", [m]() { return m->_thresholdRange == 1.0f; }, [m]() { m->_thresholdRange = 1.0f; }));
    286 		tr->addItem(OptionMenuItem("2x (-48dB to 12dB)", [m]() { return m->_thresholdRange == 2.0f; }, [m]() { m->_thresholdRange = 2.0f; }));
    287 		OptionsMenuItem::addToMenu(tr, menu);
    288 	}
    289 };
    290 
    291 Model* modelLmtr = bogaudio::createModel<Lmtr, LmtrWidget>("Bogaudio-Lmtr", "LMTR", "Limiter", "Limiter", "Dynamics", "Polyphonic");