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:
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;
};