| @@ -31,15 +31,11 @@ struct AudioInterface : Module, audio::Port { | |||
| 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() { | |||
| 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<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 | |||
| 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<NUM_AUDIO_OUTPUTS> inputAudioBuffer[frames]; | |||
| std::memset(inputAudioBuffer, 0, sizeof(inputAudioBuffer)); | |||
| 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++) { | |||
| 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<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++) { | |||
| if (inputBuffer.empty()) | |||
| break; | |||
| dsp::Frame<NUM_AUDIO_INPUTS> 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<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 { | |||
| // 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 { | |||