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 }