commit a226da4fd3a558cbc0321a8823568db12b5d389e
parent 60efafdb6433f7530ebd80382e91fdbadc07b5b9
Author: Matt Demanett <matt@demanett.net>
Date: Mon, 17 Aug 2020 14:13:33 -0400
SWITCH*, MATRIX*: averaging mode everywhere. #135
Diffstat:
9 files changed, 62 insertions(+), 71 deletions(-)
diff --git a/README-prerelease.md b/README-prerelease.md
@@ -528,12 +528,18 @@ _Polyphony:_ <a href="#polyphony">Polyphonic</a>, with polyphonic channels defin
Essentially identical to UMIX, but with mute buttons for each input.
-If averaging mode is enabled, note that the averaging is based on how many inputs are connected and not muted. For example, if three inputs are connected, and one is muted, the output will be the sum of the two unmuted channels, divided by two.
+If averaging mode is enabled, note that the divisor for the average is the count of how many inputs are connected. For example, if three inputs are connected, and one is muted, the output will be the sum of the two unmuted channels, divided by three.
See also <a href="switch81">SWITCH81</a>, which is similar to this, with options to attenuate or invert the inputs.
_Polyphony:_ same as UMIX.
+#### <a name="matrix81"></a> MATRIX81
+
+An eight input, one output version of <a href="#matrix44">MATRIX44</a>, below.
+
+_Polyphony:_ <a href="#polyphony">Polyphonic</a>, as on MATRIX44.
+
#### <a name="matrix44"></a> MATRIX44
An 4x4 channel matrix mixer. Each input can be routed with an independent level to each of the eight output mixes.
@@ -544,6 +550,8 @@ Saturation (soft clipping) limits each output to +/-12V. This can be changed to
Another context menu option allows the input gains to be reduced by up to 12db.
+Option "Average" sets the output to be the average of its inputs. The divisor for the average is the number of inputs in use; for example, if three inputs are connected, each output will be the sum of those inputs, scaled by the corresponding knobs, and divided by three.
+
The knobs visually indicate their values with green/orange colors. This can be disabled on the context menu.
_Polyphony:_ <a href="#polyphony">Polyphonic</a>, with polyphonic channels defined by input 1.
@@ -575,6 +583,8 @@ The signal inverting behavior may be set with the "Inverting" context menu optio
- "On second click" causes a click on a non-inverting but enabled switch to change to inverting; anotehr click turns it off.
- "None" disables inverting entirely. This option is handy if you want to map MIDI controller buttons/pads to switches.
+Option "Average" sets the output to be the average of its inputs. The divisor for the average is the number of inputs in use; for example, if three inputs are connected, each output will be the sum of those inputs, scaled by the corresponding switch values, and divided by three.
+
Two other options, "Exclusive by rows" and "Exclusive by columns", if enabled, allow only one switch to be non-zero in a row, or column, respectively. Both may be on at once. (These options do not work well with MIDI mapping via Rack's MIDI-MAP module; this is a known issue for which there is no good solution; but see the discussion [here](https://github.com/bogaudio/BogaudioModules/issues/112) for a potential workaround. The same problem may apply to other parameter-mapping methods.)
Every switch applies a bit of slew limitation when it changes values, as an anti-popping measure.
diff --git a/src/Mumix.cpp b/src/Mumix.cpp
@@ -31,25 +31,28 @@ void Mumix::modulate() {
for (int i = 0; i < 8; ++i) {
_muted[i] = solo ? params[MUTE1_PARAM + i].getValue() < 2.0f : params[MUTE1_PARAM + i].getValue() > 0.5f;
}
-}
-void Mumix::processAlways(const ProcessArgs& args) {
- int active = 0;
- for (int i = 0; i < 8; ++i) {
- float level = _slewLimiters[i].next(_muted[i] ? minDecibels : maxDecibels);
- _amplifiers[i].setLevel(level);
- if (!_sum && inputs[IN1_INPUT + i].isConnected() && level > minDecibels + 1.0f) {
- ++active;
- }
- }
if (_sum) {
_invActive = 0.0f;
}
else {
+ int active = 0;
+ for (int i = 0; i < 8; ++i) {
+ if (inputs[IN1_INPUT + i].isConnected()) {
+ ++active;
+ }
+ }
_invActive = active > 0 ? 1.0f / (float)active : 0.0f;
}
}
+void Mumix::processAlways(const ProcessArgs& args) {
+ for (int i = 0; i < 8; ++i) {
+ float level = _slewLimiters[i].next(_muted[i] ? minDecibels : maxDecibels);
+ _amplifiers[i].setLevel(level);
+ }
+}
+
void Mumix::processChannel(const ProcessArgs& args, int c) {
outputs[OUT_OUTPUT].setChannels(_channels);
@@ -68,7 +71,7 @@ void Mumix::processChannel(const ProcessArgs& args, int c) {
}
}
-struct MumixWidget : SumAverageModuleWidget {
+struct MumixWidget : MatrixBaseModuleWidget {
static constexpr int hp = 6;
MumixWidget(Mumix* module) {
diff --git a/src/Mumix.hpp b/src/Mumix.hpp
@@ -1,7 +1,7 @@
#pragma once
#include "bogaudio.hpp"
-#include "sum_average.hpp"
+#include "matrix_base.hpp"
#include "dsp/signal.hpp"
using namespace bogaudio::dsp;
@@ -10,7 +10,7 @@ extern Model* modelMumix;
namespace bogaudio {
-struct Mumix : SumAverageModule {
+struct Mumix : MatrixBaseModule {
enum ParamsIds {
MUTE1_PARAM,
MUTE2_PARAM,
diff --git a/src/UMix.cpp b/src/UMix.cpp
@@ -48,7 +48,7 @@ void UMix::processChannel(const ProcessArgs& args, int c) {
}
}
-struct UMixWidget : SumAverageModuleWidget {
+struct UMixWidget : MatrixBaseModuleWidget {
static constexpr int hp = 3;
UMixWidget(UMix* module) {
diff --git a/src/UMix.hpp b/src/UMix.hpp
@@ -1,7 +1,7 @@
#pragma once
#include "bogaudio.hpp"
-#include "sum_average.hpp"
+#include "matrix_base.hpp"
#include "dsp/signal.hpp"
using namespace bogaudio::dsp;
@@ -10,7 +10,7 @@ extern Model* modelUMix;
namespace bogaudio {
-struct UMix : SumAverageModule {
+struct UMix : MatrixBaseModule {
enum ParamsIds {
NUM_PARAMS
};
diff --git a/src/matrix_base.cpp b/src/matrix_base.cpp
@@ -7,10 +7,12 @@ using namespace bogaudio::dsp;
#define CLIPPING_MODE "clipping_mode"
#define INPUT_GAIN_DB "input_gain_db"
+#define SUM "sum"
json_t* MatrixBaseModule::toJson(json_t* root) {
json_object_set_new(root, CLIPPING_MODE, json_integer(_clippingMode));
json_object_set_new(root, INPUT_GAIN_DB, json_real(_inputGainDb));
+ json_object_set_new(root, SUM, json_boolean(_sum));
return root;
}
@@ -27,6 +29,11 @@ void MatrixBaseModule::fromJson(json_t* root) {
if (g) {
_inputGainDb = clamp(json_real_value(g), -60.0f, 6.0f);
}
+
+ json_t* s = json_object_get(root, SUM);
+ if (s) {
+ _sum = json_is_true(s);
+ }
}
void MatrixBaseModule::modulate() {
@@ -49,6 +56,8 @@ void MatrixBaseModuleWidget::contextMenu(Menu* menu) {
c->addItem(OptionMenuItem("Soft/saturated (better for audio)", [m]() { return m->_clippingMode == MatrixBaseModule::SOFT_CLIPPING; }, [m]() { m->_clippingMode = MatrixBaseModule::SOFT_CLIPPING; }));
c->addItem(OptionMenuItem("Hard/clipped (better for CV)", [m]() { return m->_clippingMode == MatrixBaseModule::HARD_CLIPPING; }, [m]() { m->_clippingMode = MatrixBaseModule::HARD_CLIPPING; }));
OptionsMenuItem::addToMenu(c, menu);
+
+ menu->addChild(new OptionMenuItem("Average", [m]() { return !m->_sum; }, [m]() { m->_sum = !m->_sum; }));
}
@@ -65,20 +74,27 @@ int MatrixModule::channels() {
void MatrixModule::modulate() {
MatrixBaseModule::modulate();
+
+ int active = 0;
for (int i = 0; i < _ins; ++i) {
+ _inActive[i] = inputs[_firstInputID + i].isConnected();
+ if (_inActive[i]) {
+ ++active;
+ }
+
for (int j = 0; j < _outs; ++j) {
int ii = j * _outs + i;
_paramValues[ii] = _sls[ii].next(params[_firstParamID + ii].getValue());
}
}
+
+ _invActive = (!_sum && active > 0) ? 1.0f / (float)active : 0.0f;
}
void MatrixModule::processChannel(const ProcessArgs& args, int c) {
- bool inActive[maxN] {};
float in[maxN] {};
for (int i = 0; i < _ins; ++i) {
- inActive[i] = inputs[_firstInputID + i].isConnected();
- if (inActive[i]) {
+ if (_inActive[i]) {
in[i] = inputs[_firstInputID + i].getPolyVoltage(c) * _inputGainLevel;
}
}
@@ -89,11 +105,17 @@ void MatrixModule::processChannel(const ProcessArgs& args, int c) {
}
float out = 0.0f;
for (int j = 0; j < _ins; ++j) {
- if (inActive[j]) {
+ if (_inActive[j]) {
out += in[j] * _paramValues[i * _outs + j];
}
}
- if (_clippingMode != HARD_CLIPPING) {
+ if (!_sum && _invActive > 0.0f) {
+ out *= _invActive;
+ }
+ if (_clippingMode == HARD_CLIPPING) {
+ out = clamp(out, -12.0f, 12.0f);
+ }
+ else {
out = _saturators[c * _outs + i].next(out);
}
outputs[_firstOutputID + i].setChannels(_channels);
diff --git a/src/matrix_base.hpp b/src/matrix_base.hpp
@@ -18,6 +18,7 @@ struct MatrixBaseModule : BGModule {
Clipping _clippingMode = SOFT_CLIPPING;
float _inputGainDb = 0.0f;
float _inputGainLevel = 1.0f;
+ bool _sum = true;
json_t* toJson(json_t* root) override;
void fromJson(json_t* root) override;
@@ -39,6 +40,8 @@ struct MatrixModule : MatrixBaseModule {
float* _paramValues = NULL;
bogaudio::dsp::SlewLimiter* _sls = NULL;
Saturator* _saturators = NULL;
+ bool* _inActive = NULL;
+ float _invActive = 0.0f;
MatrixModule(int ins, int outs, int firstParamID, int firstInputID, int firstOutputID)
: _ins(ins)
@@ -52,11 +55,13 @@ struct MatrixModule : MatrixBaseModule {
_paramValues = new float[_ins * _outs] {};
_sls = new bogaudio::dsp::SlewLimiter[_ins * _outs];
_saturators = new Saturator[_outs * maxChannels];
+ _inActive = new bool[_ins] {};
}
virtual ~MatrixModule() {
delete[] _paramValues;
delete[] _sls;
delete[] _saturators;
+ delete[] _inActive;
}
void sampleRateChange() override;
diff --git a/src/sum_average.cpp b/src/sum_average.cpp
@@ -1,28 +0,0 @@
-
-#include "sum_average.hpp"
-
-using namespace bogaudio;
-
-#define SUM "sum"
-
-json_t* SumAverageModule::toJson(json_t* root) {
- root = MatrixBaseModule::toJson(root);
- json_object_set_new(root, SUM, json_boolean(_sum));
- return root;
-}
-
-void SumAverageModule::fromJson(json_t* root) {
- MatrixBaseModule::fromJson(root);
- json_t* s = json_object_get(root, SUM);
- if (s) {
- _sum = json_is_true(s);
- }
-}
-
-
-void SumAverageModuleWidget::contextMenu(Menu* menu) {
- auto m = dynamic_cast<SumAverageModule*>(module);
- assert(m);
- MatrixBaseModuleWidget::contextMenu(menu);
- menu->addChild(new OptionMenuItem("Average", [m]() { return !m->_sum; }, [m]() { m->_sum = !m->_sum; }));
-}
diff --git a/src/sum_average.hpp b/src/sum_average.hpp
@@ -1,21 +0,0 @@
-#pragma once
-
-#include "bogaudio.hpp"
-#include "matrix_base.hpp"
-
-using namespace rack;
-
-namespace bogaudio {
-
-struct SumAverageModule : MatrixBaseModule {
- bool _sum = true;
-
- json_t* toJson(json_t* root) override;
- void fromJson(json_t* root) override;
-};
-
-struct SumAverageModuleWidget : MatrixBaseModuleWidget {
- void contextMenu(Menu* menu) override;
-};
-
-} // namespace bogaudio