|
- // Mutable Instruments Streams emulation for VCV Rack
- // Copyright (C) 2020 Tyler Coy
- //
- // This program is free software: you can redistribute it and/or modify
- // it under the terms of the GNU General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // This program is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU General Public License for more details.
- //
- // You should have received a copy of the GNU General Public License
- // along with this program. If not, see <https://www.gnu.org/licenses/>.
-
- #include <string>
- #include <algorithm>
- #include "plugin.hpp"
- #include "Streams/streams.hpp"
-
- namespace streams {
-
- struct StreamsChannelMode {
- ProcessorFunction function;
- bool alternate;
- std::string label;
- };
-
- static constexpr int kNumChannelModes = 10;
-
- static const StreamsChannelMode kChannelModeTable[kNumChannelModes] = {
- {PROCESSOR_FUNCTION_ENVELOPE, false, "Envelope"},
- {PROCESSOR_FUNCTION_VACTROL, false, "Vactrol"},
- {PROCESSOR_FUNCTION_FOLLOWER, false, "Follower"},
- {PROCESSOR_FUNCTION_COMPRESSOR, false, "Compressor"},
- {PROCESSOR_FUNCTION_ENVELOPE, true, "AR envelope"},
- {PROCESSOR_FUNCTION_VACTROL, true, "Plucked vactrol"},
- {PROCESSOR_FUNCTION_FOLLOWER, true, "Cutoff controller"},
- {PROCESSOR_FUNCTION_COMPRESSOR, true, "Slow compressor"},
- {PROCESSOR_FUNCTION_FILTER_CONTROLLER, true, "Direct VCF controller"},
- {PROCESSOR_FUNCTION_LORENZ_GENERATOR, false, "Lorenz generator"},
- };
-
- struct StreamsMonitorMode {
- MonitorMode mode;
- std::string label;
- };
-
- static constexpr int kNumMonitorModes = 4;
-
- static const StreamsMonitorMode kMonitorModeTable[kNumMonitorModes] = {
- {MONITOR_MODE_EXCITE_IN, "Excite"},
- {MONITOR_MODE_VCA_CV, "Level"},
- {MONITOR_MODE_AUDIO_IN, "In"},
- {MONITOR_MODE_OUTPUT, "Out"},
- };
-
- }
-
- struct Streams : Module {
- enum ParamIds {
- CH1_SHAPE_PARAM,
- CH1_MOD_PARAM,
- CH1_LEVEL_MOD_PARAM,
- CH1_RESPONSE_PARAM,
- CH2_SHAPE_PARAM,
- CH2_MOD_PARAM,
- CH2_LEVEL_MOD_PARAM,
- CH2_RESPONSE_PARAM,
- CH1_FUNCTION_BUTTON_PARAM,
- CH2_FUNCTION_BUTTON_PARAM,
- METERING_BUTTON_PARAM,
- NUM_PARAMS
- };
- enum InputIds {
- CH1_EXCITE_INPUT,
- CH1_SIGNAL_INPUT,
- CH1_LEVEL_INPUT,
- CH2_EXCITE_INPUT,
- CH2_SIGNAL_INPUT,
- CH2_LEVEL_INPUT,
- NUM_INPUTS
- };
- enum OutputIds {
- CH1_SIGNAL_OUTPUT,
- CH2_SIGNAL_OUTPUT,
- NUM_OUTPUTS
- };
- enum LightIds {
- CH1_LIGHT_1_G,
- CH1_LIGHT_1_R,
- CH1_LIGHT_2_G,
- CH1_LIGHT_2_R,
- CH1_LIGHT_3_G,
- CH1_LIGHT_3_R,
- CH1_LIGHT_4_G,
- CH1_LIGHT_4_R,
- CH2_LIGHT_1_G,
- CH2_LIGHT_1_R,
- CH2_LIGHT_2_G,
- CH2_LIGHT_2_R,
- CH2_LIGHT_3_G,
- CH2_LIGHT_3_R,
- CH2_LIGHT_4_G,
- CH2_LIGHT_4_R,
- NUM_LIGHTS
- };
-
- streams::StreamsEngine engines[PORT_MAX_CHANNELS];
- int prevNumChannels;
- float brightnesses[NUM_LIGHTS][PORT_MAX_CHANNELS];
-
- Streams() {
- config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
-
- configParam(CH1_SHAPE_PARAM, 0.f, 1.f, 0.5f, "Ch 1 shape");
- configParam(CH1_MOD_PARAM, 0.f, 1.f, 0.5f, "Ch 1 mod");
- configParam(CH1_LEVEL_MOD_PARAM, 0.f, 1.f, 0.5f, "Ch 1 level mod");
- configParam(CH2_SHAPE_PARAM, 0.f, 1.f, 0.5f, "Ch 2 shape");
- configParam(CH2_MOD_PARAM, 0.f, 1.f, 0.5f, "Ch 2 mod");
- configParam(CH2_LEVEL_MOD_PARAM, 0.f, 1.f, 0.5f, "Ch 2 level mod");
- configParam(CH1_RESPONSE_PARAM, 0.f, 1.f, 0.5f, "Ch 1 response");
- configParam(CH2_RESPONSE_PARAM, 0.f, 1.f, 0.5f, "Ch 2 response");
-
- configParam(CH1_FUNCTION_BUTTON_PARAM, 0.f, 1.f, 0.f, "Ch 1 function");
- configParam(CH2_FUNCTION_BUTTON_PARAM, 0.f, 1.f, 0.f, "Ch 2 function");
- configParam(METERING_BUTTON_PARAM, 0.f, 1.f, 0.f, "Meter");
-
- onReset();
- }
-
- void onReset() override {
- for (int c = 0; c < PORT_MAX_CHANNELS; c++) {
- engines[c].Reset();
- }
-
- prevNumChannels = 1;
- onSampleRateChange();
- }
-
- void onSampleRateChange() override {
- float sampleRate = APP->engine->getSampleRate();
-
- for (int c = 0; c < PORT_MAX_CHANNELS; c++) {
- engines[c].SetSampleRate(sampleRate);
- }
- }
-
- json_t* dataToJson() override {
- streams::UiSettings settings = engines[0].ui_settings();
- json_t* rootJ = json_object();
- json_object_set_new(rootJ, "function1", json_integer(settings.function[0]));
- json_object_set_new(rootJ, "function2", json_integer(settings.function[1]));
- json_object_set_new(rootJ, "alternate1", json_integer(settings.alternate[0]));
- json_object_set_new(rootJ, "alternate2", json_integer(settings.alternate[1]));
- json_object_set_new(rootJ, "monitorMode", json_integer(settings.monitor_mode));
- json_object_set_new(rootJ, "linked", json_integer(settings.linked));
- return rootJ;
- }
-
- void dataFromJson(json_t* rootJ) override {
- json_t* function1J = json_object_get(rootJ, "function1");
- json_t* function2J = json_object_get(rootJ, "function2");
- json_t* alternate1J = json_object_get(rootJ, "alternate1");
- json_t* alternate2J = json_object_get(rootJ, "alternate2");
- json_t* monitorModeJ = json_object_get(rootJ, "monitorMode");
- json_t* linkedJ = json_object_get(rootJ, "linked");
-
- streams::UiSettings settings = {};
-
- if (function1J)
- settings.function[0] = json_integer_value(function1J);
- if (function2J)
- settings.function[1] = json_integer_value(function2J);
- if (alternate1J)
- settings.alternate[0] = json_integer_value(alternate1J);
- if (alternate2J)
- settings.alternate[1] = json_integer_value(alternate2J);
- if (monitorModeJ)
- settings.monitor_mode = json_integer_value(monitorModeJ);
- if (linkedJ)
- settings.linked = json_integer_value(linkedJ);
-
- for (int c = 0; c < PORT_MAX_CHANNELS; c++) {
- engines[c].ApplySettings(settings);
- }
- }
-
- void onRandomize() override {
- for (int c = 0; c < PORT_MAX_CHANNELS; c++) {
- engines[c].Randomize();
- }
- }
-
- void toggleLink() {
- streams::UiSettings settings = engines[0].ui_settings();
- settings.linked ^= 1;
-
- for (int c = 0; c < PORT_MAX_CHANNELS; c++) {
- engines[c].ApplySettings(settings);
- }
- }
-
- void setChannelMode(int channel, int mode_id) {
- streams::UiSettings settings = engines[0].ui_settings();
- settings.function[channel] = streams::kChannelModeTable[mode_id].function;
- settings.alternate[channel] = streams::kChannelModeTable[mode_id].alternate;
-
- for (int c = 0; c < PORT_MAX_CHANNELS; c++) {
- engines[c].ApplySettings(settings);
- }
- }
-
- void setMonitorMode(int mode_id) {
- streams::UiSettings settings = engines[0].ui_settings();
- settings.monitor_mode = streams::kMonitorModeTable[mode_id].mode;
-
- for (int c = 0; c < PORT_MAX_CHANNELS; c++) {
- engines[c].ApplySettings(settings);
- }
- }
-
- int function(int channel) {
- return engines[0].ui_settings().function[channel];
- }
-
- int alternate(int channel) {
- return engines[0].ui_settings().alternate[channel];
- }
-
- bool linked() {
- return engines[0].ui_settings().linked;
- }
-
- int monitorMode() {
- return engines[0].ui_settings().monitor_mode;
- }
-
- void process(const ProcessArgs& args) override {
- int numChannels = std::max(inputs[CH1_SIGNAL_INPUT].getChannels(), inputs[CH2_SIGNAL_INPUT].getChannels());
- numChannels = std::max(numChannels, 1);
-
- if (numChannels > prevNumChannels) {
- for (int c = prevNumChannels; c < numChannels; c++) {
- engines[c].SyncUI(engines[0]);
- }
- }
-
- prevNumChannels = numChannels;
-
- // Reuse the same frame object for multiple engines because the params
- // aren't touched.
- streams::StreamsEngine::Frame frame;
-
- frame.ch1.shape_knob = params[CH1_SHAPE_PARAM] .getValue();
- frame.ch1.mod_knob = params[CH1_MOD_PARAM] .getValue();
- frame.ch1.level_mod_knob = params[CH1_LEVEL_MOD_PARAM].getValue();
- frame.ch1.response_knob = params[CH1_RESPONSE_PARAM] .getValue();
- frame.ch2.shape_knob = params[CH2_SHAPE_PARAM] .getValue();
- frame.ch2.mod_knob = params[CH2_MOD_PARAM] .getValue();
- frame.ch2.level_mod_knob = params[CH2_LEVEL_MOD_PARAM].getValue();
- frame.ch2.response_knob = params[CH2_RESPONSE_PARAM] .getValue();
-
- frame.ch1.signal_in_connected = inputs[CH1_SIGNAL_INPUT].isConnected();
- frame.ch1.level_cv_connected = inputs[CH1_LEVEL_INPUT] .isConnected();
- frame.ch2.signal_in_connected = inputs[CH2_SIGNAL_INPUT].isConnected();
- frame.ch2.level_cv_connected = inputs[CH2_LEVEL_INPUT] .isConnected();
-
- frame.ch1.function_button = params[CH1_FUNCTION_BUTTON_PARAM].getValue();
- frame.ch2.function_button = params[CH2_FUNCTION_BUTTON_PARAM].getValue();
- frame.metering_button = params[METERING_BUTTON_PARAM].getValue();
-
- bool lights_updated = false;
-
- for (int c = 0; c < numChannels; c++) {
- frame.ch1.excite_in = inputs[CH1_EXCITE_INPUT].getPolyVoltage(c);
- frame.ch1.signal_in = inputs[CH1_SIGNAL_INPUT].getPolyVoltage(c);
- frame.ch1.level_cv = inputs[CH1_LEVEL_INPUT] .getPolyVoltage(c);
- frame.ch2.excite_in = inputs[CH2_EXCITE_INPUT].getPolyVoltage(c);
- frame.ch2.signal_in = inputs[CH2_SIGNAL_INPUT].getPolyVoltage(c);
- frame.ch2.level_cv = inputs[CH2_LEVEL_INPUT] .getPolyVoltage(c);
-
- engines[c].Process(frame);
-
- outputs[CH1_SIGNAL_OUTPUT].setVoltage(frame.ch1.signal_out, c);
- outputs[CH2_SIGNAL_OUTPUT].setVoltage(frame.ch2.signal_out, c);
-
- if (frame.lights_updated) {
- brightnesses[CH1_LIGHT_1_G][c] = frame.ch1.led_green[0];
- brightnesses[CH1_LIGHT_2_G][c] = frame.ch1.led_green[1];
- brightnesses[CH1_LIGHT_3_G][c] = frame.ch1.led_green[2];
- brightnesses[CH1_LIGHT_4_G][c] = frame.ch1.led_green[3];
- brightnesses[CH1_LIGHT_1_R][c] = frame.ch1.led_red[0];
- brightnesses[CH1_LIGHT_2_R][c] = frame.ch1.led_red[1];
- brightnesses[CH1_LIGHT_3_R][c] = frame.ch1.led_red[2];
- brightnesses[CH1_LIGHT_4_R][c] = frame.ch1.led_red[3];
- brightnesses[CH2_LIGHT_1_G][c] = frame.ch2.led_green[0];
- brightnesses[CH2_LIGHT_2_G][c] = frame.ch2.led_green[1];
- brightnesses[CH2_LIGHT_3_G][c] = frame.ch2.led_green[2];
- brightnesses[CH2_LIGHT_4_G][c] = frame.ch2.led_green[3];
- brightnesses[CH2_LIGHT_1_R][c] = frame.ch2.led_red[0];
- brightnesses[CH2_LIGHT_2_R][c] = frame.ch2.led_red[1];
- brightnesses[CH2_LIGHT_3_R][c] = frame.ch2.led_red[2];
- brightnesses[CH2_LIGHT_4_R][c] = frame.ch2.led_red[3];
- }
-
- lights_updated |= frame.lights_updated;
- }
-
- outputs[CH1_SIGNAL_OUTPUT].setChannels(numChannels);
- outputs[CH2_SIGNAL_OUTPUT].setChannels(numChannels);
-
- if (lights_updated) {
- // Drive lights according to maximum brightness across engines
- for (int i = 0; i < NUM_LIGHTS; i++) {
- float brightness = 0.f;
-
- for (int c = 0; c < numChannels; c++) {
- brightness = std::max(brightnesses[i][c], brightness);
- }
-
- lights[i].setBrightness(brightness);
- }
- }
- }
- };
-
- struct StreamsWidget : ModuleWidget {
- StreamsWidget(Streams* module) {
- setModule(module);
- setPanel(Svg::load(asset::plugin(pluginInstance, "res/Streams.svg")));
-
- addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
- addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
- addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
- addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
-
- addParam(createParamCentered<Rogan1PSWhite>(mm2px(Vec(11.065, 128.75 - 107.695)), module, Streams::CH1_SHAPE_PARAM));
- addParam(createParamCentered<Rogan1PSWhite>(mm2px(Vec(11.065, 128.75 - 84.196)), module, Streams::CH1_MOD_PARAM));
- addParam(createParamCentered<Rogan1PSRed> (mm2px(Vec(11.065, 128.75 - 60.706)), module, Streams::CH1_LEVEL_MOD_PARAM));
- addParam(createParamCentered<Rogan1PSWhite>(mm2px(Vec(49.785, 128.75 - 107.695)), module, Streams::CH2_SHAPE_PARAM));
- addParam(createParamCentered<Rogan1PSWhite>(mm2px(Vec(49.785, 128.75 - 84.196)), module, Streams::CH2_MOD_PARAM));
- addParam(createParamCentered<Rogan1PSGreen>(mm2px(Vec(49.785, 128.75 - 60.706)), module, Streams::CH2_LEVEL_MOD_PARAM));
-
- addParam(createParamCentered<Trimpot>(mm2px(Vec(30.425, 128.75 - 68.006)), module, Streams::CH1_RESPONSE_PARAM));
- addParam(createParamCentered<Trimpot>(mm2px(Vec(30.425, 128.75 - 53.406)), module, Streams::CH2_RESPONSE_PARAM));
-
- addParam(createParamCentered<TL1105>(mm2px(Vec(24.715, 128.75 - 113.726)), module, Streams::CH1_FUNCTION_BUTTON_PARAM));
- addParam(createParamCentered<TL1105>(mm2px(Vec(36.135, 128.75 - 113.726)), module, Streams::CH2_FUNCTION_BUTTON_PARAM));
- addParam(createParamCentered<TL1105>(mm2px(Vec(30.425, 128.75 - 81.976)), module, Streams::METERING_BUTTON_PARAM));
-
- addInput(createInputCentered<PJ301MPort>(mm2px(Vec(8.506, 128.75 - 32.136)), module, Streams::CH1_EXCITE_INPUT));
- addInput(createInputCentered<PJ301MPort>(mm2px(Vec(23.116, 128.75 - 32.136)), module, Streams::CH1_SIGNAL_INPUT));
- addInput(createInputCentered<PJ301MPort>(mm2px(Vec(8.506, 128.75 - 17.526)), module, Streams::CH1_LEVEL_INPUT));
- addInput(createInputCentered<PJ301MPort>(mm2px(Vec(52.335, 128.75 - 32.136)), module, Streams::CH2_EXCITE_INPUT));
- addInput(createInputCentered<PJ301MPort>(mm2px(Vec(37.726, 128.75 - 32.136)), module, Streams::CH2_SIGNAL_INPUT));
- addInput(createInputCentered<PJ301MPort>(mm2px(Vec(52.335, 128.75 - 17.526)), module, Streams::CH2_LEVEL_INPUT));
-
- addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(23.116, 128.75 - 17.526)), module, Streams::CH1_SIGNAL_OUTPUT));
- addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(37.726, 128.75 - 17.526)), module, Streams::CH2_SIGNAL_OUTPUT));
-
- addChild(createLightCentered<MediumLight<GreenRedLight>>(mm2px(Vec(24.715, 128.75 - 106.746)), module, Streams::CH1_LIGHT_1_G));
- addChild(createLightCentered<MediumLight<GreenRedLight>>(mm2px(Vec(24.715, 128.75 - 101.026)), module, Streams::CH1_LIGHT_2_G));
- addChild(createLightCentered<MediumLight<GreenRedLight>>(mm2px(Vec(24.715, 128.75 - 95.305)), module, Streams::CH1_LIGHT_3_G));
- addChild(createLightCentered<MediumLight<GreenRedLight>>(mm2px(Vec(24.715, 128.75 - 89.585)), module, Streams::CH1_LIGHT_4_G));
- addChild(createLightCentered<MediumLight<GreenRedLight>>(mm2px(Vec(36.135, 128.75 - 106.746)), module, Streams::CH2_LIGHT_1_G));
- addChild(createLightCentered<MediumLight<GreenRedLight>>(mm2px(Vec(36.135, 128.75 - 101.026)), module, Streams::CH2_LIGHT_2_G));
- addChild(createLightCentered<MediumLight<GreenRedLight>>(mm2px(Vec(36.135, 128.75 - 95.305)), module, Streams::CH2_LIGHT_3_G));
- addChild(createLightCentered<MediumLight<GreenRedLight>>(mm2px(Vec(36.135, 128.75 - 89.585)), module, Streams::CH2_LIGHT_4_G));
- }
-
- void appendContextMenu(Menu* menu) override {
- Streams* module = dynamic_cast<Streams*>(this->module);
-
- struct LinkItem : MenuItem {
- Streams* module;
- void onAction(const event::Action& e) override {
- module->toggleLink();
- }
- };
-
- struct ChannelModeItem : MenuItem {
- Streams* module;
- int channel;
- int mode;
- void onAction(const event::Action& e) override {
- module->setChannelMode(channel, mode);
- }
- };
-
- struct MonitorModeItem : MenuItem {
- Streams* module;
- int mode;
- void onAction(const event::Action& e) override {
- module->setMonitorMode(mode);
- }
- };
-
- menu->addChild(new MenuSeparator);
- LinkItem* linkItem = createMenuItem<LinkItem>(
- "Link channels", CHECKMARK(module->linked()));
- linkItem->module = module;
- menu->addChild(linkItem);
-
- menu->addChild(new MenuSeparator);
- menu->addChild(createMenuLabel("Channel 1"));
- for (int i = 0; i < streams::kNumChannelModes; i++) {
- auto modeItem = createMenuItem<ChannelModeItem>(
- streams::kChannelModeTable[i].label, CHECKMARK(
- module->function(0) == streams::kChannelModeTable[i].function &&
- module->alternate(0) == streams::kChannelModeTable[i].alternate));
- modeItem->module = module;
- modeItem->channel = 0;
- modeItem->mode = i;
- menu->addChild(modeItem);
- }
-
- menu->addChild(new MenuSeparator);
- menu->addChild(createMenuLabel("Channel 2"));
- for (int i = 0; i < streams::kNumChannelModes; i++) {
- auto modeItem = createMenuItem<ChannelModeItem>(
- streams::kChannelModeTable[i].label, CHECKMARK(
- module->function(1) == streams::kChannelModeTable[i].function &&
- module->alternate(1) == streams::kChannelModeTable[i].alternate));
- modeItem->module = module;
- modeItem->channel = 1;
- modeItem->mode = i;
- menu->addChild(modeItem);
- }
-
- menu->addChild(new MenuSeparator);
- menu->addChild(createMenuLabel("Meter"));
- for (int i = 0; i < streams::kNumMonitorModes; i++) {
- auto modeItem = createMenuItem<MonitorModeItem>(
- streams::kMonitorModeTable[i].label, CHECKMARK(
- module->monitorMode() == streams::kMonitorModeTable[i].mode));
- modeItem->module = module;
- modeItem->mode = i;
- menu->addChild(modeItem);
- }
- }
- };
-
- Model* modelStreams = createModel<Streams, StreamsWidget>("Streams");
|