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");