diff --git a/src/core/AudioInterface.cpp b/src/core/AudioInterface.cpp index cf7e1c68..1481c8df 100644 --- a/src/core/AudioInterface.cpp +++ b/src/core/AudioInterface.cpp @@ -13,100 +13,7 @@ namespace core { template -struct AudioInterfacePort : audio::Port { - // std::mutex engineMutex; - // std::condition_variable engineCv; - // std::mutex audioMutex; - // std::condition_variable audioCv; - // // Audio thread produces, engine thread consumes - // dsp::DoubleRingBuffer < dsp::Frame, (1 << 15) > inputBuffer; - // // Audio thread consumes, engine thread produces - // dsp::DoubleRingBuffer < dsp::Frame, (1 << 15) > outputBuffer; - // bool active = false; - - // For checking getPrimaryModule() - Module* module = NULL; - const float* input = NULL; - float* output = NULL; - int frame = 0; - int numFrames = 0; - - ~AudioInterfacePort() { - // Close stream here before destructing AudioInterfacePort, so the mutexes are still valid when waiting to close. - setDeviceId(-1, 0); - } - - void processStream(const float* input, float* output, int frames) override { - if (APP->engine->getPrimaryModule() != module) { - // TEMP - std::memset(output, 0, sizeof(float) * frames * numOutputs); - return; - } - - frame = 0; - numFrames = frames; - this->input = input; - this->output = output; - - APP->engine->step(frames); - - // // Reactivate idle stream - // if (!active) { - // active = true; - // inputBuffer.clear(); - // outputBuffer.clear(); - // } - - // 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; - // dsp::Frame inputFrame; - // std::memset(&inputFrame, 0, sizeof(inputFrame)); - // std::memcpy(&inputFrame, &input[numInputs * i], numInputs * sizeof(float)); - // inputBuffer.push(inputFrame); - // } - // } - - // 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++) { - // dsp::Frame f = outputBuffer.shift(); - // for (int j = 0; j < numOutputs; j++) { - // output[numOutputs * i + j] = clamp(f.samples[j], -1.f, 1.f); - // } - // } - // } - // else { - // // Timed out, fill output with zeros - // std::memset(output, 0, frames * numOutputs * sizeof(float)); - // // DEBUG("Audio Interface Port underflow"); - // } - // } - - // // Notify engine when finished processing - // engineCv.notify_one(); - } - - void onCloseStream() override { - // inputBuffer.clear(); - // outputBuffer.clear(); - } - - void onChannelsChange() override { - } -}; - - -template -struct AudioInterface : Module { +struct AudioInterface : Module, audio::Port { enum ParamIds { NUM_PARAMS }; @@ -124,7 +31,6 @@ struct AudioInterface : Module { NUM_LIGHTS }; - AudioInterfacePort port; // int lastSampleRate = 0; // int lastNumOutputs = -1; // int lastNumInputs = -1; @@ -132,9 +38,8 @@ struct AudioInterface : Module { // dsp::SampleRateConverter inputSrc; // dsp::SampleRateConverter outputSrc; - // // in rack's sample rate - // dsp::DoubleRingBuffer, 16> inputBuffer; - // dsp::DoubleRingBuffer, 16> outputBuffer; + dsp::DoubleRingBuffer, 16384> inputBuffer; + dsp::DoubleRingBuffer, 16384> outputBuffer; AudioInterface() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); @@ -142,33 +47,32 @@ struct AudioInterface : Module { configInput(AUDIO_INPUTS + i, string::f("To device %d", i + 1)); for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) configOutput(AUDIO_OUTPUTS + i, string::f("From device %d", i + 1)); - port.maxChannels = std::max(NUM_AUDIO_INPUTS, NUM_AUDIO_OUTPUTS); - port.module = this; + maxChannels = std::max(NUM_AUDIO_INPUTS, NUM_AUDIO_OUTPUTS); onSampleRateChange(); } - void process(const ProcessArgs& args) override { - // Claim primary module if there is none - if (!APP->engine->getPrimaryModule()) { - APP->engine->setPrimaryModule(this); - } + ~AudioInterface() { + // Close stream here before destructing AudioInterfacePort, so the mutexes are still valid when waiting to close. + setDeviceId(-1, 0); + } + void process(const ProcessArgs& args) override { // Get inputs - for (int i = 0; i < port.numOutputs; i++) { - float v = inputs[AUDIO_INPUTS + i].getVoltage() / 10.f; - port.output[port.frame * port.numOutputs + i] = v; + if (!inputBuffer.full()) { + dsp::Frame inputFrame; + for (int i = 0; i < NUM_AUDIO_INPUTS; i++) { + inputFrame.samples[i] = inputs[AUDIO_INPUTS + i].getVoltage() / 10.f; + } + inputBuffer.push(inputFrame); } // Set outputs - for (int i = 0; i < port.numInputs; i++) { - float v = port.input[port.frame * port.numInputs + i]; - outputs[AUDIO_OUTPUTS + i].setVoltage(10.f * v); + if (!outputBuffer.empty()) { + dsp::Frame outputFrame = outputBuffer.shift(); + for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) { + outputs[AUDIO_OUTPUTS + i].setVoltage(10.f * outputFrame.samples[i]); + } } - for (int i = port.numInputs; i < NUM_AUDIO_INPUTS; i++) { - outputs[AUDIO_OUTPUTS + i].setVoltage(0.f); - } - - port.frame++; // // Update SRC states // inputSrc.setRates(port.sampleRate, args.sampleRate); @@ -259,27 +163,112 @@ struct AudioInterface : Module { // Turn on light if at least one port is enabled in the nearby pair for (int i = 0; i < NUM_AUDIO_INPUTS / 2; i++) { - lights[INPUT_LIGHTS + i].setBrightness(port.numOutputs >= 2 * i + 1); + lights[INPUT_LIGHTS + i].setBrightness(numOutputs >= 2 * i + 1); } for (int i = 0; i < NUM_AUDIO_OUTPUTS / 2; i++) { - lights[OUTPUT_LIGHTS + i].setBrightness(port.numInputs >= 2 * i + 1); + lights[OUTPUT_LIGHTS + i].setBrightness(numInputs >= 2 * i + 1); } } json_t* dataToJson() override { json_t* rootJ = json_object(); - json_object_set_new(rootJ, "audio", port.toJson()); + json_object_set_new(rootJ, "audio", audio::Port::toJson()); return rootJ; } void dataFromJson(json_t* rootJ) override { json_t* audioJ = json_object_get(rootJ, "audio"); if (audioJ) - port.fromJson(audioJ); + audio::Port::fromJson(audioJ); } void onReset() override { - port.setDeviceId(-1, 0); + setDeviceId(-1, 0); + } + + // audio::Port + + void processStream(const float* input, float* output, int frames) override { + // Claim primary module if there is none + if (!APP->engine->getPrimaryModule()) { + APP->engine->setPrimaryModule(this); + } + + // Clear output in case the audio driver uses this buffer in another thread before this method returns. (Not sure if any do this in practice.) + std::memset(output, 0, sizeof(float) * numOutputs * frames); + + // audio input -> module output + for (int i = 0; i < frames; i++) { + if (outputBuffer.full()) + break; + dsp::Frame outputFrame; + std::memset(&outputFrame, 0, sizeof(outputFrame)); + for (int j = 0; j < std::min(numInputs, NUM_AUDIO_OUTPUTS); j++) { + outputFrame.samples[j] = input[i * numInputs + j]; + } + outputBuffer.push(outputFrame); + } + + // Step engine estimated number of steps + if (APP->engine->getPrimaryModule() == this) { + APP->engine->step(frames); + } + + // module input -> audio output + for (int i = 0; i < frames; i++) { + if (inputBuffer.empty()) + break; + dsp::Frame inputFrame = inputBuffer.shift(); + for (int j = 0; j < std::min(numOutputs, NUM_AUDIO_INPUTS); j++) { + output[i * numOutputs + j] = inputFrame.samples[j]; + } + } + + + // 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; + // dsp::Frame inputFrame; + // std::memset(&inputFrame, 0, sizeof(inputFrame)); + // std::memcpy(&inputFrame, &input[numInputs * i], numInputs * sizeof(float)); + // inputBuffer.push(inputFrame); + // } + // } + + // 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++) { + // dsp::Frame f = outputBuffer.shift(); + // for (int j = 0; j < numOutputs; j++) { + // output[numOutputs * i + j] = clamp(f.samples[j], -1.f, 1.f); + // } + // } + // } + // else { + // // Timed out, fill output with zeros + // std::memset(output, 0, frames * numOutputs * sizeof(float)); + // // DEBUG("Audio Interface Port underflow"); + // } + // } + + // // Notify engine when finished processing + // engineCv.notify_one(); + } + + void onCloseStream() override { + // inputBuffer.clear(); + // outputBuffer.clear(); + } + + void onChannelsChange() override { } }; @@ -338,7 +327,7 @@ struct AudioInterfaceWidget : ModuleWidget { AudioWidget* audioWidget = createWidget(mm2px(Vec(3.2122073, 14.837339))); audioWidget->box.size = mm2px(Vec(44, 28)); - audioWidget->setAudioPort(module ? &module->port : NULL); + audioWidget->setAudioPort(module); addChild(audioWidget); } else if (NUM_AUDIO_INPUTS == 16 && NUM_AUDIO_OUTPUTS == 16) { @@ -402,7 +391,7 @@ struct AudioInterfaceWidget : ModuleWidget { AudioWidget* audioWidget = createWidget(mm2px(Vec(2.57, 14.839))); audioWidget->box.size = mm2px(Vec(91.382, 28.0)); - audioWidget->setAudioPort(module ? &module->port : NULL); + audioWidget->setAudioPort(module); addChild(audioWidget); } }