BogaudioModules

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

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