diff --git a/Core.json b/Core.json index 21f44ab3..c4185e13 100644 --- a/Core.json +++ b/Core.json @@ -13,6 +13,14 @@ "donateUrl": "", "description": "Necessary modules built into VCV Rack", "modules": [ + { + "slug": "AudioInterface2", + "name": "Audio-2", + "description": "Sends audio and CV to/from an audio device", + "tags": [ + "External" + ] + }, { "slug": "AudioInterface", "name": "Audio-8", diff --git a/include/app/AudioWidget.hpp b/include/app/AudioWidget.hpp index d36d4744..5c77add8 100644 --- a/include/app/AudioWidget.hpp +++ b/include/app/AudioWidget.hpp @@ -9,6 +9,7 @@ namespace rack { namespace app { +/** Designed for Audio-8 and Audio-16 module. */ struct AudioWidget : LedDisplay { LedDisplayChoice* driverChoice; LedDisplaySeparator* driverSeparator; @@ -21,6 +22,13 @@ struct AudioWidget : LedDisplay { }; +/** Designed for Audio-2 module. */ +struct AudioDeviceWidget : LedDisplay { + LedDisplayChoice* deviceChoice; + void setAudioPort(audio::Port* port); +}; + + /** Appends menu items to the given menu with driver, device, etc. Useful alternative to putting an AudioWidget on your module's panel. */ diff --git a/include/componentlibrary.hpp b/include/componentlibrary.hpp index 2850804e..7813fd0a 100644 --- a/include/componentlibrary.hpp +++ b/include/componentlibrary.hpp @@ -236,6 +236,12 @@ struct RoundLargeBlackKnob : RoundKnob { } }; +struct RoundBigBlackKnob : RoundKnob { + RoundBigBlackKnob() { + setSvg(APP->window->loadSvg(asset::system("res/ComponentLibrary/RoundBigBlackKnob.svg"))); + } +}; + struct RoundHugeBlackKnob : RoundKnob { RoundHugeBlackKnob() { setSvg(APP->window->loadSvg(asset::system("res/ComponentLibrary/RoundHugeBlackKnob.svg"))); diff --git a/res/ComponentLibrary/RoundBigBlackKnob.svg b/res/ComponentLibrary/RoundBigBlackKnob.svg new file mode 100644 index 00000000..881b9749 --- /dev/null +++ b/res/ComponentLibrary/RoundBigBlackKnob.svg @@ -0,0 +1,71 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/res/Core/AudioInterface2.svg b/res/Core/AudioInterface2.svg new file mode 100644 index 00000000..d322e982 --- /dev/null +++ b/res/Core/AudioInterface2.svg @@ -0,0 +1,451 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/AudioWidget.cpp b/src/app/AudioWidget.cpp index dd46d8e7..166adb16 100644 --- a/src/app/AudioWidget.cpp +++ b/src/app/AudioWidget.cpp @@ -304,6 +304,16 @@ void AudioWidget::setAudioPort(audio::Port* port) { } +void AudioDeviceWidget::setAudioPort(audio::Port* port) { + AudioDeviceChoice* deviceChoice = createWidget(math::Vec()); + deviceChoice->box.size.x = box.size.x; + deviceChoice->box.size.y = box.size.y; + deviceChoice->port = port; + addChild(deviceChoice); + this->deviceChoice = deviceChoice; +} + + void appendAudioMenu(ui::Menu* menu, audio::Port* port) { menu->addChild(createMenuLabel("Audio driver")); appendAudioDriverMenu(menu, port); diff --git a/src/core/AudioInterface.cpp b/src/core/AudioInterface.cpp index 35d63338..3961239e 100644 --- a/src/core/AudioInterface.cpp +++ b/src/core/AudioInterface.cpp @@ -14,7 +14,11 @@ namespace core { template struct AudioInterface : Module, audio::Port { + static constexpr int NUM_INPUT_LIGHTS = (NUM_AUDIO_INPUTS > 2) ? (NUM_AUDIO_INPUTS / 2) : 0; + static constexpr int NUM_OUTPUT_LIGHTS = (NUM_AUDIO_OUTPUTS > 2) ? (NUM_AUDIO_OUTPUTS / 2) : 0; + enum ParamIds { + ENUMS(GAIN_PARAM, NUM_AUDIO_INPUTS == 2), NUM_PARAMS }; enum InputIds { @@ -26,8 +30,9 @@ struct AudioInterface : Module, audio::Port { NUM_OUTPUTS }; enum LightIds { - ENUMS(INPUT_LIGHTS, NUM_AUDIO_INPUTS / 2 * 2), - ENUMS(OUTPUT_LIGHTS, NUM_AUDIO_OUTPUTS / 2 * 2), + ENUMS(INPUT_LIGHTS, NUM_INPUT_LIGHTS * 2), + ENUMS(OUTPUT_LIGHTS, NUM_OUTPUT_LIGHTS * 2), + ENUMS(VU_LIGHTS, (NUM_AUDIO_INPUTS == 2) ? (2 * 6) : 0), NUM_LIGHTS }; @@ -39,8 +44,9 @@ struct AudioInterface : Module, audio::Port { dsp::ClockDivider lightDivider; // For each pair of inputs/outputs - float inputClipTimers[NUM_AUDIO_INPUTS / 2] = {}; - float outputClipTimers[NUM_AUDIO_OUTPUTS / 2] = {}; + float inputClipTimers[(NUM_AUDIO_INPUTS > 0) ? NUM_INPUT_LIGHTS : 0] = {}; + float outputClipTimers[(NUM_AUDIO_INPUTS > 0) ? NUM_OUTPUT_LIGHTS : 0] = {}; + dsp::VuMeter2 vuMeter[(NUM_AUDIO_INPUTS == 2) ? 2 : 0]; // Port variables int requestedEngineFrames = 0; @@ -48,6 +54,8 @@ struct AudioInterface : Module, audio::Port { AudioInterface() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); + if (NUM_AUDIO_INPUTS == 2) + configParam(GAIN_PARAM, 0.f, 2.f, 1.f, "Level", " dB", -10, 20); for (int i = 0; i < NUM_AUDIO_INPUTS; i++) configInput(AUDIO_INPUTS + i, string::f("To device %d", i + 1)); for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) @@ -77,14 +85,33 @@ struct AudioInterface : Module, audio::Port { const float clipTime = 0.25f; // Push inputs to buffer - if (!inputBuffer.full()) { - dsp::Frame inputFrame; - for (int i = 0; i < NUM_AUDIO_INPUTS; i++) { - float v = inputs[AUDIO_INPUTS + i].getVoltageSum() / 10.f; - inputFrame.samples[i] = v; + dsp::Frame inputFrame; + for (int i = 0; i < NUM_AUDIO_INPUTS; i++) { + // Get input + float v = 0.f; + if (inputs[AUDIO_INPUTS + i].isConnected()) + v = inputs[AUDIO_INPUTS + i].getVoltageSum() / 10.f; + // Normalize right input to left on Audio-2 + else if (i > 0 && NUM_AUDIO_INPUTS == 2) + v = inputFrame.samples[i - 1]; + + // Detect clipping + if (NUM_AUDIO_INPUTS > 2) { if (std::fabs(v) >= 1.f) inputClipTimers[i / 2] = clipTime; } + inputFrame.samples[i] = v; + } + + // Apply gain from knob + if (NUM_AUDIO_INPUTS == 2) { + float gain = params[GAIN_PARAM].getValue(); + for (int i = 0; i < NUM_AUDIO_INPUTS; i++) { + inputFrame.samples[i] *= gain; + } + } + + if (!inputBuffer.full()) { inputBuffer.push(inputFrame); } @@ -94,8 +121,12 @@ struct AudioInterface : Module, audio::Port { for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) { float v = outputFrame.samples[i]; outputs[AUDIO_OUTPUTS + i].setVoltage(10.f * v); - if (std::fabs(v) >= 1.f) - outputClipTimers[i / 2] = clipTime; + + // Detect clipping + if (NUM_AUDIO_OUTPUTS > 2) { + if (std::fabs(v) >= 1.f) + outputClipTimers[i / 2] = clipTime; + } } } else { @@ -104,26 +135,44 @@ struct AudioInterface : Module, audio::Port { } } + // Lights + if (NUM_AUDIO_INPUTS == 2) { + for (int i = 0; i < 2; i++) { + vuMeter[i].process(args.sampleTime, inputFrame.samples[i]); + } + } if (lightDivider.process()) { float lightTime = args.sampleTime * lightDivider.getDivision(); - int numDeviceInputs = getNumInputs(); - int numDeviceOutputs = getNumOutputs(); - // Turn on light if at least one port is enabled in the nearby pair. - for (int i = 0; i < NUM_AUDIO_INPUTS / 2; i++) { - bool active = numDeviceOutputs >= 2 * i + 1; - bool clip = inputClipTimers[i] > 0.f; - if (clip) - inputClipTimers[i] -= lightTime; - lights[INPUT_LIGHTS + i * 2 + 0].setBrightness(active && !clip); - lights[INPUT_LIGHTS + i * 2 + 1].setBrightness(active && clip); + if (NUM_AUDIO_INPUTS == 2) { + for (int i = 0; i < 2; i++) { + lights[VU_LIGHTS + i * 6 + 0].setBrightness(vuMeter[i].getBrightness(0, 0)); + lights[VU_LIGHTS + i * 6 + 1].setBrightness(vuMeter[i].getBrightness(-3, 0)); + lights[VU_LIGHTS + i * 6 + 2].setBrightness(vuMeter[i].getBrightness(-6, -3)); + lights[VU_LIGHTS + i * 6 + 3].setBrightness(vuMeter[i].getBrightness(-12, -6)); + lights[VU_LIGHTS + i * 6 + 4].setBrightness(vuMeter[i].getBrightness(-24, -12)); + lights[VU_LIGHTS + i * 6 + 5].setBrightness(vuMeter[i].getBrightness(-36, -24)); + } } - for (int i = 0; i < NUM_AUDIO_OUTPUTS / 2; i++) { - bool active = numDeviceInputs >= 2 * i + 1; - bool clip = outputClipTimers[i] > 0.f; - if (clip) - outputClipTimers[i] -= lightTime; - lights[OUTPUT_LIGHTS + i * 2 + 0].setBrightness(active & !clip); - lights[OUTPUT_LIGHTS + i * 2 + 1].setBrightness(active & clip); + else { + int numDeviceInputs = getNumInputs(); + int numDeviceOutputs = getNumOutputs(); + // Turn on light if at least one port is enabled in the nearby pair. + for (int i = 0; i < NUM_AUDIO_INPUTS / 2; i++) { + bool active = numDeviceOutputs >= 2 * i + 1; + bool clip = inputClipTimers[i] > 0.f; + if (clip) + inputClipTimers[i] -= lightTime; + lights[INPUT_LIGHTS + i * 2 + 0].setBrightness(active && !clip); + lights[INPUT_LIGHTS + i * 2 + 1].setBrightness(active && clip); + } + for (int i = 0; i < NUM_AUDIO_OUTPUTS / 2; i++) { + bool active = numDeviceInputs >= 2 * i + 1; + bool clip = outputClipTimers[i] > 0.f; + if (clip) + outputClipTimers[i] -= lightTime; + lights[OUTPUT_LIGHTS + i * 2 + 0].setBrightness(active & !clip); + lights[OUTPUT_LIGHTS + i * 2 + 1].setBrightness(active & clip); + } } } } @@ -362,6 +411,42 @@ struct AudioInterfaceWidget : ModuleWidget { audioWidget->setAudioPort(module); addChild(audioWidget); } + else if (NUM_AUDIO_INPUTS == 2 && NUM_AUDIO_OUTPUTS == 2) { + setPanel(APP->window->loadSvg(asset::system("res/Core/AudioInterface2.svg"))); + + addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); + addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); + addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + + addParam(createParamCentered(mm2px(Vec(12.7, 74.019)), module, TAudioInterface::GAIN_PARAM)); + + addInput(createInputCentered(mm2px(Vec(6.697, 94.253)), module, TAudioInterface::AUDIO_INPUTS + 0)); + addInput(createInputCentered(mm2px(Vec(18.703, 94.253)), module, TAudioInterface::AUDIO_INPUTS + 1)); + + addOutput(createOutputCentered(mm2px(Vec(6.699, 112.254)), module, TAudioInterface::AUDIO_OUTPUTS + 0)); + addOutput(createOutputCentered(mm2px(Vec(18.7, 112.254)), module, TAudioInterface::AUDIO_OUTPUTS + 1)); + + addChild(createLightCentered>(mm2px(Vec(6.7, 29.759)), module, TAudioInterface::VU_LIGHTS + 0 * 6 + 0)); + addChild(createLightCentered>(mm2px(Vec(18.7, 29.759)), module, TAudioInterface::VU_LIGHTS + 1 * 6 + 0)); + addChild(createLightCentered>(mm2px(Vec(6.7, 34.753)), module, TAudioInterface::VU_LIGHTS + 0 * 6 + 1)); + addChild(createLightCentered>(mm2px(Vec(18.7, 34.753)), module, TAudioInterface::VU_LIGHTS + 1 * 6 + 1)); + addChild(createLightCentered>(mm2px(Vec(6.7, 39.749)), module, TAudioInterface::VU_LIGHTS + 0 * 6 + 2)); + addChild(createLightCentered>(mm2px(Vec(18.7, 39.749)), module, TAudioInterface::VU_LIGHTS + 1 * 6 + 2)); + addChild(createLightCentered>(mm2px(Vec(6.7, 44.744)), module, TAudioInterface::VU_LIGHTS + 0 * 6 + 3)); + addChild(createLightCentered>(mm2px(Vec(18.7, 44.744)), module, TAudioInterface::VU_LIGHTS + 1 * 6 + 3)); + addChild(createLightCentered>(mm2px(Vec(6.7, 49.744)), module, TAudioInterface::VU_LIGHTS + 0 * 6 + 4)); + addChild(createLightCentered>(mm2px(Vec(18.7, 49.744)), module, TAudioInterface::VU_LIGHTS + 1 * 6 + 4)); + addChild(createLightCentered>(mm2px(Vec(6.7, 54.745)), module, TAudioInterface::VU_LIGHTS + 0 * 6 + 5)); + addChild(createLightCentered>(mm2px(Vec(18.7, 54.745)), module, TAudioInterface::VU_LIGHTS + 1 * 6 + 5)); + + AudioDeviceWidget* audioWidget = createWidget(mm2px(Vec(2.135, 14.259))); + audioWidget->box.size = mm2px(Vec(21.128, 6.725)); + audioWidget->setAudioPort(module); + // Adjust deviceChoice position + audioWidget->deviceChoice->box.pos = Vec(-4, -4); + addChild(audioWidget); + } } void appendContextMenu(Menu* menu) override { @@ -378,6 +463,8 @@ struct AudioInterfaceWidget : ModuleWidget { }; +Model* modelAudioInterface2 = createModel, AudioInterfaceWidget<2, 2>>("AudioInterface2"); +// Legacy name for Audio-8 Model* modelAudioInterface = createModel, AudioInterfaceWidget<8, 8>>("AudioInterface"); Model* modelAudioInterface16 = createModel, AudioInterfaceWidget<16, 16>>("AudioInterface16"); diff --git a/src/core/plugin.cpp b/src/core/plugin.cpp index 349dd3aa..1112ee99 100644 --- a/src/core/plugin.cpp +++ b/src/core/plugin.cpp @@ -6,6 +6,7 @@ namespace core { void init(rack::Plugin* p) { + p->addModel(modelAudioInterface2); p->addModel(modelAudioInterface); p->addModel(modelAudioInterface16); p->addModel(modelMIDI_CV); diff --git a/src/core/plugin.hpp b/src/core/plugin.hpp index 56d78316..7a447c09 100644 --- a/src/core/plugin.hpp +++ b/src/core/plugin.hpp @@ -5,6 +5,7 @@ namespace rack { namespace core { +extern Model* modelAudioInterface2; extern Model* modelAudioInterface; extern Model* modelAudioInterface16; extern Model* modelMIDI_CV;