BogaudioModules

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

commit 05004e51ea90476c45cbd89cf69149c38f8f776a
parent 5b143766a79d1ab0dd439d6df0cceabf7a0a7c17
Author: Matt Demanett <matt@demanett.net>
Date:   Sun, 25 Jul 2021 23:08:05 -0400

MIX8, MIX4, MIX2, MIX1: add context menu option to make the level CV response linear in amplitude. #179

Diffstat:
MREADME-prerelease.md | 1+
Msrc/Mix1.cpp | 4++--
Msrc/Mix1.hpp | 2+-
Msrc/Mix2.cpp | 6+++---
Msrc/Mix2.hpp | 2+-
Msrc/Mix4.cpp | 25++++++++++++++++++++-----
Msrc/Mix4.hpp | 1+
Msrc/Mix8.cpp | 25++++++++++++++++++++-----
Msrc/Mix8.hpp | 1+
Msrc/mixer.cpp | 40+++++++++++++++++++++++++++++++++++++---
Msrc/mixer.hpp | 18+++++++++++++++---
11 files changed, 102 insertions(+), 23 deletions(-)

diff --git a/README-prerelease.md b/README-prerelease.md @@ -541,6 +541,7 @@ Features: - Right-clicking a mute buttons solos that channel (un-mutes that channel and temporarily mutes all others). Right or left click will un-solo, restoring the old state. Multiple channels can be "soloed" at once. - The fader handles contain lights indicating the signal level out of that channel. - The master output has MUTE and DIM controls (DIM is a partial mute, with a value configurable on the context menu; it defaults to -12dB). + - When context menu option "Linear level CV response" is enabled, level CV inputs on each channel and on the master output affect the corresponding level linearly in amplitude (that is, a 5V input would cut the level set by the slider by half); by default they respond in decibels. - The output saturates (soft clips) at +/-12 volts. _Polyphony:_ The module is monophonic: if a polyphonic cable is present at an input, its channels will be summed. diff --git a/src/Mix1.cpp b/src/Mix1.cpp @@ -38,7 +38,7 @@ void Mix1::processAlways(const ProcessArgs& args) { void Mix1::processChannel(const ProcessArgs& args, int c) { MixerChannel& e = *_engines[c]; - e.next(inputs[IN_INPUT].getVoltage(c), false, c); + e.next(inputs[IN_INPUT].getVoltage(c), false, c, _linearCV); _rmsSum += e.rms; outputs[OUT_OUTPUT].setChannels(_channels); outputs[OUT_OUTPUT].setVoltage(e.out, c); @@ -48,7 +48,7 @@ void Mix1::postProcessAlways(const ProcessArgs& args) { _rms = _rmsSum * _inverseChannels; } -struct Mix1Widget : BGModuleWidget { +struct Mix1Widget : LinearCVMixerWidget { static constexpr int hp = 3; Mix1Widget(Mix1* module) { diff --git a/src/Mix1.hpp b/src/Mix1.hpp @@ -10,7 +10,7 @@ extern Model* modelMix1; namespace bogaudio { -struct Mix1 : BGModule { +struct Mix1 : LinearCVMixerModule { enum ParamsIds { LEVEL_PARAM, MUTE_PARAM, diff --git a/src/Mix2.cpp b/src/Mix2.cpp @@ -40,7 +40,7 @@ void Mix2::processChannel(const ProcessArgs& args, int c) { Engine& e = *_engines[c]; float left = inputs[L_INPUT].getVoltage(c); - e.left.next(left, false, c); + e.left.next(left, false, c, _linearCV); _leftRmsSum += e.left.rms; outputs[L_OUTPUT].setChannels(_channels); outputs[L_OUTPUT].setVoltage(e.left.out, c); @@ -49,7 +49,7 @@ void Mix2::processChannel(const ProcessArgs& args, int c) { if (inputs[R_INPUT].isConnected()) { right = inputs[R_INPUT].getVoltage(c); } - e.right.next(right, false, c); + e.right.next(right, false, c, _linearCV); _rightRmsSum += e.right.rms; outputs[R_OUTPUT].setChannels(_channels); outputs[R_OUTPUT].setVoltage(e.right.out, c); @@ -60,7 +60,7 @@ void Mix2::postProcessAlways(const ProcessArgs& args) { _rightRms = _rightRmsSum * _inverseChannels; } -struct Mix2Widget : BGModuleWidget { +struct Mix2Widget : LinearCVMixerWidget { static constexpr int hp = 5; Mix2Widget(Mix2* module) { diff --git a/src/Mix2.hpp b/src/Mix2.hpp @@ -10,7 +10,7 @@ extern Model* modelMix2; namespace bogaudio { -struct Mix2 : BGModule { +struct Mix2 : LinearCVMixerModule { enum ParamsIds { LEVEL_PARAM, MUTE_PARAM, diff --git a/src/Mix4.cpp b/src/Mix4.cpp @@ -24,6 +24,7 @@ void Mix4::sampleRateChange() { _panSLs[i].setParams(sr, MIXER_PAN_SLEW_MS, 2.0f); } _slewLimiter.setParams(sr, MixerChannel::levelSlewTimeMS, MixerChannel::maxDecibels - MixerChannel::minDecibels); + _levelCVSL.setParams(sr, MixerChannel::levelSlewTimeMS, 1.0f); _rms.setSampleRate(sr); } @@ -68,7 +69,7 @@ void Mix4::processAll(const ProcessArgs& args) { } else { sample = inputs[IN1_INPUT].getVoltageSum(); } - _channels[0]->next(sample, solo); + _channels[0]->next(sample, solo, 0, _linearCV); toExp->preFader[0] = sample; toExp->active[0] = inputs[IN1_INPUT].isConnected(); @@ -76,12 +77,12 @@ void Mix4::processAll(const ProcessArgs& args) { float sample = 0.0f; if (inputs[IN1_INPUT + 3 * i].isConnected()) { sample = inputs[IN1_INPUT + 3 * i].getVoltageSum(); - _channels[i]->next(sample, solo); + _channels[i]->next(sample, solo, 0, _linearCV); _channelActive[i] = true; } else if (_polyChannelOffset >= 0) { sample = inputs[IN1_INPUT].getPolyVoltage(_polyChannelOffset + i); - _channels[i]->next(sample, solo); + _channels[i]->next(sample, solo, 0, _linearCV); _channelActive[i] = true; } else if (_channelActive[i]) { @@ -93,11 +94,15 @@ void Mix4::processAll(const ProcessArgs& args) { } } + float levelCV = 1.0f; + if (inputs[MIX_CV_INPUT].isConnected()) { + levelCV = clamp(inputs[MIX_CV_INPUT].getVoltage() / 10.0f, 0.0f, 1.0f); + } float level = Amplifier::minDecibels; if (params[MIX_MUTE_PARAM].getValue() < 0.5f) { level = params[MIX_PARAM].getValue(); - if (inputs[MIX_CV_INPUT].isConnected()) { - level *= clamp(inputs[MIX_CV_INPUT].getVoltage() / 10.0f, 0.0f, 1.0f); + if (!_linearCV) { + level *= levelCV; } level *= MixerChannel::maxDecibels - MixerChannel::minDecibels; level += MixerChannel::minDecibels; @@ -106,6 +111,7 @@ void Mix4::processAll(const ProcessArgs& args) { } } _amplifier.setLevel(_slewLimiter.next(level)); + levelCV = _levelCVSL.next(levelCV); float outs[4]; for (int i = 0; i < 4; ++i) { @@ -126,6 +132,9 @@ void Mix4::processAll(const ProcessArgs& args) { mono += outs[i]; } mono = _amplifier.next(mono); + if (_linearCV) { + mono *= levelCV; + } mono = _saturator.next(mono); _rmsLevel = _rms.next(mono) / 5.0f; @@ -143,10 +152,16 @@ void Mix4::processAll(const ProcessArgs& args) { } left = _amplifier.next(left); + if (_linearCV) { + left *= levelCV; + } left = _saturator.next(left); outputs[L_OUTPUT].setVoltage(left); right = _amplifier.next(right); + if (_linearCV) { + right *= levelCV; + } right = _saturator.next(right); outputs[R_OUTPUT].setVoltage(right); } diff --git a/src/Mix4.hpp b/src/Mix4.hpp @@ -61,6 +61,7 @@ struct Mix4 : ExpandableModule<Mix4ExpanderMessage, DimmableMixerModule> { float _rmsLevel = 0.0f; Mix4ExpanderMessage _dummyExpanderMessage; int _wasActive = 0; + bogaudio::dsp::SlewLimiter _levelCVSL; Mix4() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); diff --git a/src/Mix8.cpp b/src/Mix8.cpp @@ -24,6 +24,7 @@ void Mix8::sampleRateChange() { _panSLs[i].setParams(sr, MIXER_PAN_SLEW_MS, 2.0f); } _slewLimiter.setParams(sr, MixerChannel::levelSlewTimeMS, MixerChannel::maxDecibels - MixerChannel::minDecibels); + _levelCVSL.setParams(sr, MixerChannel::levelSlewTimeMS, 1.0f); _rms.setSampleRate(sr); } @@ -76,7 +77,7 @@ void Mix8::processAll(const ProcessArgs& args) { } else { sample = inputs[IN1_INPUT].getVoltageSum(); } - _channels[0]->next(sample, solo); + _channels[0]->next(sample, solo, 0, _linearCV); toExp->preFader[0] = sample; toExp->active[0] = inputs[IN1_INPUT].isConnected(); @@ -84,12 +85,12 @@ void Mix8::processAll(const ProcessArgs& args) { float sample = 0.0f; if (inputs[IN1_INPUT + 3 * i].isConnected()) { sample = inputs[IN1_INPUT + 3 * i].getVoltageSum(); - _channels[i]->next(sample, solo); + _channels[i]->next(sample, solo, 0, _linearCV); _channelActive[i] = true; } else if (_polyChannelOffset >= 0) { sample = inputs[IN1_INPUT].getPolyVoltage(_polyChannelOffset + i); - _channels[i]->next(sample, solo); + _channels[i]->next(sample, solo, 0, _linearCV); _channelActive[i] = true; } else if (_channelActive[i]) { @@ -101,11 +102,15 @@ void Mix8::processAll(const ProcessArgs& args) { } } + float levelCV = 1.0f; + if (inputs[MIX_CV_INPUT].isConnected()) { + levelCV = clamp(inputs[MIX_CV_INPUT].getVoltage() / 10.0f, 0.0f, 1.0f); + } float level = Amplifier::minDecibels; if (params[MIX_MUTE_PARAM].getValue() < 0.5f) { level = params[MIX_PARAM].getValue(); - if (inputs[MIX_CV_INPUT].isConnected()) { - level *= clamp(inputs[MIX_CV_INPUT].getVoltage() / 10.0f, 0.0f, 1.0f); + if (!_linearCV) { + level *= levelCV; } level *= MixerChannel::maxDecibels - MixerChannel::minDecibels; level += MixerChannel::minDecibels; @@ -114,6 +119,7 @@ void Mix8::processAll(const ProcessArgs& args) { } } _amplifier.setLevel(_slewLimiter.next(level)); + levelCV = _levelCVSL.next(levelCV); float outs[8]; for (int i = 0; i < 8; ++i) { @@ -134,6 +140,9 @@ void Mix8::processAll(const ProcessArgs& args) { mono += outs[i]; } mono = _amplifier.next(mono); + if (_linearCV) { + mono *= levelCV; + } mono = _saturator.next(mono); _rmsLevel = _rms.next(mono) / 5.0f; @@ -151,10 +160,16 @@ void Mix8::processAll(const ProcessArgs& args) { } left = _amplifier.next(left); + if (_linearCV) { + left *= levelCV; + } left = _saturator.next(left); outputs[L_OUTPUT].setVoltage(left); right = _amplifier.next(right); + if (_linearCV) { + right *= levelCV; + } right = _saturator.next(right); outputs[R_OUTPUT].setVoltage(right); } diff --git a/src/Mix8.hpp b/src/Mix8.hpp @@ -85,6 +85,7 @@ struct Mix8 : ExpandableModule<Mix8ExpanderMessage, DimmableMixerModule> { float _rmsLevel = 0.0f; Mix8ExpanderMessage _dummyExpanderMessage; int _wasActive = 0; + bogaudio::dsp::SlewLimiter _levelCVSL; Mix8() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); diff --git a/src/mixer.cpp b/src/mixer.cpp @@ -7,6 +7,7 @@ const float MixerChannel::levelSlewTimeMS = 5.0f; void MixerChannel::setSampleRate(float sampleRate) { _levelSL.setParams(sampleRate, levelSlewTimeMS, maxDecibels - minDecibels); + _levelCVSL.setParams(sampleRate, levelSlewTimeMS, 1.0f); _rms.setSampleRate(sampleRate); } @@ -14,7 +15,12 @@ void MixerChannel::reset() { out = rms = 0.0f; } -void MixerChannel::next(float sample, bool solo, int c) { +void MixerChannel::next(float sample, bool solo, int c, bool linearCV) { + float cv = 1.0f; + if (_levelInput.isConnected()) { + cv = clamp(_levelInput.getPolyVoltage(c) / 10.0f, 0.0f, 1.0f); + } + float mute = _muteParam.getValue(); if (_muteInput) { mute += clamp(_muteInput->getPolyVoltage(c), 0.0f, 10.0f); @@ -25,8 +31,8 @@ void MixerChannel::next(float sample, bool solo, int c) { } else { float level = clamp(_levelParam.getValue(), 0.0f, 1.0f); - if (_levelInput.isConnected()) { - level *= clamp(_levelInput.getPolyVoltage(c) / 10.0f, 0.0f, 1.0f); + if (!linearCV) { + level *= cv; } level *= maxDecibels - minDecibels; level += minDecibels; @@ -34,18 +40,45 @@ void MixerChannel::next(float sample, bool solo, int c) { } out = _amplifier.next(sample); + if (linearCV) { + out *= _levelCVSL.next(cv); + } rms = _rms.next(out / 5.0f); } +#define LINEAR_CV "linear_cv" + +json_t* LinearCVMixerModule::toJson(json_t* root) { + json_object_set_new(root, LINEAR_CV, json_boolean(_linearCV)); + return root; +} + +void LinearCVMixerModule::fromJson(json_t* root) { + json_t* l = json_object_get(root, LINEAR_CV); + if (l) { + _linearCV = json_boolean_value(l); + } +} + + +void LinearCVMixerWidget::contextMenu(Menu* menu) { + auto m = dynamic_cast<LinearCVMixerModule*>(module); + assert(m); + menu->addChild(new BoolOptionMenuItem("Linear level CV response", [m]() { return &m->_linearCV; })); +} + + #define DIM_DB "dim_decibels" json_t* DimmableMixerModule::toJson(json_t* root) { + root = LinearCVMixerModule::toJson(root); json_object_set_new(root, DIM_DB, json_real(_dimDb)); return root; } void DimmableMixerModule::fromJson(json_t* root) { + LinearCVMixerModule::fromJson(root); json_t* o = json_object_get(root, DIM_DB); if (o) { _dimDb = json_real_value(o); @@ -54,6 +87,7 @@ void DimmableMixerModule::fromJson(json_t* root) { void DimmableMixerWidget::contextMenu(Menu* menu) { + LinearCVMixerWidget::contextMenu(menu); auto m = dynamic_cast<DimmableMixerModule*>(module); assert(m); OptionsMenuItem* da = new OptionsMenuItem("Dim amount"); diff --git a/src/mixer.hpp b/src/mixer.hpp @@ -17,6 +17,7 @@ struct MixerChannel { Amplifier _amplifier; bogaudio::dsp::SlewLimiter _levelSL; + bogaudio::dsp::SlewLimiter _levelCVSL; RootMeanSquare _rms; Param& _levelParam; @@ -45,17 +46,28 @@ struct MixerChannel { void setSampleRate(float sampleRate); void reset(); - void next(float sample, bool solo, int c = 0); // outputs on members out, rms. + void next(float sample, bool solo, int c = 0, bool linearCV = false); // outputs on members out, rms. }; -struct DimmableMixerModule : BGModule { +struct LinearCVMixerModule : BGModule { + bool _linearCV = false; + + json_t* toJson(json_t* root) override; + void fromJson(json_t* root) override; +}; + +struct LinearCVMixerWidget : BGModuleWidget { + void contextMenu(Menu* menu) override; +}; + +struct DimmableMixerModule : LinearCVMixerModule { float _dimDb = 12.0f; json_t* toJson(json_t* root) override; void fromJson(json_t* root) override; }; -struct DimmableMixerWidget : BGModuleWidget { +struct DimmableMixerWidget : LinearCVMixerWidget { void contextMenu(Menu* menu) override; };