BogaudioModules

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

matrix_base.cpp (13981B)


      1 
      2 #include "matrix_base.hpp"
      3 #include "dsp/signal.hpp"
      4 
      5 using namespace bogaudio;
      6 using namespace bogaudio::dsp;
      7 
      8 #define CLIPPING_MODE "clipping_mode"
      9 #define INPUT_GAIN_DB "input_gain_db"
     10 #define SUM "sum"
     11 
     12 json_t* MatrixBaseModule::saveToJson(json_t* root) {
     13 	json_object_set_new(root, CLIPPING_MODE, json_integer(_clippingMode));
     14 	json_object_set_new(root, INPUT_GAIN_DB, json_real(_inputGainDb));
     15 	json_object_set_new(root, SUM, json_boolean(_sum));
     16 	return root;
     17 }
     18 
     19 void MatrixBaseModule::loadFromJson(json_t* root) {
     20 	json_t* c = json_object_get(root, CLIPPING_MODE);
     21 	if (c) {
     22 		_clippingMode = (Clipping)json_integer_value(c);
     23 		if (_clippingMode != HARD_CLIPPING && _clippingMode != NO_CLIPPING) {
     24 			_clippingMode = SOFT_CLIPPING;
     25 		}
     26 	}
     27 
     28 	json_t* g = json_object_get(root, INPUT_GAIN_DB);
     29 	if (g) {
     30 		_inputGainDb = clamp(json_real_value(g), -60.0f, 6.0f);
     31 	}
     32 
     33 	json_t* s = json_object_get(root, SUM);
     34 	if (s) {
     35 		_sum = json_is_true(s);
     36 	}
     37 }
     38 
     39 void MatrixBaseModule::modulate() {
     40 	_inputGainLevel = decibelsToAmplitude(_inputGainDb);
     41 }
     42 
     43 void MatrixBaseModuleWidget::contextMenu(Menu* menu) {
     44 	auto m = dynamic_cast<MatrixBaseModule*>(module);
     45 	assert(m);
     46 
     47 	if (!m->_singleInput) {
     48 		OptionsMenuItem* g = new OptionsMenuItem("Input gain");
     49 		g->addItem(OptionMenuItem("Unity", [m]() { return (int)m->_inputGainDb == 0; }, [m]() { m->_inputGainDb = 0.0f; }));
     50 		g->addItem(OptionMenuItem("-3db", [m]() { return (int)m->_inputGainDb == -3; }, [m]() { m->_inputGainDb = -3.0f; }));
     51 		g->addItem(OptionMenuItem("-6db", [m]() { return (int)m->_inputGainDb == -6; }, [m]() { m->_inputGainDb = -6.0f; }));
     52 		g->addItem(OptionMenuItem("-12db", [m]() { return (int)m->_inputGainDb == -12; }, [m]() { m->_inputGainDb = -12.0f; }));
     53 		OptionsMenuItem::addToMenu(g, menu);
     54 	}
     55 
     56 	OptionsMenuItem* c = new OptionsMenuItem("Output clipping");
     57 	c->addItem(OptionMenuItem("Soft/saturated (better for audio)", [m]() { return m->_clippingMode == MatrixBaseModule::SOFT_CLIPPING; }, [m]() { m->_clippingMode = MatrixBaseModule::SOFT_CLIPPING; }));
     58 	c->addItem(OptionMenuItem("Hard/clipped (better for CV)", [m]() { return m->_clippingMode == MatrixBaseModule::HARD_CLIPPING; }, [m]() { m->_clippingMode = MatrixBaseModule::HARD_CLIPPING; }));
     59 	c->addItem(OptionMenuItem("None", [m]() { return m->_clippingMode == MatrixBaseModule::NO_CLIPPING; }, [m]() { m->_clippingMode = MatrixBaseModule::NO_CLIPPING; }));
     60 	OptionsMenuItem::addToMenu(c, menu);
     61 
     62 	if (!m->_singleInput) {
     63 		menu->addChild(new OptionMenuItem("Average", [m]() { return !m->_sum; }, [m]() { m->_sum = !m->_sum; }));
     64 	}
     65 }
     66 
     67 void MatrixModule::configMatrixModule(int ins, int outs, int firstParamID, int firstInputID, int firstOutputID) {
     68 	assert(!_paramValues && !_sls && !_saturators && !_inActive);
     69 	_ins = ins;
     70 	_outs = outs;
     71 	_firstParamID = firstParamID;
     72 	_firstInputID = firstInputID;
     73 	_firstOutputID = firstOutputID;
     74 	assert(_ins <= maxN);
     75 	assert(_outs <= maxN);
     76 	_paramValues = new float[_ins * _outs] {};
     77 	_sls = new bogaudio::dsp::SlewLimiter[_ins * _outs];
     78 	_saturators = new Saturator[_outs * maxChannels];
     79 	_inActive = new bool[_ins] {};
     80 	_singleInput = _ins <= 1;
     81 }
     82 
     83 void MatrixModule::sampleRateChange() {
     84 	float sr = APP->engine->getSampleRate();
     85 	for (int i = 0, n = _ins * _outs; i < n; ++i) {
     86 		_sls[i].setParams(sr, 0.5f, 1.0f);
     87 	}
     88 }
     89 
     90 int MatrixModule::channels() {
     91 	return inputs[_firstInputID].getChannels();
     92 }
     93 
     94 void MatrixModule::modulate() {
     95 	MatrixBaseModule::modulate();
     96 
     97 	bool solo = false;
     98 	bool soloColumn[maxN] {};
     99 	if (_muteParams) {
    100 		bool soloByColumns = false;
    101 		if (_soloByColumns) {
    102 			soloByColumns = *_soloByColumns;
    103 		}
    104 
    105 		for (int i = 0; i < _outs; ++i) {
    106 			for (int j = 0; j < _ins; ++j) {
    107 				if (_muteParams[i * _ins + j]->getValue() > 1.5f) {
    108 					solo = !soloByColumns;
    109 					soloColumn[i] = soloByColumns;
    110 					break;
    111 				}
    112 			}
    113 		}
    114 	}
    115 
    116 	int active = 0;
    117 	for (int i = 0; i < _ins; ++i) {
    118 		_inActive[i] = inputs[_firstInputID + i].isConnected();
    119 		if (_inActive[i]) {
    120 			++active;
    121 		}
    122 
    123 		for (int j = 0; j < _outs; ++j) {
    124 			int ii = j * _ins + i;
    125 			float v = params[_firstParamID + ii].getValue();
    126 			if (_muteParams) {
    127 				bool muted = (solo || soloColumn[j]) ? _muteParams[ii]->getValue() < 2.0f : _muteParams[ii]->getValue() > 0.5f;
    128 				v *= !muted;
    129 			}
    130 			_paramValues[ii] = _sls[ii].next(v);
    131 		}
    132 	}
    133 
    134 	_invActive = (!_sum && active > 0) ? 1.0f / (float)active : 0.0f;
    135 }
    136 
    137 void MatrixModule::processChannel(const ProcessArgs& args, int c) {
    138 	float in[maxN] {};
    139 	for (int i = 0; i < _ins; ++i) {
    140 		if (_inActive[i]) {
    141 			in[i] = inputs[_firstInputID + i].getPolyVoltage(c) * _inputGainLevel;
    142 		}
    143 	}
    144 
    145 	for (int i = 0; i < _outs; ++i) {
    146 		if (!outputs[_firstOutputID + i].isConnected()) {
    147 			continue;
    148 		}
    149 		float out = 0.0f;
    150 		for (int j = 0; j < _ins; ++j) {
    151 			if (_inActive[j]) {
    152 				int ii = i * _ins + j;
    153 				float cv = 1.0f;
    154 				if (_cvInputs && _cvInputs[ii]->isConnected()) {
    155 					cv = clamp(_cvInputs[ii]->getPolyVoltage(c) / 5.0f, -1.0f, 1.0f);
    156 				}
    157 				out += in[j] * _paramValues[ii] * cv;
    158 			}
    159 		}
    160 		if (!_sum && _invActive > 0.0f) {
    161 			out *= _invActive;
    162 		}
    163 		if (_clippingMode == SOFT_CLIPPING) {
    164 			out = _saturators[c * _outs + i].next(out);
    165 		}
    166 		else if (_clippingMode == HARD_CLIPPING) {
    167 			out = clamp(out, -12.0f, 12.0f);
    168 		}
    169 		outputs[_firstOutputID + i].setChannels(_channels);
    170 		outputs[_firstOutputID + i].setVoltage(out, c);
    171 	}
    172 }
    173 
    174 
    175 #define INDICATOR_KNOBS "indicator_knobs"
    176 #define UNIPOLAR "unipolar"
    177 
    178 json_t* KnobMatrixModule::saveToJson(json_t* root) {
    179 	root = MatrixBaseModule::saveToJson(root);
    180 	json_object_set_new(root, INDICATOR_KNOBS, json_boolean(_indicatorKnobs));
    181 	json_object_set_new(root, UNIPOLAR, json_boolean(_unipolar));
    182 	return root;
    183 }
    184 
    185 void KnobMatrixModule::loadFromJson(json_t* root) {
    186 	MatrixBaseModule::loadFromJson(root);
    187 
    188 	json_t* k = json_object_get(root, INDICATOR_KNOBS);
    189 	if (k) {
    190 		_indicatorKnobs = json_is_true(k);
    191 	}
    192 
    193 	json_t* u = json_object_get(root, UNIPOLAR);
    194 	if (u) {
    195 		_unipolar = json_is_true(u);
    196 		updateParamMinimumValues();
    197 	}
    198 }
    199 
    200 void KnobMatrixModule::updateParamMinimumValues() {
    201 	if (_unipolar) {
    202 		for (int i = 0, n = _ins * _outs; i < n; ++i) {
    203 			paramQuantities[i]->minValue = 0.0f;
    204 			params[i].value = std::max(params[i].value, 0.0f);
    205 		}
    206 	} else {
    207 		for (int i = 0, n = _ins * _outs; i < n; ++i) {
    208 			paramQuantities[i]->minValue = -1.0f;
    209 		}
    210 	}
    211 }
    212 
    213 
    214 void KnobMatrixModuleWidget::createKnob(math::Vec& position, KnobMatrixModule* module, int id) {
    215 	auto knob = dynamic_cast<IndicatorKnob19*>(createParam<IndicatorKnob19>(position, module, id));
    216 	if (module) {
    217 		knob->setDrawColorsCallback([module]() { return module->_indicatorKnobs; });
    218 		knob->setUnipolarCallback([module]() { return module->_unipolar; });
    219 	}
    220 	addParam(knob);
    221 	_knobs.push_back(knob);
    222 }
    223 
    224 void KnobMatrixModuleWidget::redrawKnobs() {
    225 	for (IndicatorKnob19* knob : _knobs) {
    226 		knob->redraw();
    227 	}
    228 }
    229 
    230 void KnobMatrixModuleWidget::contextMenu(Menu* menu) {
    231 	auto m = dynamic_cast<KnobMatrixModule*>(module);
    232 	assert(m);
    233 	MatrixModuleWidget::contextMenu(menu);
    234 	menu->addChild(new OptionMenuItem(
    235 		"Indicator knobs",
    236 		[m]() { return m->_indicatorKnobs; },
    237 		[m, this]() { m->_indicatorKnobs = !m->_indicatorKnobs; this->redrawKnobs(); }
    238 	));
    239 	menu->addChild(new OptionMenuItem(
    240 		"Unipolar",
    241 		[m]() { return m->_unipolar; },
    242 		[m, this]() { m->_unipolar = !m->_unipolar; m->updateParamMinimumValues(); this->redrawKnobs(); }
    243 	));
    244 }
    245 
    246 
    247 float SwitchMatrixModule::randomSwitchParamValue(bool allowZero) {
    248 	switch (_inverting) {
    249 		case NO_INVERTING: {
    250 			if (allowZero) {
    251 				return (float)(random::u32() % 2);
    252 			}
    253 			return 1.0f;
    254 		}
    255 		default: {
    256 			if (allowZero) {
    257 				return (float)(random::u32() % 3) - 1.0f;
    258 			}
    259 			return random::u32() % 2 == 0 ? -1.0f : 1.0f;
    260 		}
    261 	}
    262 }
    263 
    264 void SwitchMatrixModule::onRandomize(const RandomizeEvent& e) {
    265 	if (_rowExclusive || _columnExclusive) {
    266 		for (ParamQuantity* pq : _switchParamQuantities) {
    267 			pq->setValue(0.0f);
    268 		}
    269 
    270 		if (_rowExclusive && _columnExclusive) {
    271 			_switchParamQuantities[random::u32() % (_ins * _outs)]->setValue(randomSwitchParamValue(false));
    272 		}
    273 		else if (_rowExclusive) {
    274 			for (int row = 0; row < _ins; ++row) {
    275 				_switchParamQuantities[row + ((random::u32() % _outs) * _ins)]->setValue(randomSwitchParamValue(false));
    276 			}
    277 		}
    278 		else {
    279 			for (int col = 0; col < _outs; ++col) {
    280 				_switchParamQuantities[col * _ins + (random::u32() % _ins)]->setValue(randomSwitchParamValue(false));
    281 			}
    282 		}
    283 	}
    284 	else {
    285 		for (ParamQuantity* pq : _switchParamQuantities) {
    286 			pq->setValue(randomSwitchParamValue());
    287 		}
    288 	}
    289 }
    290 
    291 #define INVERTING "inverting"
    292 #define INVERTING_CLICK "click"
    293 #define INVERTING_PARAM "param"
    294 #define INVERTING_DISABLED "disabled"
    295 
    296 #define ROW_EXCLUSIVE "row_exclusive"
    297 #define COLUMN_EXCLUSIVE "column_exclusive"
    298 
    299 json_t* SwitchMatrixModule::saveToJson(json_t* root) {
    300 	root = MatrixBaseModule::saveToJson(root);
    301 
    302 	switch (_inverting) {
    303 		case CLICK_INVERTING: {
    304 			json_object_set_new(root, INVERTING, json_string(INVERTING_CLICK));
    305 			break;
    306 		}
    307 		case PARAM_INVERTING: {
    308 			json_object_set_new(root, INVERTING, json_string(INVERTING_PARAM));
    309 			break;
    310 		}
    311 		case NO_INVERTING: {
    312 			json_object_set_new(root, INVERTING, json_string(INVERTING_DISABLED));
    313 			break;
    314 		}
    315 	}
    316 
    317 	json_object_set_new(root, ROW_EXCLUSIVE, json_boolean(_rowExclusive));
    318 	json_object_set_new(root, COLUMN_EXCLUSIVE, json_boolean(_columnExclusive));
    319 
    320 	return root;
    321 }
    322 
    323 void SwitchMatrixModule::loadFromJson(json_t* root) {
    324 	MatrixBaseModule::loadFromJson(root);
    325 
    326 	json_t* i = json_object_get(root, INVERTING);
    327 	if (i) {
    328 		const char* s = json_string_value(i);
    329 		if (s) {
    330 			if (0 == strcmp(INVERTING_CLICK, s)) {
    331 				setInverting(CLICK_INVERTING);
    332 			}
    333 			else if (0 == strcmp(INVERTING_PARAM, s)) {
    334 				setInverting(PARAM_INVERTING);
    335 			}
    336 			else if (0 == strcmp(INVERTING_DISABLED, s)) {
    337 				setInverting(NO_INVERTING);
    338 			}
    339 		}
    340 	}
    341 
    342 	json_t* re = json_object_get(root, ROW_EXCLUSIVE);
    343 	if (re) {
    344 		_rowExclusive = json_is_true(re);
    345 	}
    346 	json_t* ce = json_object_get(root, COLUMN_EXCLUSIVE);
    347 	if (ce) {
    348 		_columnExclusive = json_is_true(ce);
    349 	}
    350 }
    351 
    352 void SwitchMatrixModule::setInverting(Inverting inverting) {
    353 	_inverting = inverting;
    354 
    355 	float minValue = -1.0f;
    356 	switch (_inverting) {
    357 		case CLICK_INVERTING:
    358 		case PARAM_INVERTING: {
    359 			minValue = -1.0f;
    360 			break;
    361 		}
    362 		default: {
    363 			minValue = 0.0f;
    364 		}
    365 	}
    366 	for (ParamQuantity* pq : _switchParamQuantities) {
    367 		pq->minValue = minValue;
    368 		if (pq->getValue() < minValue) {
    369 			pq->setValue(minValue);
    370 		}
    371 	}
    372 }
    373 
    374 void SwitchMatrixModule::configSwitchParam(int id, const char* label) {
    375 	configParam(id, -1.0f, 1.0f, 0.0f, label, "%", 0.0f, 100.0f);
    376 	_switchParamQuantities.push_back(paramQuantities[id]);
    377 }
    378 
    379 void SwitchMatrixModule::switchChanged(int id, float value) {
    380 	if (value != 0.0f) {
    381 		int row = (id - _firstParamID) % _ins;
    382 		int col = (id - _firstParamID) / _ins;
    383 
    384 		if (_rowExclusive) {
    385 			for (int i = 0; i < col; ++i) {
    386 				_switchParamQuantities[i * _ins + row]->setValue(0.0f);
    387 			}
    388 			for (int i = col + 1; i < _outs; ++i) {
    389 				_switchParamQuantities[i * _ins + row]->setValue(0.0f);
    390 			}
    391 		}
    392 
    393 		if (_columnExclusive) {
    394 			for (int i = 0; i < row; ++i) {
    395 				_switchParamQuantities[col * _ins + i]->setValue(0.0f);
    396 			}
    397 			for (int i = row + 1; i < _ins; ++i) {
    398 				_switchParamQuantities[col * _ins + i]->setValue(0.0f);
    399 			}
    400 		}
    401 	}
    402 }
    403 
    404 void SwitchMatrixModule::setRowExclusive(bool e) {
    405 	_rowExclusive = e;
    406 	if (e) {
    407 		for (int i = 0; i < _ins; ++i) {
    408 			int j = 0;
    409 			for (; j < _outs; ++j) {
    410 				if (_switchParamQuantities[j * _ins + i]->getValue() != 0.0f) {
    411 					break;
    412 				}
    413 			}
    414 			++j;
    415 			for (; j < _outs; ++j) {
    416 				_switchParamQuantities[j * _ins + i]->setValue(0.0f);
    417 			}
    418 		}
    419 	}
    420 }
    421 
    422 void SwitchMatrixModule::setColumnExclusive(bool e) {
    423 	_columnExclusive = e;
    424 	if (e) {
    425 		for (int i = 0; i < _outs; ++i) {
    426 			int j = 0;
    427 			for (; j < _ins; ++j) {
    428 				if (_switchParamQuantities[i * _ins + j]->getValue() != 0.0f) {
    429 					break;
    430 				}
    431 			}
    432 			++j;
    433 			for (; j < _ins; ++j) {
    434 				_switchParamQuantities[i * _ins + j]->setValue(0.0f);
    435 			}
    436 		}
    437 	}
    438 }
    439 
    440 
    441 void SwitchMatrixModuleWidget::contextMenu(Menu* menu) {
    442 	auto m = dynamic_cast<SwitchMatrixModule*>(module);
    443 	assert(m);
    444 	MatrixModuleWidget::contextMenu(menu);
    445 
    446 	OptionsMenuItem* i = new OptionsMenuItem("Inverting");
    447 	i->addItem(OptionMenuItem("Disabled", [m]() { return m->_inverting == SwitchMatrixModule::NO_INVERTING; }, [m]() { m->setInverting(SwitchMatrixModule::NO_INVERTING); }));
    448 	i->addItem(OptionMenuItem("By param entry (right-click)", [m]() { return m->_inverting == SwitchMatrixModule::PARAM_INVERTING; }, [m]() { m->setInverting(SwitchMatrixModule::PARAM_INVERTING); }));
    449 	i->addItem(OptionMenuItem("On second click", [m]() { return m->_inverting == SwitchMatrixModule::CLICK_INVERTING; }, [m]() { m->setInverting(SwitchMatrixModule::CLICK_INVERTING); }));
    450 	OptionsMenuItem::addToMenu(i, menu);
    451 
    452 	if (m->_outs > 1) {
    453 		std::string label("Exclusive switching");
    454 		if (m->_ins > 1) {
    455 			label += " by rows";
    456 		}
    457 		menu->addChild(new OptionMenuItem(label.c_str(), [m]() { return m->_rowExclusive; }, [m]() { m->setRowExclusive(!m->_rowExclusive); }));
    458 	}
    459 	if (m->_ins > 1) {
    460 		std::string label("Exclusive switching");
    461 		if (m->_outs > 1) {
    462 			label += " by columns";
    463 		}
    464 		menu->addChild(new OptionMenuItem(label.c_str(), [m]() { return m->_columnExclusive; }, [m]() { m->setColumnExclusive(!m->_columnExclusive); }));
    465 	}
    466 }
    467 
    468 
    469 #define SOLO_BY_COLUMNS "solo_by_columns"
    470 
    471 json_t* MutesMatrixExpanderModule::saveToJson(json_t* root) {
    472 	json_object_set_new(root, SOLO_BY_COLUMNS, json_boolean(_soloByColumns));
    473 	return root;
    474 }
    475 
    476 void MutesMatrixExpanderModule::loadFromJson(json_t* root) {
    477 	json_t* sbc = json_object_get(root, SOLO_BY_COLUMNS);
    478 	if (sbc) {
    479 		_soloByColumns = json_is_true(sbc);
    480 	}
    481 }
    482 
    483 
    484 void MutesMatrixExpanderModuleWidget::contextMenu(Menu* menu) {
    485 	auto m = dynamic_cast<MutesMatrixExpanderModule*>(module);
    486 	assert(m);
    487 	menu->addChild(new BoolOptionMenuItem("Solo mutes by column", [m]() { return &m->_soloByColumns; }));
    488 }