#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 OUTPUTS 8 #define INPUTS 8 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() { // Close stream here before destructing AudioInterfaceIO, so the mutexes are still valid when waiting to close. setDevice(-1, 0); } void processStream(const float *input, float *output, int frames) override { if (numInputs > 0) { // TODO Do we need to wait on the input to be consumed here? Experimentally, it works fine if we don't. for (int i = 0; i < frames; 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() >= (size_t) frames); }; auto timeout = std::chrono::milliseconds(100); if (audioCv.wait_for(lock, timeout, cond)) { // Consume audio block for (int i = 0; i < frames; i++) { Frame f = outputBuffer.shift(); memcpy(&output[numOutputs * i], &f, numOutputs * sizeof(float)); } } else { // Timed out, fill output with zeros memset(output, 0, frames * numOutputs * sizeof(float)); debug("Audio Interface IO underflow"); } } // 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, INPUTS), NUM_INPUTS }; enum OutputIds { ENUMS(AUDIO_OUTPUT, OUTPUTS), NUM_OUTPUTS }; enum LightIds { ENUMS(INPUT_LIGHT, INPUTS / 2), ENUMS(OUTPUT_LIGHT, OUTPUTS / 2), NUM_LIGHTS }; AudioInterfaceIO audioIO; int lastSampleRate = 0; SampleRateConverter inputSrc; SampleRateConverter outputSrc; // in rack's sample rate DoubleRingBuffer, 16> inputBuffer; DoubleRingBuffer, 16> outputBuffer; AudioInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { onSampleRateChange(); } void step() override; json_t *toJson() override { json_t *rootJ = json_object(); json_object_set_new(rootJ, "audio", audioIO.toJson()); return rootJ; } void fromJson(json_t *rootJ) override { json_t *audioJ = json_object_get(rootJ, "audio"); audioIO.fromJson(audioJ); } void onSampleRateChange() override { // for (int i = 0; i < INPUTS; i++) { // inputSrc[i].setRates(audioIO.sampleRate, engineGetSampleRate()); // } // for (int i = 0; i < OUTPUTS; i++) { // outputSrc[i].setRates(engineGetSampleRate(), audioIO.sampleRate); // } inputSrc.setRates(audioIO.sampleRate, engineGetSampleRate()); outputSrc.setRates(engineGetSampleRate(), audioIO.sampleRate); } void onReset() override { audioIO.setDevice(-1, 0); } }; void AudioInterface::step() { Frame inputFrame; memset(&inputFrame, 0, sizeof(inputFrame)); // Update sample rate if changed by audio driver if (audioIO.sampleRate != lastSampleRate) { onSampleRateChange(); lastSampleRate = audioIO.sampleRate; } if (audioIO.numInputs > 0) { if (inputBuffer.empty()) { 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 < 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() < (size_t) audioIO.blockSize); }; auto timeout = std::chrono::milliseconds(100); if (audioIO.engineCv.wait_for(lock, timeout, cond)) { // Push converted output 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 debug("Audio Interface underflow"); } } } for (int i = 0; i < INPUTS / 2; i++) lights[INPUT_LIGHT + i].value = (audioIO.numOutputs >= 2*i+1); for (int i = 0; i < OUTPUTS / 2; i++) lights[OUTPUT_LIGHT + i].value = (audioIO.numInputs >= 2*i+1); audioIO.audioCv.notify_all(); } struct AudioInterfaceWidget : ModuleWidget { AudioInterfaceWidget(AudioInterface *module) : ModuleWidget(module) { setPanel(SVG::load(assetGlobal("res/Core/AudioInterface.svg"))); addChild(Widget::create(Vec(RACK_GRID_WIDTH, 0))); addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); addInput(Port::create(mm2px(Vec(3.7069211, 55.530807)), Port::INPUT, module, AudioInterface::AUDIO_INPUT + 0)); addInput(Port::create(mm2px(Vec(15.307249, 55.530807)), Port::INPUT, module, AudioInterface::AUDIO_INPUT + 1)); addInput(Port::create(mm2px(Vec(26.906193, 55.530807)), Port::INPUT, module, AudioInterface::AUDIO_INPUT + 2)); addInput(Port::create(mm2px(Vec(38.506519, 55.530807)), Port::INPUT, module, AudioInterface::AUDIO_INPUT + 3)); addInput(Port::create(mm2px(Vec(3.7069209, 70.144905)), Port::INPUT, module, AudioInterface::AUDIO_INPUT + 4)); addInput(Port::create(mm2px(Vec(15.307249, 70.144905)), Port::INPUT, module, AudioInterface::AUDIO_INPUT + 5)); addInput(Port::create(mm2px(Vec(26.906193, 70.144905)), Port::INPUT, module, AudioInterface::AUDIO_INPUT + 6)); addInput(Port::create(mm2px(Vec(38.506519, 70.144905)), Port::INPUT, module, AudioInterface::AUDIO_INPUT + 7)); addOutput(Port::create(mm2px(Vec(3.7069209, 92.143906)), Port::OUTPUT, module, AudioInterface::AUDIO_OUTPUT + 0)); addOutput(Port::create(mm2px(Vec(15.307249, 92.143906)), Port::OUTPUT, module, AudioInterface::AUDIO_OUTPUT + 1)); addOutput(Port::create(mm2px(Vec(26.906193, 92.143906)), Port::OUTPUT, module, AudioInterface::AUDIO_OUTPUT + 2)); addOutput(Port::create(mm2px(Vec(38.506519, 92.143906)), Port::OUTPUT, module, AudioInterface::AUDIO_OUTPUT + 3)); addOutput(Port::create(mm2px(Vec(3.7069209, 108.1443)), Port::OUTPUT, module, AudioInterface::AUDIO_OUTPUT + 4)); addOutput(Port::create(mm2px(Vec(15.307249, 108.1443)), Port::OUTPUT, module, AudioInterface::AUDIO_OUTPUT + 5)); addOutput(Port::create(mm2px(Vec(26.906193, 108.1443)), Port::OUTPUT, module, AudioInterface::AUDIO_OUTPUT + 6)); addOutput(Port::create(mm2px(Vec(38.506523, 108.1443)), Port::OUTPUT, module, AudioInterface::AUDIO_OUTPUT + 7)); addChild(ModuleLightWidget::create>(mm2px(Vec(12.524985, 54.577202)), module, AudioInterface::INPUT_LIGHT + 0)); addChild(ModuleLightWidget::create>(mm2px(Vec(35.725647, 54.577202)), module, AudioInterface::INPUT_LIGHT + 1)); addChild(ModuleLightWidget::create>(mm2px(Vec(12.524985, 69.158226)), module, AudioInterface::INPUT_LIGHT + 2)); addChild(ModuleLightWidget::create>(mm2px(Vec(35.725647, 69.158226)), module, AudioInterface::INPUT_LIGHT + 3)); addChild(ModuleLightWidget::create>(mm2px(Vec(12.524985, 91.147583)), module, AudioInterface::OUTPUT_LIGHT + 0)); addChild(ModuleLightWidget::create>(mm2px(Vec(35.725647, 91.147583)), module, AudioInterface::OUTPUT_LIGHT + 1)); addChild(ModuleLightWidget::create>(mm2px(Vec(12.524985, 107.17003)), module, AudioInterface::OUTPUT_LIGHT + 2)); addChild(ModuleLightWidget::create>(mm2px(Vec(35.725647, 107.17003)), module, AudioInterface::OUTPUT_LIGHT + 3)); AudioWidget *audioWidget = Widget::create(mm2px(Vec(3.2122073, 14.837339))); audioWidget->box.size = mm2px(Vec(44, 28)); audioWidget->audioIO = &module->audioIO; addChild(audioWidget); } }; Model *modelAudioInterface = Model::create("Core", "AudioInterface", "Audio", EXTERNAL_TAG);