#include #include #include #include #include #include #include "core.hpp" #include "audio.hpp" #include "dsp/samplerate.hpp" #include "dsp/ringbuffer.hpp" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsuggest-override" #include #pragma GCC diagnostic pop #define MAX_OUTPUTS 8 #define MAX_INPUTS 8 static auto audioTimeout = std::chrono::milliseconds(100); using namespace rack; struct AudioInterfaceIO : AudioIO { std::mutex engineMutex; std::condition_variable engineCv; std::mutex audioMutex; std::condition_variable audioCv; // Audio thread produces, engine thread consumes DoubleRingBuffer, (1<<15)> inputBuffer; // Audio thread consumes, engine thread produces DoubleRingBuffer, (1<<15)> outputBuffer; AudioInterfaceIO() { maxOutputs = MAX_OUTPUTS; maxInputs = MAX_INPUTS; } void processStream(const float *input, float *output, int length) override { if (numInputs > 0) { // TODO Do we need to wait on the input to be consumed here? for (int i = 0; i < length; i++) { if (inputBuffer.full()) break; Frame f; memset(&f, 0, sizeof(f)); memcpy(&f, &input[numInputs * i], numInputs * sizeof(float)); inputBuffer.push(f); } } if (numOutputs > 0) { std::unique_lock lock(audioMutex); auto cond = [&] { return outputBuffer.size() >= length; }; if (audioCv.wait_for(lock, audioTimeout, cond)) { // Consume audio block for (int i = 0; i < length; i++) { Frame f = outputBuffer.shift(); memcpy(&output[numOutputs * i], &f, numOutputs * sizeof(float)); } } else { // Timed out, fill output with zeros memset(output, 0, length * numOutputs * sizeof(float)); } } // Notify engine when finished processing engineCv.notify_all(); } void onCloseStream() override { inputBuffer.clear(); outputBuffer.clear(); } }; struct AudioInterface : Module { enum ParamIds { NUM_PARAMS }; enum InputIds { ENUMS(AUDIO_INPUT, MAX_INPUTS), NUM_INPUTS }; enum OutputIds { ENUMS(AUDIO_OUTPUT, MAX_OUTPUTS), NUM_OUTPUTS }; AudioInterfaceIO audioIO; SampleRateConverter inputSrc; SampleRateConverter outputSrc; // in rack's sample rate DoubleRingBuffer, 16> inputBuffer; DoubleRingBuffer, 16> outputBuffer; AudioInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { } void step() override; json_t *toJson() override { json_t *rootJ = json_object(); json_object_set_new(rootJ, "driver", json_integer(audioIO.getDriver())); std::string deviceName = audioIO.getDeviceName(audioIO.device); json_object_set_new(rootJ, "deviceName", json_string(deviceName.c_str())); json_object_set_new(rootJ, "sampleRate", json_integer(audioIO.sampleRate)); json_object_set_new(rootJ, "blockSize", json_integer(audioIO.blockSize)); return rootJ; } void fromJson(json_t *rootJ) override { json_t *driverJ = json_object_get(rootJ, "driver"); if (driverJ) audioIO.setDriver(json_number_value(driverJ)); json_t *deviceNameJ = json_object_get(rootJ, "deviceName"); if (deviceNameJ) { std::string deviceName = json_string_value(deviceNameJ); // Search for device ID with equal name for (int device = 0; device < audioIO.getDeviceCount(); device++) { if (audioIO.getDeviceName(device) == deviceName) { audioIO.device = device; break; } } } json_t *sampleRateJ = json_object_get(rootJ, "sampleRate"); if (sampleRateJ) audioIO.sampleRate = json_integer_value(sampleRateJ); json_t *blockSizeJ = json_object_get(rootJ, "blockSize"); if (blockSizeJ) audioIO.blockSize = json_integer_value(blockSizeJ); audioIO.openStream(); } void onReset() override { audioIO.closeStream(); } }; void AudioInterface::step() { Frame inputFrame; memset(&inputFrame, 0, sizeof(inputFrame)); if (audioIO.numInputs > 0) { if (inputBuffer.empty()) { inputSrc.setRates(audioIO.sampleRate, engineGetSampleRate()); int inLen = audioIO.inputBuffer.size(); int outLen = inputBuffer.capacity(); inputSrc.process(audioIO.inputBuffer.startData(), &inLen, inputBuffer.endData(), &outLen); audioIO.inputBuffer.startIncr(inLen); inputBuffer.endIncr(outLen); } } if (!inputBuffer.empty()) { inputFrame = inputBuffer.shift(); } for (int i = 0; i < MAX_INPUTS; i++) { outputs[AUDIO_OUTPUT + i].value = 10.0 * inputFrame.samples[i]; } if (audioIO.numOutputs > 0) { // Get and push output SRC frame if (!outputBuffer.full()) { Frame f; for (int i = 0; i < audioIO.numOutputs; i++) { f.samples[i] = inputs[AUDIO_INPUT + i].value / 10.0; } outputBuffer.push(f); } if (outputBuffer.full()) { // Wait until outputs are needed std::unique_lock lock(audioIO.engineMutex); auto cond = [&] { return audioIO.outputBuffer.size() < audioIO.blockSize; }; if (audioIO.engineCv.wait_for(lock, audioTimeout, cond)) { // Push converted output outputSrc.setRates(engineGetSampleRate(), audioIO.sampleRate); int inLen = outputBuffer.size(); int outLen = audioIO.outputBuffer.capacity(); outputSrc.process(outputBuffer.startData(), &inLen, audioIO.outputBuffer.endData(), &outLen); outputBuffer.startIncr(inLen); audioIO.outputBuffer.endIncr(outLen); } else { // Give up on pushing output } } } audioIO.audioCv.notify_all(); } AudioInterfaceWidget::AudioInterfaceWidget() { AudioInterface *module = new AudioInterface(); setModule(module); box.size = Vec(15*12, 380); { Panel *panel = new LightPanel(); panel->box.size = box.size; addChild(panel); } // addChild(createScrew(Vec(15, 0))); // addChild(createScrew(Vec(box.size.x-30, 0))); // addChild(createScrew(Vec(15, 365))); // addChild(createScrew(Vec(box.size.x-30, 365))); Vec margin = Vec(5, 2); float labelHeight = 15; float yPos = margin.y + 100; float xPos; { Label *label = new Label(); label->box.pos = Vec(margin.x, yPos); label->text = "Outputs"; addChild(label); yPos += labelHeight + margin.y; } yPos += 5; xPos = 10; for (int i = 0; i < 4; i++) { addInput(createInput(Vec(xPos, yPos), module, AudioInterface::AUDIO_INPUT + i)); Label *label = new Label(); label->box.pos = Vec(xPos + 4, yPos + 28); label->text = stringf("%d", i + 1); addChild(label); xPos += 37 + margin.x; } yPos += 35 + margin.y; yPos += 5; xPos = 10; for (int i = 4; i < 8; i++) { addInput(createInput(Vec(xPos, yPos), module, AudioInterface::AUDIO_INPUT + i)); Label *label = new Label(); label->box.pos = Vec(xPos + 4, yPos + 28); label->text = stringf("%d", i + 1); addChild(label); xPos += 37 + margin.x; } yPos += 35 + margin.y; { Label *label = new Label(); label->box.pos = Vec(margin.x, yPos); label->text = "Inputs"; addChild(label); yPos += labelHeight + margin.y; } yPos += 5; xPos = 10; for (int i = 0; i < 4; i++) { addOutput(createOutput(Vec(xPos, yPos), module, AudioInterface::AUDIO_OUTPUT + i)); Label *label = new Label(); label->box.pos = Vec(xPos + 4, yPos + 28); label->text = stringf("%d", i + 1); addChild(label); xPos += 37 + margin.x; } yPos += 35 + margin.y; yPos += 5; xPos = 10; for (int i = 4; i < 8; i++) { addOutput(createOutput(Vec(xPos, yPos), module, AudioInterface::AUDIO_OUTPUT + i)); Label *label = new Label(); label->box.pos = Vec(xPos + 4, yPos + 28); label->text = stringf("%d", i + 1); addChild(label); xPos += 37 + margin.x; } yPos += 35 + margin.y; AudioWidget *audioWidget = construct(); audioWidget->audioIO = &module->audioIO; addChild(audioWidget); }