|
@@ -31,6 +31,7 @@ struct AudioInterfaceIO : AudioIO { |
|
|
DoubleRingBuffer<Frame<INPUTS>, (1<<15)> inputBuffer; |
|
|
DoubleRingBuffer<Frame<INPUTS>, (1<<15)> inputBuffer; |
|
|
// Audio thread consumes, engine thread produces |
|
|
// Audio thread consumes, engine thread produces |
|
|
DoubleRingBuffer<Frame<OUTPUTS>, (1<<15)> outputBuffer; |
|
|
DoubleRingBuffer<Frame<OUTPUTS>, (1<<15)> outputBuffer; |
|
|
|
|
|
bool active = false; |
|
|
|
|
|
|
|
|
~AudioInterfaceIO() { |
|
|
~AudioInterfaceIO() { |
|
|
// Close stream here before destructing AudioInterfaceIO, so the mutexes are still valid when waiting to close. |
|
|
// Close stream here before destructing AudioInterfaceIO, so the mutexes are still valid when waiting to close. |
|
@@ -38,6 +39,13 @@ struct AudioInterfaceIO : AudioIO { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void processStream(const float *input, float *output, int frames) override { |
|
|
void processStream(const float *input, float *output, int frames) override { |
|
|
|
|
|
// Reactivate idle stream |
|
|
|
|
|
if (!active) { |
|
|
|
|
|
active = true; |
|
|
|
|
|
inputBuffer.clear(); |
|
|
|
|
|
outputBuffer.clear(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (numInputs > 0) { |
|
|
if (numInputs > 0) { |
|
|
// TODO Do we need to wait on the input to be consumed here? Experimentally, it works fine if we don't. |
|
|
// 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++) { |
|
|
for (int i = 0; i < frames; i++) { |
|
@@ -144,35 +152,50 @@ struct AudioInterface : Module { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void AudioInterface::step() { |
|
|
void AudioInterface::step() { |
|
|
Frame<INPUTS> inputFrame; |
|
|
|
|
|
memset(&inputFrame, 0, sizeof(inputFrame)); |
|
|
|
|
|
|
|
|
|
|
|
// Update sample rate if changed by audio driver |
|
|
// Update sample rate if changed by audio driver |
|
|
if (audioIO.sampleRate != lastSampleRate) { |
|
|
if (audioIO.sampleRate != lastSampleRate) { |
|
|
onSampleRateChange(); |
|
|
onSampleRateChange(); |
|
|
lastSampleRate = audioIO.sampleRate; |
|
|
lastSampleRate = audioIO.sampleRate; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (audioIO.numInputs > 0) { |
|
|
|
|
|
// Convert inputs if needed |
|
|
|
|
|
if (inputBuffer.empty()) { |
|
|
|
|
|
|
|
|
// Inputs: audio engine -> rack engine |
|
|
|
|
|
if (audioIO.active && audioIO.numInputs > 0) { |
|
|
|
|
|
// Wait until inputs are present |
|
|
|
|
|
// Give up after a timeout in case the audio device is being unresponsive. |
|
|
|
|
|
std::unique_lock<std::mutex> lock(audioIO.engineMutex); |
|
|
|
|
|
auto cond = [&] { |
|
|
|
|
|
return (!audioIO.inputBuffer.empty()); |
|
|
|
|
|
}; |
|
|
|
|
|
auto timeout = std::chrono::milliseconds(200); |
|
|
|
|
|
if (audioIO.engineCv.wait_for(lock, timeout, cond)) { |
|
|
|
|
|
// Convert inputs |
|
|
int inLen = audioIO.inputBuffer.size(); |
|
|
int inLen = audioIO.inputBuffer.size(); |
|
|
int outLen = inputBuffer.capacity(); |
|
|
int outLen = inputBuffer.capacity(); |
|
|
inputSrc.process(audioIO.inputBuffer.startData(), &inLen, inputBuffer.endData(), &outLen); |
|
|
inputSrc.process(audioIO.inputBuffer.startData(), &inLen, inputBuffer.endData(), &outLen); |
|
|
audioIO.inputBuffer.startIncr(inLen); |
|
|
audioIO.inputBuffer.startIncr(inLen); |
|
|
inputBuffer.endIncr(outLen); |
|
|
inputBuffer.endIncr(outLen); |
|
|
} |
|
|
} |
|
|
|
|
|
else { |
|
|
|
|
|
// Give up on pulling input |
|
|
|
|
|
audioIO.active = false; |
|
|
|
|
|
debug("Audio Interface underflow"); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Take input from buffer |
|
|
// Take input from buffer |
|
|
|
|
|
Frame<INPUTS> inputFrame; |
|
|
if (!inputBuffer.empty()) { |
|
|
if (!inputBuffer.empty()) { |
|
|
inputFrame = inputBuffer.shift(); |
|
|
inputFrame = inputBuffer.shift(); |
|
|
} |
|
|
} |
|
|
|
|
|
else { |
|
|
|
|
|
memset(&inputFrame, 0, sizeof(inputFrame)); |
|
|
|
|
|
} |
|
|
for (int i = 0; i < INPUTS; i++) { |
|
|
for (int i = 0; i < INPUTS; i++) { |
|
|
outputs[AUDIO_OUTPUT + i].value = 10.f * inputFrame.samples[i]; |
|
|
outputs[AUDIO_OUTPUT + i].value = 10.f * inputFrame.samples[i]; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (audioIO.numOutputs > 0) { |
|
|
|
|
|
|
|
|
// Outputs: rack engine -> audio engine |
|
|
|
|
|
if (audioIO.active && audioIO.numOutputs > 0) { |
|
|
// Get and push output SRC frame |
|
|
// Get and push output SRC frame |
|
|
if (!outputBuffer.full()) { |
|
|
if (!outputBuffer.full()) { |
|
|
Frame<OUTPUTS> outputFrame; |
|
|
Frame<OUTPUTS> outputFrame; |
|
@@ -183,13 +206,13 @@ void AudioInterface::step() { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (outputBuffer.full()) { |
|
|
if (outputBuffer.full()) { |
|
|
// Wait until outputs are needed. |
|
|
|
|
|
|
|
|
// Wait until enough outputs are consumed |
|
|
// Give up after a timeout in case the audio device is being unresponsive. |
|
|
// Give up after a timeout in case the audio device is being unresponsive. |
|
|
std::unique_lock<std::mutex> lock(audioIO.engineMutex); |
|
|
std::unique_lock<std::mutex> lock(audioIO.engineMutex); |
|
|
auto cond = [&] { |
|
|
auto cond = [&] { |
|
|
return (audioIO.outputBuffer.size() < (size_t) audioIO.blockSize); |
|
|
return (audioIO.outputBuffer.size() < (size_t) audioIO.blockSize); |
|
|
}; |
|
|
}; |
|
|
auto timeout = std::chrono::milliseconds(100); |
|
|
|
|
|
|
|
|
auto timeout = std::chrono::milliseconds(200); |
|
|
if (audioIO.engineCv.wait_for(lock, timeout, cond)) { |
|
|
if (audioIO.engineCv.wait_for(lock, timeout, cond)) { |
|
|
// Push converted output |
|
|
// Push converted output |
|
|
int inLen = outputBuffer.size(); |
|
|
int inLen = outputBuffer.size(); |
|
@@ -200,6 +223,8 @@ void AudioInterface::step() { |
|
|
} |
|
|
} |
|
|
else { |
|
|
else { |
|
|
// Give up on pushing output |
|
|
// Give up on pushing output |
|
|
|
|
|
audioIO.active = false; |
|
|
|
|
|
outputBuffer.clear(); |
|
|
debug("Audio Interface underflow"); |
|
|
debug("Audio Interface underflow"); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
@@ -210,9 +235,9 @@ void AudioInterface::step() { |
|
|
|
|
|
|
|
|
// Turn on light if at least one port is enabled in the nearby pair |
|
|
// Turn on light if at least one port is enabled in the nearby pair |
|
|
for (int i = 0; i < INPUTS / 2; i++) |
|
|
for (int i = 0; i < INPUTS / 2; i++) |
|
|
lights[INPUT_LIGHT + i].value = (audioIO.numOutputs >= 2*i+1); |
|
|
|
|
|
|
|
|
lights[INPUT_LIGHT + i].value = (audioIO.active && audioIO.numOutputs >= 2*i+1); |
|
|
for (int i = 0; i < OUTPUTS / 2; i++) |
|
|
for (int i = 0; i < OUTPUTS / 2; i++) |
|
|
lights[OUTPUT_LIGHT + i].value = (audioIO.numInputs >= 2*i+1); |
|
|
|
|
|
|
|
|
lights[OUTPUT_LIGHT + i].value = (audioIO.active && audioIO.numInputs >= 2*i+1); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|