Pressor.cpp (10920B)
1 2 #include "Pressor.hpp" 3 4 #define THRESHOLD_RANGE "threshold_range" 5 6 void Pressor::Engine::sampleRateChange() { 7 detectorRMS.setSampleRate(APP->engine->getSampleRate()); 8 } 9 10 float Pressor::ThresholdParamQuantity::getDisplayValue() { 11 float v = getValue(); 12 if (!module) { 13 return v; 14 } 15 16 v *= 30.0f; 17 v -= 24.0f; 18 v *= dynamic_cast<Pressor*>(module)->_thresholdRange; 19 return v; 20 } 21 22 void Pressor::ThresholdParamQuantity::setDisplayValue(float v) { 23 if (!module) { 24 return; 25 } 26 Pressor* m = dynamic_cast<Pressor*>(module); 27 v /= m->_thresholdRange; 28 v = clamp(v, -24.0f, 6.0f); 29 v += 24.0f; 30 v /= 30.0f; 31 setValue(v); 32 } 33 34 void Pressor::sampleRateChange() { 35 for (int c = 0; c < _channels; ++c) { 36 _engines[c]->sampleRateChange(); 37 } 38 } 39 40 json_t* Pressor::saveToJson(json_t* root) { 41 json_object_set_new(root, THRESHOLD_RANGE, json_real(_thresholdRange)); 42 return root; 43 } 44 45 void Pressor::loadFromJson(json_t* root) { 46 json_t* tr = json_object_get(root, THRESHOLD_RANGE); 47 if (tr) { 48 _thresholdRange = std::max(0.0f, (float)json_real_value(tr)); 49 } 50 } 51 52 bool Pressor::active() { 53 return ( 54 outputs[LEFT_OUTPUT].isConnected() || 55 outputs[RIGHT_OUTPUT].isConnected() || 56 outputs[ENVELOPE_OUTPUT].isConnected() || 57 outputs[LEFT_INPUT].isConnected() || 58 outputs[RIGHT_INPUT].isConnected() || 59 outputs[SIDECHAIN_INPUT].isConnected() 60 ); 61 } 62 63 int Pressor::channels() { 64 return inputs[LEFT_INPUT].getChannels(); 65 } 66 67 void Pressor::addChannel(int c) { 68 _engines[c] = new Engine(); 69 _engines[c]->sampleRateChange(); 70 } 71 72 void Pressor::removeChannel(int c) { 73 delete _engines[c]; 74 _engines[c] = NULL; 75 } 76 77 void Pressor::modulate() { 78 _compressorMode = params[MODE_PARAM].getValue() > 0.5f; 79 _rmsDetector = params[DECTECTOR_MODE_PARAM].getValue() > 0.5f; 80 _softKnee = params[KNEE_PARAM].getValue() > 0.5f; 81 } 82 83 void Pressor::modulateChannel(int c) { 84 Engine& e = *_engines[c]; 85 86 e.thresholdDb = params[THRESHOLD_PARAM].getValue(); 87 if (inputs[THRESHOLD_INPUT].isConnected()) { 88 e.thresholdDb *= clamp(inputs[THRESHOLD_INPUT].getPolyVoltage(c) / 10.0f, 0.0f, 1.0f); 89 } 90 e.thresholdDb *= 30.0f; 91 e.thresholdDb -= 24.0f; 92 e.thresholdDb *= _thresholdRange; 93 94 float ratio = params[RATIO_PARAM].getValue(); 95 if (inputs[RATIO_INPUT].isConnected()) { 96 ratio *= clamp(inputs[RATIO_INPUT].getPolyVoltage(c) / 10.0f, 0.0f, 1.0f); 97 } 98 if (e.ratioKnob != ratio) { 99 e.ratioKnob = ratio; 100 ratio = powf(ratio, 1.5f); 101 ratio = 1.0f - ratio; 102 ratio *= M_PI; 103 ratio *= 0.25f; 104 ratio = tanf(ratio); 105 ratio = 1.0f / ratio; 106 e.ratio = ratio; 107 } 108 109 float sampleRate = APP->engine->getSampleRate(); 110 float attack = params[ATTACK_PARAM].getValue(); 111 if (inputs[ATTACK_INPUT].isConnected()) { 112 attack *= clamp(inputs[ATTACK_INPUT].getPolyVoltage(c) / 10.0f, 0.0f, 1.0f); 113 } 114 attack *= attack; 115 e.attackSL.setParams(sampleRate, attack * 500.0f); 116 117 float release = params[RELEASE_PARAM].getValue(); 118 if (inputs[RELEASE_INPUT].isConnected()) { 119 release *= clamp(inputs[RELEASE_INPUT].getPolyVoltage(c) / 10.0f, 0.0f, 1.0f); 120 } 121 release *= release; 122 e.releaseSL.setParams(sampleRate, release * 2000.0f); 123 124 float inGain = params[INPUT_GAIN_PARAM].getValue(); 125 if (inputs[INPUT_GAIN_INPUT].isConnected()) { 126 inGain = clamp(inGain + inputs[INPUT_GAIN_INPUT].getPolyVoltage(c) / 5.0f, -1.0f, 1.0f); 127 } 128 inGain *= 12.0f; 129 if (e.inGain != inGain) { 130 e.inGain = inGain; 131 e.inLevel = decibelsToAmplitude(e.inGain); 132 } 133 134 float outGain = params[OUTPUT_GAIN_PARAM].getValue(); 135 if (inputs[OUTPUT_GAIN_INPUT].isConnected()) { 136 outGain = clamp(outGain + inputs[OUTPUT_GAIN_INPUT].getPolyVoltage(c) / 5.0f, 0.0f, 1.0f); 137 } 138 outGain *= 24.0f; 139 if (e.outGain != outGain) { 140 e.outGain = outGain; 141 e.outLevel = decibelsToAmplitude(e.outGain); 142 } 143 144 e.detectorMix.setParams(params[DETECTOR_MIX_PARAM].getValue(), 0.0f, true); 145 } 146 147 void Pressor::processChannel(const ProcessArgs& args, int c) { 148 Engine& e = *_engines[c]; 149 150 float leftInput = inputs[LEFT_INPUT].getPolyVoltage(c) * e.inLevel; 151 float rightInput = inputs[RIGHT_INPUT].getPolyVoltage(c) * e.inLevel; 152 float env = leftInput + rightInput; 153 if (inputs[SIDECHAIN_INPUT].isConnected()) { 154 env = e.detectorMix.next(env, inputs[SIDECHAIN_INPUT].getPolyVoltage(c)); 155 } 156 if (_rmsDetector) { 157 env = e.detectorRMS.next(env); 158 } 159 else { 160 env = fabsf(env); 161 } 162 if (env > e.lastEnv) { 163 env = e.attackSL.next(env, e.lastEnv); 164 } 165 else { 166 env = e.releaseSL.next(env, e.lastEnv); 167 } 168 e.lastEnv = env; 169 170 float detectorDb = amplitudeToDecibels(env / 5.0f); 171 float compressionDb = 0.0f; 172 if (_compressorMode) { 173 compressionDb = e.compressor.compressionDb(detectorDb, e.thresholdDb, e.ratio, _softKnee); 174 } 175 else { 176 compressionDb = e.noiseGate.compressionDb(detectorDb, e.thresholdDb, e.ratio, _softKnee); 177 } 178 e.amplifier.setLevel(-compressionDb); 179 if (c == 0) { 180 _compressionDb = compressionDb; 181 outputs[ENVELOPE_OUTPUT].setChannels(_channels); 182 outputs[LEFT_OUTPUT].setChannels(_channels); 183 outputs[RIGHT_OUTPUT].setChannels(_channels); 184 } 185 outputs[ENVELOPE_OUTPUT].setVoltage(env, c); 186 if (outputs[LEFT_OUTPUT].isConnected()) { 187 outputs[LEFT_OUTPUT].setVoltage(e.saturator.next(e.amplifier.next(leftInput) * e.outLevel), c); 188 } 189 if (outputs[RIGHT_OUTPUT].isConnected()) { 190 outputs[RIGHT_OUTPUT].setVoltage(e.saturator.next(e.amplifier.next(rightInput) * e.outLevel), c); 191 } 192 } 193 194 struct PressorWidget : BGModuleWidget { 195 struct CompressionDisplay : LightEmittingWidget<OpaqueWidget> { 196 struct Level { 197 float db; 198 NVGcolor color; 199 Level(float db, const NVGcolor& color) : db(db), color(color) {} 200 }; 201 202 const NVGcolor bgColor = nvgRGBA(0xaa, 0xaa, 0xaa, 0xff); 203 Pressor* _module; 204 std::vector<Level> _levels; 205 206 CompressionDisplay(Pressor* module) : _module(module) { 207 auto color = nvgRGBA(0xff, 0xaa, 0x00, 0xff); 208 _levels.push_back(Level(30.0f, color)); 209 for (int i = 1; i <= 15; ++i) { 210 float db = 30.0f - i*2.0f; 211 _levels.push_back(Level(db, color)); // decibelsToColor(db - 15.0f))); 212 } 213 } 214 215 bool isLit() override { 216 return _module && !_module->isBypassed(); 217 } 218 219 void draw(const DrawArgs& args) override { 220 nvgSave(args.vg); 221 for (int i = 0; i < 80; i += 5) { 222 drawBox(args, i); 223 nvgFillColor(args.vg, bgColor); 224 nvgFill(args.vg); 225 } 226 nvgRestore(args.vg); 227 } 228 229 void drawLit(const DrawArgs& args) override { 230 float compressionDb = 0.0f; 231 if (_module && !_module->isBypassed()) { 232 compressionDb = _module->_compressionDb; 233 } 234 235 nvgSave(args.vg); 236 for (int i = 0; i < 80; i += 5) { 237 const Level& l = _levels.at(i / 5); 238 if (compressionDb > l.db) { 239 drawBox(args, i); 240 nvgFillColor(args.vg, l.color); 241 nvgFill(args.vg); 242 } 243 } 244 nvgRestore(args.vg); 245 } 246 247 void drawBox(const DrawArgs& args, int offset) { 248 nvgBeginPath(args.vg); 249 nvgRect(args.vg, 3, offset + 1, 5, 4); 250 } 251 }; 252 253 static constexpr int hp = 15; 254 255 PressorWidget(Pressor* module) { 256 setModule(module); 257 box.size = Vec(RACK_GRID_WIDTH * hp, RACK_GRID_HEIGHT); 258 setPanel(box.size, "Pressor"); 259 createScrews(); 260 261 { 262 auto display = new CompressionDisplay(module); 263 display->box.pos = Vec(208.0, 30.0 - 0.5); 264 display->box.size = Vec(18, 180); 265 addChild(display); 266 } 267 268 // generated by svg_widgets.rb 269 auto thresholdParamPosition = Vec(36.0, 53.0); 270 auto ratioParamPosition = Vec(125.0, 53.0); 271 auto attackParamPosition = Vec(42.0, 141.0); 272 auto releaseParamPosition = Vec(131.0, 141.0); 273 auto inputGainParamPosition = Vec(28.0, 213.0); 274 auto outputGainParamPosition = Vec(89.0, 213.0); 275 auto detectorMixParamPosition = Vec(150.0, 213.0); 276 auto modeParamPosition = Vec(198.5, 129.5); 277 auto dectectorModeParamPosition = Vec(198.5, 178.5); 278 auto kneeParamPosition = Vec(198.5, 227.5); 279 280 auto leftInputPosition = Vec(16.0, 274.0); 281 auto sidechainInputPosition = Vec(50.0, 274.0); 282 auto thresholdInputPosition = Vec(84.0, 274.0); 283 auto ratioInputPosition = Vec(118.0, 274.0); 284 auto rightInputPosition = Vec(16.0, 318.0); 285 auto attackInputPosition = Vec(50.0, 318.0); 286 auto releaseInputPosition = Vec(84.0, 318.0); 287 auto inputGainInputPosition = Vec(118.0, 318.0); 288 auto outputGainInputPosition = Vec(152.0, 318.0); 289 290 auto envelopeOutputPosition = Vec(152.0, 274.0); 291 auto leftOutputPosition = Vec(186.0, 274.0); 292 auto rightOutputPosition = Vec(186.0, 318.0); 293 // end generated by svg_widgets.rb 294 295 addParam(createParam<Knob38>(thresholdParamPosition, module, Pressor::THRESHOLD_PARAM)); 296 addParam(createParam<Knob38>(ratioParamPosition, module, Pressor::RATIO_PARAM)); 297 addParam(createParam<Knob26>(attackParamPosition, module, Pressor::ATTACK_PARAM)); 298 addParam(createParam<Knob26>(releaseParamPosition, module, Pressor::RELEASE_PARAM)); 299 addParam(createParam<Knob26>(outputGainParamPosition, module, Pressor::OUTPUT_GAIN_PARAM)); 300 addParam(createParam<Knob26>(inputGainParamPosition, module, Pressor::INPUT_GAIN_PARAM)); 301 addParam(createParam<Knob26>(detectorMixParamPosition, module, Pressor::DETECTOR_MIX_PARAM)); 302 addParam(createParam<SliderSwitch2State14>(modeParamPosition, module, Pressor::MODE_PARAM)); 303 addParam(createParam<SliderSwitch2State14>(dectectorModeParamPosition, module, Pressor::DECTECTOR_MODE_PARAM)); 304 addParam(createParam<SliderSwitch2State14>(kneeParamPosition, module, Pressor::KNEE_PARAM)); 305 306 addInput(createInput<Port24>(leftInputPosition, module, Pressor::LEFT_INPUT)); 307 addInput(createInput<Port24>(sidechainInputPosition, module, Pressor::SIDECHAIN_INPUT)); 308 addInput(createInput<Port24>(thresholdInputPosition, module, Pressor::THRESHOLD_INPUT)); 309 addInput(createInput<Port24>(ratioInputPosition, module, Pressor::RATIO_INPUT)); 310 addInput(createInput<Port24>(rightInputPosition, module, Pressor::RIGHT_INPUT)); 311 addInput(createInput<Port24>(attackInputPosition, module, Pressor::ATTACK_INPUT)); 312 addInput(createInput<Port24>(releaseInputPosition, module, Pressor::RELEASE_INPUT)); 313 addInput(createInput<Port24>(inputGainInputPosition, module, Pressor::INPUT_GAIN_INPUT)); 314 addInput(createInput<Port24>(outputGainInputPosition, module, Pressor::OUTPUT_GAIN_INPUT)); 315 316 addOutput(createOutput<Port24>(envelopeOutputPosition, module, Pressor::ENVELOPE_OUTPUT)); 317 addOutput(createOutput<Port24>(leftOutputPosition, module, Pressor::LEFT_OUTPUT)); 318 addOutput(createOutput<Port24>(rightOutputPosition, module, Pressor::RIGHT_OUTPUT)); 319 } 320 321 void contextMenu(Menu* menu) override { 322 auto m = dynamic_cast<Pressor*>(module); 323 assert(m); 324 325 OptionsMenuItem* tr = new OptionsMenuItem("Threshold range"); 326 tr->addItem(OptionMenuItem("1x (-24dB to 6dB)", [m]() { return m->_thresholdRange == 1.0f; }, [m]() { m->_thresholdRange = 1.0f; })); 327 tr->addItem(OptionMenuItem("2x (-48dB to 12dB)", [m]() { return m->_thresholdRange == 2.0f; }, [m]() { m->_thresholdRange = 2.0f; })); 328 OptionsMenuItem::addToMenu(tr, menu); 329 } 330 }; 331 332 Model* modelPressor = bogaudio::createModel<Pressor, PressorWidget>("Bogaudio-Pressor", "PRESSOR", "Stereo compressor and noise gate", "Compressor", "Dynamics", "Polyphonic");