From 4f8718fcbf8eaab91d11d41c2a67b9a3535ee561 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Wed, 30 Oct 2019 11:49:02 -0400 Subject: [PATCH] Add WIP sample rate conversion to AudioInterface. --- src/core/AudioInterface.cpp | 180 +++++++----------------------------- 1 file changed, 34 insertions(+), 146 deletions(-) diff --git a/src/core/AudioInterface.cpp b/src/core/AudioInterface.cpp index 1481c8df..20595fb9 100644 --- a/src/core/AudioInterface.cpp +++ b/src/core/AudioInterface.cpp @@ -31,15 +31,11 @@ struct AudioInterface : Module, audio::Port { NUM_LIGHTS }; - // int lastSampleRate = 0; - // int lastNumOutputs = -1; - // int lastNumInputs = -1; + dsp::DoubleRingBuffer, 8192> inputBuffer; + dsp::DoubleRingBuffer, 8192> outputBuffer; - // dsp::SampleRateConverter inputSrc; - // dsp::SampleRateConverter outputSrc; - - dsp::DoubleRingBuffer, 16384> inputBuffer; - dsp::DoubleRingBuffer, 16384> outputBuffer; + dsp::SampleRateConverter inputSrc; + dsp::SampleRateConverter outputSrc; AudioInterface() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); @@ -48,7 +44,8 @@ struct AudioInterface : Module, audio::Port { for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) configOutput(AUDIO_OUTPUTS + i, string::f("From device %d", i + 1)); maxChannels = std::max(NUM_AUDIO_INPUTS, NUM_AUDIO_OUTPUTS); - onSampleRateChange(); + inputSrc.setQuality(6); + outputSrc.setQuality(6); } ~AudioInterface() { @@ -74,93 +71,6 @@ struct AudioInterface : Module, audio::Port { } } - // // Update SRC states - // inputSrc.setRates(port.sampleRate, args.sampleRate); - // outputSrc.setRates(args.sampleRate, port.sampleRate); - - // inputSrc.setChannels(port.numInputs); - // outputSrc.setChannels(port.numOutputs); - - // // Inputs: audio engine -> rack engine - // if (port.active && port.numInputs > 0) { - // // Wait until inputs are present - // // Give up after a timeout in case the audio device is being unresponsive. - // std::unique_lock lock(port.engineMutex); - // auto cond = [&] { - // return (!port.inputBuffer.empty()); - // }; - // auto timeout = std::chrono::milliseconds(200); - // if (port.engineCv.wait_for(lock, timeout, cond)) { - // // Convert inputs - // int inLen = port.inputBuffer.size(); - // int outLen = inputBuffer.capacity(); - // inputSrc.process(port.inputBuffer.startData(), &inLen, inputBuffer.endData(), &outLen); - // port.inputBuffer.startIncr(inLen); - // inputBuffer.endIncr(outLen); - // } - // else { - // // Give up on pulling input - // port.active = false; - // // DEBUG("Audio Interface underflow"); - // } - // } - - // // Take input from buffer - // dsp::Frame inputFrame; - // if (!inputBuffer.empty()) { - // inputFrame = inputBuffer.shift(); - // } - // else { - // std::memset(&inputFrame, 0, sizeof(inputFrame)); - // } - // for (int i = 0; i < port.numInputs; i++) { - // outputs[AUDIO_OUTPUTS + i].setVoltage(10.f * inputFrame.samples[i]); - // } - // for (int i = port.numInputs; i < NUM_AUDIO_INPUTS; i++) { - // outputs[AUDIO_OUTPUTS + i].setVoltage(0.f); - // } - - // // Outputs: rack engine -> audio engine - // if (port.active && port.numOutputs > 0) { - // // Get and push output SRC frame - // if (!outputBuffer.full()) { - // dsp::Frame outputFrame; - // for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) { - // outputFrame.samples[i] = inputs[AUDIO_INPUTS + i].getVoltageSum() / 10.f; - // } - // outputBuffer.push(outputFrame); - // } - - // if (outputBuffer.full()) { - // // Wait until enough outputs are consumed - // // Give up after a timeout in case the audio device is being unresponsive. - // auto cond = [&] { - // return (port.outputBuffer.size() < (size_t) port.blockSize); - // }; - // if (!cond()) - // APP->engine->yieldWorkers(); - // std::unique_lock lock(port.engineMutex); - // auto timeout = std::chrono::milliseconds(200); - // if (port.engineCv.wait_for(lock, timeout, cond)) { - // // Push converted output - // int inLen = outputBuffer.size(); - // int outLen = port.outputBuffer.capacity(); - // outputSrc.process(outputBuffer.startData(), &inLen, port.outputBuffer.endData(), &outLen); - // outputBuffer.startIncr(inLen); - // port.outputBuffer.endIncr(outLen); - // } - // else { - // // Give up on pushing output - // port.active = false; - // outputBuffer.clear(); - // // DEBUG("Audio Interface underflow"); - // } - // } - - // // Notify audio thread that an output is potentially ready - // port.audioCv.notify_one(); - // } - // 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(numOutputs >= 2 * i + 1); @@ -197,75 +107,53 @@ struct AudioInterface : Module, audio::Port { // 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); + // Initialize sample rate converters + outputSrc.setRates((int) sampleRate, (int) APP->engine->getSampleRate()); + outputSrc.setChannels(numInputs); + inputSrc.setRates((int) APP->engine->getSampleRate(), (int) sampleRate); + inputSrc.setChannels(numOutputs); + // audio input -> module output + dsp::Frame inputAudioBuffer[frames]; + std::memset(inputAudioBuffer, 0, sizeof(inputAudioBuffer)); 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]; + inputAudioBuffer[i].samples[j] = input[i * numInputs + j]; } - outputBuffer.push(outputFrame); } + int inputAudioFrames = frames; + int outputFrames = outputBuffer.capacity(); + outputSrc.process(inputAudioBuffer, &inputAudioFrames, outputBuffer.endData(), &outputFrames); + outputBuffer.endIncr(outputFrames); // Step engine estimated number of steps if (APP->engine->getPrimaryModule() == this) { - APP->engine->step(frames); + APP->engine->step(outputFrames); } // module input -> audio output + dsp::Frame outputAudioBuffer[frames]; + int inputFrames = inputBuffer.size(); + int outputAudioFrames = frames; + inputSrc.process(inputBuffer.startData(), &inputFrames, outputAudioBuffer, &outputAudioFrames); + inputBuffer.startIncr(inputFrames); 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]; + output[i * numOutputs + j] = outputAudioBuffer[i].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(); + DEBUG("%p %d: frames %d, %d -> %d outputBuffer %d, %d -> %d inputBuffer %d", + this, APP->engine->getPrimaryModule() == this, frames, + inputAudioFrames, outputFrames, outputBuffer.size(), + inputFrames, outputAudioFrames, inputBuffer.size()); } void onCloseStream() override { - // inputBuffer.clear(); - // outputBuffer.clear(); + // Reset outputs + for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) { + outputs[AUDIO_OUTPUTS + i].setVoltage(0.f); + } } void onChannelsChange() override {