diff --git a/include/audio.hpp b/include/audio.hpp index 6570be5d..fff6ce3c 100644 --- a/include/audio.hpp +++ b/include/audio.hpp @@ -22,7 +22,7 @@ struct AudioIO { int numInputs = 0; AudioIO(); - ~AudioIO(); + virtual ~AudioIO(); std::vector listDrivers(); std::string getDriverName(int driver); @@ -36,6 +36,10 @@ struct AudioIO { void closeStream(); std::vector listSampleRates(); + + virtual void processStream(const float *input, float *output, int length) {} + virtual void onCloseStream() {} + virtual void onOpenStream() {} }; diff --git a/include/dsp/samplerate.hpp b/include/dsp/samplerate.hpp index a4a99880..d74d99a6 100644 --- a/include/dsp/samplerate.hpp +++ b/include/dsp/samplerate.hpp @@ -10,7 +10,7 @@ namespace rack { template struct SampleRateConverter { - SpeexResamplerState *state = NULL; + SpeexResamplerState *state; bool bypass = false; SampleRateConverter() { @@ -27,14 +27,21 @@ struct SampleRateConverter { } void setRates(int inRate, int outRate) { - spx_uint32_t oldInRate, oldOutRate; - speex_resampler_get_rate(state, &oldInRate, &oldOutRate); - if (inRate == (int) oldInRate && outRate == (int) oldOutRate) + int oldInRate, oldOutRate; + getRates(&oldInRate, &oldOutRate); + if (inRate == oldInRate && outRate == oldOutRate) return; int error = speex_resampler_set_rate(state, inRate, outRate); assert(error == RESAMPLER_ERR_SUCCESS); } + void getRates(int *inRate, int *outRate) { + spx_uint32_t inRate32, outRate32; + speex_resampler_get_rate(state, &inRate32, &outRate32); + if (inRate) *inRate = inRate32; + if (outRate) *outRate = outRate32; + } + /** `in` and `out` are interlaced with the number of channels */ void process(const Frame *in, int *inFrames, Frame *out, int *outFrames) { if (bypass) { diff --git a/src/audio.cpp b/src/audio.cpp index 3484160f..b4c31d77 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -46,7 +46,7 @@ int AudioIO::getDriver() { } void AudioIO::setDriver(int driver) { - // closeStream(); + closeStream(); if (stream) delete stream; stream = new RtAudio((RtAudio::Api) driver); @@ -89,7 +89,7 @@ std::string AudioIO::getDeviceDetail(int device) { static int rtCallback(void *outputBuffer, void *inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void *userData) { AudioIO *audioIO = (AudioIO*) userData; assert(audioIO); - // audioInterface->stepStream((const float *) inputBuffer, (float *) outputBuffer, nFrames); + audioIO->processStream((const float *) inputBuffer, (float *) outputBuffer, nFrames); return 0; } @@ -162,6 +162,7 @@ void AudioIO::openStream() { // Update sample rate because this may have changed this->sampleRate = stream->getStreamSampleRate(); this->device = device; + onOpenStream(); } } @@ -191,6 +192,7 @@ void AudioIO::closeStream() { device = -1; numOutputs = 0; numInputs = 0; + onCloseStream(); } std::vector AudioIO::listSampleRates() { diff --git a/src/core/AudioInterface.cpp b/src/core/AudioInterface.cpp index 19475460..2cdd81fe 100644 --- a/src/core/AudioInterface.cpp +++ b/src/core/AudioInterface.cpp @@ -1,6 +1,9 @@ #include #include +#include #include +#include +#include #include "core.hpp" #include "audio.hpp" #include "dsp/samplerate.hpp" @@ -12,33 +15,93 @@ #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 { - AUDIO1_INPUT, - NUM_INPUTS = AUDIO1_INPUT + 8 + ENUMS(AUDIO_INPUT, MAX_INPUTS), + NUM_INPUTS }; enum OutputIds { - AUDIO1_OUTPUT, - NUM_OUTPUTS = AUDIO1_OUTPUT + 8 + ENUMS(AUDIO_OUTPUT, MAX_OUTPUTS), + NUM_OUTPUTS }; - AudioIO audioIO; + AudioInterfaceIO audioIO; SampleRateConverter<8> inputSrc; SampleRateConverter<8> outputSrc; // in rack's sample rate - DoubleRingBuffer, 16> inputBuffer; - DoubleRingBuffer, (1<<15)> outputBuffer; - // in device's sample rate - DoubleRingBuffer, (1<<15)> inputSrcBuffer; + DoubleRingBuffer, 16> inputBuffer; + DoubleRingBuffer, 16> outputBuffer; AudioInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { } @@ -49,31 +112,40 @@ struct AudioInterface : Module { json_t *toJson() override { json_t *rootJ = json_object(); - // json_object_set_new(rootJ, "driver", json_integer(getDriver())); - // json_object_set_new(rootJ, "device", json_integer(device)); - // json_object_set_new(rootJ, "audioIO.sampleRate", json_real(audioIO.sampleRate)); - // json_object_set_new(rootJ, "audioIO.blockSize", json_integer(audioIO.blockSize)); + 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) - // setDriver(json_number_value(driverJ)); - - // json_t *deviceJ = json_object_get(rootJ, "device"); - // if (deviceJ) - // device = json_number_value(deviceJ); + 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, "audioIO.sampleRate"); - // if (sampleRateJ) - // audioIO.sampleRate = json_number_value(sampleRateJ); + json_t *sampleRateJ = json_object_get(rootJ, "sampleRate"); + if (sampleRateJ) + audioIO.sampleRate = json_integer_value(sampleRateJ); - // json_t *blockSizeJ = json_object_get(rootJ, "audioIO.blockSize"); - // if (blockSizeJ) - // audioIO.blockSize = json_integer_value(blockSizeJ); + json_t *blockSizeJ = json_object_get(rootJ, "blockSize"); + if (blockSizeJ) + audioIO.blockSize = json_integer_value(blockSizeJ); - // openStream(); + audioIO.openStream(); } void onReset() override { @@ -82,117 +154,61 @@ struct AudioInterface : Module { }; -#define TIMED_SLEEP_LOCK(_cond, _spinTime, _totalTime) { \ - auto startTime = std::chrono::high_resolution_clock::now(); \ - while (!(_cond)) { \ - std::this_thread::sleep_for(std::chrono::duration(_spinTime)); \ - auto currTime = std::chrono::high_resolution_clock::now(); \ - float totalTime = std::chrono::duration(currTime - startTime).count(); \ - if (totalTime > (_totalTime)) \ - break; \ - } \ -} - - void AudioInterface::step() { - // debug("inputBuffer %d inputSrcBuffer %d outputBuffer %d", inputBuffer.size(), inputSrcBuffer.size(), outputBuffer.size()); - // Read/write stream if we have enough input, OR the output buffer is empty if we have no input - if (audioIO.numOutputs > 0) { - TIMED_SLEEP_LOCK(inputSrcBuffer.size() < audioIO.blockSize, 100e-6, 0.2); - } - else if (audioIO.numInputs > 0) { - TIMED_SLEEP_LOCK(!outputBuffer.empty(), 100e-6, 0.2); - } + Frame inputFrame; + memset(&inputFrame, 0, sizeof(inputFrame)); - // Get input and pass it through the sample rate converter - if (audioIO.numOutputs > 0) { - if (!inputBuffer.full()) { - Frame<8> f; - for (int i = 0; i < 8; i++) { - f.samples[i] = inputs[AUDIO1_INPUT + i].value / 10.0; - } - inputBuffer.push(f); - } - - // Once full, sample rate convert the input - // inputBuffer -> SRC -> inputSrcBuffer - if (inputBuffer.full()) { - inputSrc.setRates(engineGetSampleRate(), audioIO.sampleRate); - int inLen = inputBuffer.size(); - int outLen = inputSrcBuffer.capacity(); - inputSrc.process(inputBuffer.startData(), &inLen, inputSrcBuffer.endData(), &outLen); - inputBuffer.startIncr(inLen); - inputSrcBuffer.endIncr(outLen); + 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); } } - // Set output - if (!outputBuffer.empty()) { - Frame<8> f = outputBuffer.shift(); - for (int i = 0; i < 8; i++) { - outputs[AUDIO1_OUTPUT + i].value = 10.0 * f.samples[i]; - } + if (!inputBuffer.empty()) { + inputFrame = inputBuffer.shift(); } -} - -void AudioInterface::stepStream(const float *input, float *output, int numFrames) { - if (gPaused) { - memset(output, 0, sizeof(float) * audioIO.numOutputs * numFrames); - return; + for (int i = 0; i < MAX_INPUTS; i++) { + outputs[AUDIO_OUTPUT + i].value = 10.0 * inputFrame.samples[i]; } if (audioIO.numOutputs > 0) { - // Wait for enough input before proceeding - TIMED_SLEEP_LOCK(inputSrcBuffer.size() >= numFrames, 100e-6, 0.2); - } - else if (audioIO.numInputs > 0) { - TIMED_SLEEP_LOCK(outputBuffer.empty(), 100e-6, 0.2); - } - - // input stream -> output buffer - if (audioIO.numInputs > 0) { - Frame<8> inputFrames[numFrames]; - for (int i = 0; i < numFrames; i++) { - for (int c = 0; c < 8; c++) { - inputFrames[i].samples[c] = (c < audioIO.numInputs) ? input[i*audioIO.numInputs + c] : 0.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); } - // Pass output through sample rate converter - outputSrc.setRates(audioIO.sampleRate, engineGetSampleRate()); - int inLen = numFrames; - int outLen = outputBuffer.capacity(); - outputSrc.process(inputFrames, &inLen, outputBuffer.endData(), &outLen); - outputBuffer.endIncr(outLen); - } - - // input buffer -> output stream - if (audioIO.numOutputs > 0) { - for (int i = 0; i < numFrames; i++) { - Frame<8> f; - if (inputSrcBuffer.empty()) { - memset(&f, 0, sizeof(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 { - f = inputSrcBuffer.shift(); - } - for (int c = 0; c < audioIO.numOutputs; c++) { - output[i*audioIO.numOutputs + c] = clampf(f.samples[c], -1.0, 1.0); + // Give up on pushing output } } } -} - - -// void AudioInterface::closeStream() { -// // Clear buffers -// inputBuffer.clear(); -// outputBuffer.clear(); -// inputSrcBuffer.clear(); -// inputSrc.reset(); -// outputSrc.reset(); -// } + audioIO.audioCv.notify_all(); +} AudioInterfaceWidget::AudioInterfaceWidget() { @@ -226,7 +242,7 @@ AudioInterfaceWidget::AudioInterfaceWidget() { yPos += 5; xPos = 10; for (int i = 0; i < 4; i++) { - addInput(createInput(Vec(xPos, yPos), module, AudioInterface::AUDIO1_INPUT + 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); @@ -239,7 +255,7 @@ AudioInterfaceWidget::AudioInterfaceWidget() { yPos += 5; xPos = 10; for (int i = 4; i < 8; i++) { - addInput(createInput(Vec(xPos, yPos), module, AudioInterface::AUDIO1_INPUT + 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); @@ -260,7 +276,7 @@ AudioInterfaceWidget::AudioInterfaceWidget() { yPos += 5; xPos = 10; for (int i = 0; i < 4; i++) { - addOutput(createOutput(Vec(xPos, yPos), module, AudioInterface::AUDIO1_OUTPUT + 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); @@ -273,7 +289,7 @@ AudioInterfaceWidget::AudioInterfaceWidget() { yPos += 5; xPos = 10; for (int i = 4; i < 8; i++) { - addOutput(createOutput(Vec(xPos, yPos), module, AudioInterface::AUDIO1_OUTPUT + 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);