diff --git a/res/console_mm_dark.svg b/res/console_mm_dark.svg
index 97c94ad..34abde2 100644
--- a/res/console_mm_dark.svg
+++ b/res/console_mm_dark.svg
@@ -1,89 +1,83 @@
diff --git a/src/console_mm.cpp b/src/console_mm.cpp
index 3d11295..054b613 100644
--- a/src/console_mm.cpp
+++ b/src/console_mm.cpp
@@ -9,7 +9,9 @@ Ported and designed by Jens Robert Janke
Changes/Additions:
- encodes, sums and decodes the direct outs of MixMaster
+- additional direct outputs, unprocessed or summed
- console types: Console6, PurestConsole
+- partly polyphonic
Additional code inspired by the Fundamental Mixer by Andrew Belt.
@@ -32,7 +34,7 @@ struct Console_mm : Module {
NUM_INPUTS
};
enum OutputIds {
- ENUMS(THRU_OUTPUTS, 3),
+ ENUMS(DIRECT_OUTPUTS, 3),
ENUMS(OUT_OUTPUTS, 2),
OUT_L_OUTPUT,
OUT_R_OUTPUT,
@@ -43,12 +45,16 @@ struct Console_mm : Module {
};
// module variables
- const double gainCut = 0.03125;
- const double gainBoost = 32.0;
+ const double gainFactor = 32;
bool quality;
int consoleType;
dsp::VuMeter2 vuMeters[9];
dsp::ClockDivider lightDivider;
+ enum directOutModes {
+ UNPROCESSED,
+ SUMMED
+ };
+ int directOutMode;
// state variables (as arrays in order to handle up to 16 polyphonic channels)
uint32_t fpd[16];
@@ -60,6 +66,7 @@ struct Console_mm : Module {
quality = loadQuality();
consoleType = loadConsoleType();
+ directOutMode = loadDirectOutMode();
lightDivider.setDivision(512);
onReset();
}
@@ -78,6 +85,9 @@ struct Console_mm : Module {
// quality
json_object_set_new(rootJ, "quality", json_integer(quality));
+ // directOutMode
+ json_object_set_new(rootJ, "directOutMode", json_integer(directOutMode));
+
// consoleType
json_object_set_new(rootJ, "consoleType", json_integer(consoleType));
@@ -91,6 +101,11 @@ struct Console_mm : Module {
if (qualityJ)
quality = json_integer_value(qualityJ);
+ // directOutMode
+ json_t* directOutModeJ = json_object_get(rootJ, "directOutMode");
+ if (directOutModeJ)
+ directOutMode = json_integer_value(directOutModeJ);
+
// consoleType
json_t* consoleTypeJ = json_object_get(rootJ, "consoleType");
if (consoleTypeJ)
@@ -141,13 +156,14 @@ struct Console_mm : Module {
void process(const ProcessArgs& args) override
{
- long double sum[] = { 0.0, 0.0 };
+ long double directOutSum[] = { 0.0, 0.0, 0.0 };
+ long double stereoOutSum[] = { 0.0, 0.0 };
// for each input
for (int x = 0; x < 3; x++) {
int numChannels = inputs[IN_INPUTS + x].getChannels();
- outputs[THRU_OUTPUTS + x].setChannels(numChannels);
+ outputs[DIRECT_OUTPUTS + x].setChannels(numChannels);
if (inputs[IN_INPUTS + x].isConnected()) {
@@ -157,14 +173,16 @@ struct Console_mm : Module {
// get input
long double inputSample = inputs[IN_INPUTS + x].getPolyVoltage(i);
- // first we send the input to the respective Thru output
- outputs[THRU_OUTPUTS + x].setVoltage(inputSample, i);
+ if (directOutMode == UNPROCESSED) {
+ // send the input directly to the respective output
+ outputs[DIRECT_OUTPUTS + x].setVoltage(inputSample, i);
+ }
// only process if there is a signal
if (inputSample) {
// pad gain, will be boosted before output
- inputSample *= gainCut;
+ inputSample /= gainFactor;
if (quality == HIGH) {
if (fabs(inputSample) < 1.18e-37)
@@ -174,40 +192,72 @@ struct Console_mm : Module {
// encode
inputSample = encode(inputSample, consoleType);
- // add alternately to left or right sum
- sum[i % 2] += inputSample;
+ // add alternately to the left or right channel of the stereo sum
+ stereoOutSum[i % 2] += inputSample;
+
+ if (directOutMode == SUMMED) {
+ // add processed sample to respective output sum
+ directOutSum[x] += inputSample;
+ }
+ }
+ }
+ }
+ }
+
+ if (directOutMode == SUMMED) {
+ // for each direct output in summing mode
+ for (int i = 0; i < 3; i++) {
+ if (outputs[DIRECT_OUTPUTS + i].isConnected()) {
+
+ // decode
+ directOutSum[i] = decode(directOutSum[i], consoleType);
+
+ if (quality == HIGH) {
+ // 32 bit floating point dither
+ int expon;
+ frexpf((float)directOutSum[i], &expon);
+ fpd[i] ^= fpd[i] << 13;
+ fpd[i] ^= fpd[i] >> 17;
+ fpd[i] ^= fpd[i] << 5;
+ directOutSum[i] += ((double(fpd[i]) - uint32_t(0x7fffffff)) * 5.5e-36l * pow(2, expon + 62));
}
+
+ // bring gain back up
+ directOutSum[i] *= gainFactor;
}
+
+ // outputs
+ outputs[DIRECT_OUTPUTS + i].setVoltage(directOutSum[i]);
}
}
- // for each output
+ // for each stereo channel
for (int i = 0; i < 2; i++) {
if (outputs[OUT_OUTPUTS + i].isConnected()) {
// decode
- sum[i] = decode(sum[i], consoleType);
+ stereoOutSum[i] = decode(stereoOutSum[i], consoleType);
if (quality == HIGH) {
// 32 bit floating point dither
int expon;
- frexpf((float)sum[i], &expon);
+ frexpf((float)stereoOutSum[i], &expon);
fpd[i] ^= fpd[i] << 13;
fpd[i] ^= fpd[i] >> 17;
fpd[i] ^= fpd[i] << 5;
- sum[i] += ((double(fpd[i]) - uint32_t(0x7fffffff)) * 5.5e-36l * pow(2, expon + 62));
+ stereoOutSum[i] += ((double(fpd[i]) - uint32_t(0x7fffffff)) * 5.5e-36l * pow(2, expon + 62));
}
// bring gain back up
- sum[i] *= gainBoost;
+ stereoOutSum[i] *= gainFactor;
}
// outpul level control
- sum[i] *= pow(params[LEVEL_PARAM].getValue(), 3);
+ stereoOutSum[i] *= pow(params[LEVEL_PARAM].getValue(), 3);
// outputs
- outputs[OUT_OUTPUTS + i].setVoltage(sum[i]);
+ outputs[OUT_OUTPUTS + i].setVoltage(stereoOutSum[i]);
}
}
};
@@ -246,6 +296,22 @@ struct Console_mmWidget : ModuleWidget {
}
};
+ // directoutMode item
+ struct DirectOutModeItem : MenuItem {
+ Console_mm* module;
+ int directOutMode;
+
+ void onAction(const event::Action& e) override
+ {
+ module->directOutMode = directOutMode;
+ }
+
+ void step() override
+ {
+ rightText = (module->directOutMode == directOutMode) ? "✔" : "";
+ }
+ };
+
void appendContextMenu(Menu* menu) override
{
Console_mm* module = dynamic_cast(this->module);
@@ -272,7 +338,7 @@ struct Console_mmWidget : ModuleWidget {
menu->addChild(new MenuSeparator()); // separator
MenuLabel* consoleTypeLabel = new MenuLabel(); // menu label
- consoleTypeLabel->text = "Type";
+ consoleTypeLabel->text = "Console Type";
menu->addChild(consoleTypeLabel);
ConsoleTypeItem* console6 = new ConsoleTypeItem(); // Console6
@@ -286,6 +352,24 @@ struct Console_mmWidget : ModuleWidget {
purestConsole->module = module;
purestConsole->consoleType = 1;
menu->addChild(purestConsole);
+
+ menu->addChild(new MenuSeparator()); // separator
+
+ MenuLabel* directOutModeLabel = new MenuLabel(); // menu label
+ directOutModeLabel->text = "Direct Output Mode";
+ menu->addChild(directOutModeLabel);
+
+ DirectOutModeItem* unprocessed = new DirectOutModeItem(); // unprocessed
+ unprocessed->text = "Unprocessed";
+ unprocessed->module = module;
+ unprocessed->directOutMode = 0;
+ menu->addChild(unprocessed);
+
+ DirectOutModeItem* summed = new DirectOutModeItem(); // summed
+ summed->text = "Summed";
+ summed->module = module;
+ summed->directOutMode = 1;
+ menu->addChild(summed);
}
Console_mmWidget(Console_mm* module)
@@ -300,19 +384,19 @@ struct Console_mmWidget : ModuleWidget {
addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
// params
- addParam(createParamCentered(Vec(45.0, 260.0), module, Console_mm::LEVEL_PARAM));
+ addParam(createParamCentered(Vec(45.0, 310.0), module, Console_mm::LEVEL_PARAM));
// inputs
- addInput(createInputCentered(Vec(26.25, 55.0), module, Console_mm::IN_INPUTS + 0));
- addInput(createInputCentered(Vec(26.25, 95.0), module, Console_mm::IN_INPUTS + 1));
- addInput(createInputCentered(Vec(26.25, 135.0), module, Console_mm::IN_INPUTS + 2));
+ addInput(createInputCentered(Vec(26.25, 75.0), module, Console_mm::IN_INPUTS + 0));
+ addInput(createInputCentered(Vec(26.25, 115.0), module, Console_mm::IN_INPUTS + 1));
+ addInput(createInputCentered(Vec(26.25, 155.0), module, Console_mm::IN_INPUTS + 2));
// outputs
- addOutput(createOutputCentered(Vec(63.695, 55.0), module, Console_mm::THRU_OUTPUTS + 0));
- addOutput(createOutputCentered(Vec(63.695, 95.0), module, Console_mm::THRU_OUTPUTS + 1));
- addOutput(createOutputCentered(Vec(63.75, 135.0), module, Console_mm::THRU_OUTPUTS + 2));
- addOutput(createOutputCentered(Vec(26.25, 325.0), module, Console_mm::OUT_OUTPUTS + 0));
- addOutput(createOutputCentered(Vec(63.75, 325.0), module, Console_mm::OUT_OUTPUTS + 1));
+ addOutput(createOutputCentered(Vec(63.75, 75.0), module, Console_mm::DIRECT_OUTPUTS + 0));
+ addOutput(createOutputCentered(Vec(63.75, 115.0), module, Console_mm::DIRECT_OUTPUTS + 1));
+ addOutput(createOutputCentered(Vec(63.75, 155.0), module, Console_mm::DIRECT_OUTPUTS + 2));
+ addOutput(createOutputCentered(Vec(26.25, 245.0), module, Console_mm::OUT_OUTPUTS + 0));
+ addOutput(createOutputCentered(Vec(63.75, 245.0), module, Console_mm::OUT_OUTPUTS + 1));
}
};
diff --git a/src/plugin.cpp b/src/plugin.cpp
index 42659a5..8015ef0 100644
--- a/src/plugin.cpp
+++ b/src/plugin.cpp
@@ -149,6 +149,46 @@ int loadConsoleType()
return ret;
}
+// direct output mode
+void saveDirectOutMode(bool directOutMode)
+{
+ json_t* settingsJ = json_object();
+ json_object_set_new(settingsJ, "directOutMode", json_boolean(directOutMode));
+ std::string settingsFilename = asset::user("Rackwindows.json");
+ FILE* file = fopen(settingsFilename.c_str(), "w");
+ if (file) {
+ json_dumpf(settingsJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
+ fclose(file);
+ }
+ json_decref(settingsJ);
+}
+
+bool loadDirectOutMode()
+{
+ bool ret = false;
+ std::string settingsFilename = asset::user("Rackwindows.json");
+ FILE* file = fopen(settingsFilename.c_str(), "r");
+ if (!file) {
+ saveDirectOutMode(false);
+ return ret;
+ }
+ json_error_t error;
+ json_t* settingsJ = json_loadf(file, 0, &error);
+ if (!settingsJ) {
+ // invalid setting json file
+ fclose(file);
+ saveDirectOutMode(false);
+ return ret;
+ }
+ json_t* directOutModeJ = json_object_get(settingsJ, "directOutMode");
+ if (directOutModeJ)
+ ret = json_boolean_value(directOutModeJ);
+
+ fclose(file);
+ json_decref(settingsJ);
+ return ret;
+}
+
// https://github.com/MarcBoule/Geodesics/blob/master/src/Geodesics.cpp
void saveDarkAsDefault(bool darkAsDefault)
{
diff --git a/src/plugin.hpp b/src/plugin.hpp
index 6669067..6bce3fd 100644
--- a/src/plugin.hpp
+++ b/src/plugin.hpp
@@ -44,6 +44,10 @@ struct highQualityDefaultItem : MenuItem {
void saveConsoleType(int consoleType);
int loadConsoleType();
+// direct output mode
+void saveDirectOutMode(int directOutMode);
+bool loadDirectOutMode();
+
// themes
static const std::string lightPanelID = "Light Panel";
static const std::string darkPanelID = "Dark Panel";
@@ -57,4 +61,4 @@ struct DarkDefaultItem : MenuItem {
{
saveDarkAsDefault(rightText.empty()); // implicitly toggled
}
-};
\ No newline at end of file
+};