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 @@
+
+
+
+
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 @@
+
+
+
+
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;