Browse Source

Use AudioIO in AudioInterface

tags/v0.6.0
Andrew Belt 6 years ago
parent
commit
ce1906a288
4 changed files with 160 additions and 131 deletions
  1. +5
    -1
      include/audio.hpp
  2. +11
    -4
      include/dsp/samplerate.hpp
  3. +4
    -2
      src/audio.cpp
  4. +140
    -124
      src/core/AudioInterface.cpp

+ 5
- 1
include/audio.hpp View File

@@ -22,7 +22,7 @@ struct AudioIO {
int numInputs = 0;

AudioIO();
~AudioIO();
virtual ~AudioIO();

std::vector<int> listDrivers();
std::string getDriverName(int driver);
@@ -36,6 +36,10 @@ struct AudioIO {
void closeStream();

std::vector<int> listSampleRates();

virtual void processStream(const float *input, float *output, int length) {}
virtual void onCloseStream() {}
virtual void onOpenStream() {}
};




+ 11
- 4
include/dsp/samplerate.hpp View File

@@ -10,7 +10,7 @@ namespace rack {

template<int CHANNELS>
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<CHANNELS> *in, int *inFrames, Frame<CHANNELS> *out, int *outFrames) {
if (bypass) {


+ 4
- 2
src/audio.cpp View File

@@ -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<int> AudioIO::listSampleRates() {


+ 140
- 124
src/core/AudioInterface.cpp View File

@@ -1,6 +1,9 @@
#include <assert.h>
#include <mutex>
#include <chrono>
#include <thread>
#include <mutex>
#include <condition_variable>
#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<Frame<MAX_INPUTS>, (1<<15)> inputBuffer;
// Audio thread consumes, engine thread produces
DoubleRingBuffer<Frame<MAX_OUTPUTS>, (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<MAX_INPUTS> f;
memset(&f, 0, sizeof(f));
memcpy(&f, &input[numInputs * i], numInputs * sizeof(float));
inputBuffer.push(f);
}
}

if (numOutputs > 0) {
std::unique_lock<std::mutex> 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<MAX_OUTPUTS> 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<Frame<8>, 16> inputBuffer;
DoubleRingBuffer<Frame<8>, (1<<15)> outputBuffer;
// in device's sample rate
DoubleRingBuffer<Frame<8>, (1<<15)> inputSrcBuffer;
DoubleRingBuffer<Frame<MAX_INPUTS>, 16> inputBuffer;
DoubleRingBuffer<Frame<MAX_OUTPUTS>, 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<float>(_spinTime)); \
auto currTime = std::chrono::high_resolution_clock::now(); \
float totalTime = std::chrono::duration<float>(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<MAX_INPUTS> 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<MAX_OUTPUTS> 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<std::mutex> 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<PJ3410Port>(Vec(xPos, yPos), module, AudioInterface::AUDIO1_INPUT + i));
addInput(createInput<PJ3410Port>(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<PJ3410Port>(Vec(xPos, yPos), module, AudioInterface::AUDIO1_INPUT + i));
addInput(createInput<PJ3410Port>(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<PJ3410Port>(Vec(xPos, yPos), module, AudioInterface::AUDIO1_OUTPUT + i));
addOutput(createOutput<PJ3410Port>(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<PJ3410Port>(Vec(xPos, yPos), module, AudioInterface::AUDIO1_OUTPUT + i));
addOutput(createOutput<PJ3410Port>(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);


Loading…
Cancel
Save