| @@ -31,15 +31,11 @@ struct AudioInterface : Module, audio::Port { | |||||
| NUM_LIGHTS | NUM_LIGHTS | ||||
| }; | }; | ||||
| // int lastSampleRate = 0; | |||||
| // int lastNumOutputs = -1; | |||||
| // int lastNumInputs = -1; | |||||
| dsp::DoubleRingBuffer<dsp::Frame<NUM_AUDIO_INPUTS>, 8192> inputBuffer; | |||||
| dsp::DoubleRingBuffer<dsp::Frame<NUM_AUDIO_OUTPUTS>, 8192> outputBuffer; | |||||
| // dsp::SampleRateConverter<NUM_AUDIO_INPUTS> inputSrc; | |||||
| // dsp::SampleRateConverter<NUM_AUDIO_OUTPUTS> outputSrc; | |||||
| dsp::DoubleRingBuffer<dsp::Frame<NUM_AUDIO_INPUTS>, 16384> inputBuffer; | |||||
| dsp::DoubleRingBuffer<dsp::Frame<NUM_AUDIO_OUTPUTS>, 16384> outputBuffer; | |||||
| dsp::SampleRateConverter<NUM_AUDIO_INPUTS> inputSrc; | |||||
| dsp::SampleRateConverter<NUM_AUDIO_OUTPUTS> outputSrc; | |||||
| AudioInterface() { | AudioInterface() { | ||||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | 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++) | for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) | ||||
| configOutput(AUDIO_OUTPUTS + i, string::f("From device %d", i + 1)); | configOutput(AUDIO_OUTPUTS + i, string::f("From device %d", i + 1)); | ||||
| maxChannels = std::max(NUM_AUDIO_INPUTS, NUM_AUDIO_OUTPUTS); | maxChannels = std::max(NUM_AUDIO_INPUTS, NUM_AUDIO_OUTPUTS); | ||||
| onSampleRateChange(); | |||||
| inputSrc.setQuality(6); | |||||
| outputSrc.setQuality(6); | |||||
| } | } | ||||
| ~AudioInterface() { | ~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<std::mutex> 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<NUM_AUDIO_INPUTS> 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<NUM_AUDIO_OUTPUTS> 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<std::mutex> 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 | // Turn on light if at least one port is enabled in the nearby pair | ||||
| for (int i = 0; i < NUM_AUDIO_INPUTS / 2; i++) { | for (int i = 0; i < NUM_AUDIO_INPUTS / 2; i++) { | ||||
| lights[INPUT_LIGHTS + i].setBrightness(numOutputs >= 2 * i + 1); | 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.) | // 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); | 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 | // audio input -> module output | ||||
| dsp::Frame<NUM_AUDIO_OUTPUTS> inputAudioBuffer[frames]; | |||||
| std::memset(inputAudioBuffer, 0, sizeof(inputAudioBuffer)); | |||||
| for (int i = 0; i < frames; i++) { | for (int i = 0; i < frames; i++) { | ||||
| if (outputBuffer.full()) | |||||
| break; | |||||
| dsp::Frame<NUM_AUDIO_OUTPUTS> outputFrame; | |||||
| std::memset(&outputFrame, 0, sizeof(outputFrame)); | |||||
| for (int j = 0; j < std::min(numInputs, NUM_AUDIO_OUTPUTS); j++) { | 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 | // Step engine estimated number of steps | ||||
| if (APP->engine->getPrimaryModule() == this) { | if (APP->engine->getPrimaryModule() == this) { | ||||
| APP->engine->step(frames); | |||||
| APP->engine->step(outputFrames); | |||||
| } | } | ||||
| // module input -> audio output | // module input -> audio output | ||||
| dsp::Frame<NUM_AUDIO_OUTPUTS> 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++) { | for (int i = 0; i < frames; i++) { | ||||
| if (inputBuffer.empty()) | |||||
| break; | |||||
| dsp::Frame<NUM_AUDIO_INPUTS> inputFrame = inputBuffer.shift(); | |||||
| for (int j = 0; j < std::min(numOutputs, NUM_AUDIO_INPUTS); j++) { | 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<NUM_AUDIO_INPUTS> 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<std::mutex> 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<NUM_AUDIO_OUTPUTS> 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 { | 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 { | void onChannelsChange() override { | ||||