Browse Source

Audio Interface: major refactor of threading and multiple driver support

tags/v0.5.0
Andrew Belt 7 years ago
parent
commit
d1f213a3e1
1 changed files with 174 additions and 79 deletions
  1. +174
    -79
      src/core/AudioInterface.cpp

+ 174
- 79
src/core/AudioInterface.cpp View File

@@ -29,7 +29,7 @@ struct AudioInterface : Module {
NUM_OUTPUTS = AUDIO1_OUTPUT + 8
};

RtAudio stream;
RtAudio *stream = NULL;
// Stream properties
int deviceId = -1;
float sampleRate = 44100.0;
@@ -46,9 +46,11 @@ struct AudioInterface : Module {
// in device's sample rate
DoubleRingBuffer<Frame<8>, (1<<15)> inputSrcBuffer;

AudioInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {}
AudioInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {
setDriver(RtAudio::UNSPECIFIED);
}
~AudioInterface() {
closeDevice();
closeStream();
}

void step() override;
@@ -57,18 +59,44 @@ struct AudioInterface : Module {
int getDeviceCount();
std::string getDeviceName(int deviceId);

void openDevice(int deviceId, float sampleRate, int blockSize);
void closeDevice();

void setDeviceId(int deviceId) {
openDevice(deviceId, sampleRate, blockSize);
}
void setSampleRate(float sampleRate) {
openDevice(deviceId, sampleRate, blockSize);
}
void setBlockSize(int blockSize) {
openDevice(deviceId, sampleRate, blockSize);
void openStream();
void closeStream();

void setDriver(int driver) {
closeStream();
if (stream)
delete stream;
stream = new RtAudio((RtAudio::Api) driver);
}
int getDriver() {
if (!stream)
return RtAudio::UNSPECIFIED;
return stream->getCurrentApi();
}
std::vector<int> getAvailableDrivers() {
std::vector<RtAudio::Api> apis;
RtAudio::getCompiledApi(apis);
std::vector<int> drivers;
for (RtAudio::Api api : apis)
drivers.push_back(api);
return drivers;
}
std::string getDriverName(int driver) {
switch (driver) {
case RtAudio::UNSPECIFIED: return "Unspecified";
case RtAudio::LINUX_ALSA: return "ALSA";
case RtAudio::LINUX_PULSE: return "PulseAudio";
case RtAudio::LINUX_OSS: return "OSS";
case RtAudio::UNIX_JACK: return "JACK";
case RtAudio::MACOSX_CORE: return "Core Audio";
case RtAudio::WINDOWS_WASAPI: return "WASAPI";
case RtAudio::WINDOWS_ASIO: return "ASIO";
case RtAudio::WINDOWS_DS: return "DirectSound";
case RtAudio::RTAUDIO_DUMMY: return "Dummy";
default: return "Unknown";
}
}

std::vector<float> getSampleRates();

json_t *toJson() override {
@@ -88,7 +116,7 @@ struct AudioInterface : Module {
std::string deviceName = json_string_value(deviceNameJ);
for (int i = 0; i < getDeviceCount(); i++) {
if (deviceName == getDeviceName(i)) {
setDeviceId(i);
deviceId = i;
break;
}
}
@@ -96,40 +124,42 @@ struct AudioInterface : Module {

json_t *sampleRateJ = json_object_get(rootJ, "sampleRate");
if (sampleRateJ) {
setSampleRate(json_number_value(sampleRateJ));
sampleRate = json_number_value(sampleRateJ);
}

json_t *blockSizeJ = json_object_get(rootJ, "blockSize");
if (blockSizeJ) {
setBlockSize(json_integer_value(blockSizeJ));
blockSize = json_integer_value(blockSizeJ);
}

openStream();
}

void reset() override {
closeDevice();
closeStream();
}
};


#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() {
// Read/write stream if we have enough input, OR the output buffer is empty if we have no input
if (numOutputs > 0) {
const float maxTime = 10e-3;
const float spinTime = 100e-6;
for (float time = 0.0; time < maxTime; time += spinTime) {
if (inputSrcBuffer.size() < blockSize)
break;
std::this_thread::sleep_for(std::chrono::duration<float>(spinTime));
}
TIMED_SLEEP_LOCK(inputSrcBuffer.size() < blockSize, 100e-6, 0.2);
}
else if (numInputs > 0) {
const float maxTime = 10e-3;
const float spinTime = 100e-6;
for (float time = 0.0; time < maxTime; time += spinTime) {
if (!outputBuffer.empty())
break;
std::this_thread::sleep_for(std::chrono::duration<float>(spinTime));
}
TIMED_SLEEP_LOCK(!outputBuffer.empty(), 100e-6, 0.2);
}

// Get input and pass it through the sample rate converter
@@ -164,20 +194,14 @@ void AudioInterface::step() {
}

void AudioInterface::stepStream(const float *input, float *output, int numFrames) {
// if (gPaused) {
// memset(output, 0, sizeof(float) * numOutputs * numFrames);
// return;
// }
if (gPaused) {
memset(output, 0, sizeof(float) * numOutputs * numFrames);
return;
}

if (numOutputs > 0) {
// Wait for enough input before proceeding
const float maxTime = 10e-3;
const float spinTime = 100e-6;
for (float time = 0.0; time < maxTime; time += spinTime) {
if (inputSrcBuffer.size() >= numFrames)
break;
std::this_thread::sleep_for(std::chrono::duration<float>(spinTime));
}
TIMED_SLEEP_LOCK(inputSrcBuffer.size() >= numFrames, 100e-6, 0.2);
}

// input stream -> output buffer
@@ -215,15 +239,17 @@ void AudioInterface::stepStream(const float *input, float *output, int numFrames
}

int AudioInterface::getDeviceCount() {
return stream.getDeviceCount();
if (!stream)
return 0;
return stream->getDeviceCount();
}

std::string AudioInterface::getDeviceName(int deviceId) {
if (deviceId < 0)
if (!stream || deviceId < 0)
return "";

try {
RtAudio::DeviceInfo deviceInfo = stream.getDeviceInfo(deviceId);
RtAudio::DeviceInfo deviceInfo = stream->getDeviceInfo(deviceId);
return stringf("%s (%d in, %d out)", deviceInfo.name.c_str(), deviceInfo.inputChannels, deviceInfo.outputChannels);
}
catch (RtAudioError &e) {
@@ -239,17 +265,17 @@ static int rtCallback(void *outputBuffer, void *inputBuffer, unsigned int nFrame
return 0;
}

void AudioInterface::openDevice(int deviceId, float sampleRate, int blockSize) {
closeDevice();
this->sampleRate = sampleRate;
this->blockSize = blockSize;
void AudioInterface::openStream() {
int deviceId = this->deviceId;
closeStream();
if (!stream)
return;

// Open new device
if (deviceId >= 0) {
RtAudio::DeviceInfo deviceInfo;
try {
deviceInfo = stream.getDeviceInfo(deviceId);
deviceInfo = stream->getDeviceInfo(deviceId);
}
catch (RtAudioError &e) {
warn("Failed to query audio device: %s", e.what());
@@ -259,6 +285,11 @@ void AudioInterface::openDevice(int deviceId, float sampleRate, int blockSize) {
numOutputs = mini(deviceInfo.outputChannels, 8);
numInputs = mini(deviceInfo.inputChannels, 8);

if (numOutputs == 0 && numInputs == 0) {
warn("Audio device %d has 0 inputs and 0 outputs");
return;
}

RtAudio::StreamParameters outParameters;
outParameters.deviceId = deviceId;
outParameters.nChannels = numOutputs;
@@ -268,15 +299,23 @@ void AudioInterface::openDevice(int deviceId, float sampleRate, int blockSize) {
inParameters.nChannels = numInputs;

RtAudio::StreamOptions options;
options.flags |= RTAUDIO_SCHEDULE_REALTIME;
// options.flags |= RTAUDIO_SCHEDULE_REALTIME;

// Find closest sample rate
unsigned int closestSampleRate = 0;
for (unsigned int sr : deviceInfo.sampleRates) {
if (fabsf(sr - sampleRate) < fabsf(closestSampleRate - sampleRate)) {
closestSampleRate = sr;
}
}

try {
// Don't use stream parameters if 0 input or output channels
debug("Opening audio stream %d", deviceId);
stream.openStream(
stream->openStream(
numOutputs == 0 ? NULL : &outParameters,
numInputs == 0 ? NULL : &inParameters,
RTAUDIO_FLOAT32, sampleRate, (unsigned int*) &blockSize, &rtCallback, this, &options, NULL);
RTAUDIO_FLOAT32, closestSampleRate, (unsigned int*) &blockSize, &rtCallback, this, &options, NULL);
}
catch (RtAudioError &e) {
warn("Failed to open audio stream: %s", e.what());
@@ -285,29 +324,38 @@ void AudioInterface::openDevice(int deviceId, float sampleRate, int blockSize) {

try {
debug("Starting audio stream %d", deviceId);
stream.startStream();
stream->startStream();
}
catch (RtAudioError &e) {
warn("Failed to start audio stream: %s", e.what());
return;
}

this->sampleRate = stream.getStreamSampleRate();
// Update sample rate because this may have changed
this->sampleRate = stream->getStreamSampleRate();
this->deviceId = deviceId;
}
}

void AudioInterface::closeDevice() {
if (stream.isStreamOpen()) {
try {
void AudioInterface::closeStream() {
if (stream) {
if (stream->isStreamRunning()) {
debug("Aborting audio stream %d", deviceId);
stream.abortStream();
debug("Closing audio stream %d", deviceId);
stream.closeStream();
try {
stream->abortStream();
}
catch (RtAudioError &e) {
warn("Failed to abort stream %s", e.what());
}
}
catch (RtAudioError &e) {
warn("Failed to abort stream %s", e.what());
return;
if (stream->isStreamOpen()) {
debug("Closing audio stream %d", deviceId);
try {
stream->closeStream();
}
catch (RtAudioError &e) {
warn("Failed to close stream %s", e.what());
}
}
}

@@ -326,12 +374,12 @@ void AudioInterface::closeDevice() {

std::vector<float> AudioInterface::getSampleRates() {
std::vector<float> allowedSampleRates = {44100, 48000, 88200, 96000, 176400, 192000};
if (deviceId < 0)
if (!stream || deviceId < 0)
return allowedSampleRates;

try {
std::vector<float> sampleRates;
RtAudio::DeviceInfo deviceInfo = stream.getDeviceInfo(deviceId);
RtAudio::DeviceInfo deviceInfo = stream->getDeviceInfo(deviceId);
for (int sr : deviceInfo.sampleRates) {
float sampleRate = sr;
auto allowedIt = std::find(allowedSampleRates.begin(), allowedSampleRates.end(), sampleRate);
@@ -349,15 +397,45 @@ std::vector<float> AudioInterface::getSampleRates() {



struct AudioItem : MenuItem {
struct AudioDriverItem : MenuItem {
AudioInterface *audioInterface;
int driver;
void onAction(EventAction &e) override {
audioInterface->setDriver(driver);
}
};

struct AudioDriverChoice : ChoiceButton {
AudioInterface *audioInterface;
void onAction(EventAction &e) override {
Menu *menu = gScene->createMenu();
menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)).round();
menu->box.size.x = box.size.x;

for (int driver : audioInterface->getAvailableDrivers()) {
AudioDriverItem *audioItem = new AudioDriverItem();
audioItem->audioInterface = audioInterface;
audioItem->driver = driver;
audioItem->text = audioInterface->getDriverName(driver);
menu->pushChild(audioItem);
}
}
void step() override {
text = audioInterface->getDriverName(audioInterface->getDriver());
}
};


struct AudioDeviceItem : MenuItem {
AudioInterface *audioInterface;
int deviceId;
void onAction(EventAction &e) override {
audioInterface->setDeviceId(deviceId);
audioInterface->deviceId = deviceId;
audioInterface->openStream();
}
};

struct AudioChoice : ChoiceButton {
struct AudioDeviceChoice : ChoiceButton {
int lastDeviceId = -1;
AudioInterface *audioInterface;
void onAction(EventAction &e) override {
@@ -367,14 +445,14 @@ struct AudioChoice : ChoiceButton {

int deviceCount = audioInterface->getDeviceCount();
{
AudioItem *audioItem = new AudioItem();
AudioDeviceItem *audioItem = new AudioDeviceItem();
audioItem->audioInterface = audioInterface;
audioItem->deviceId = -1;
audioItem->text = "No device";
menu->pushChild(audioItem);
}
for (int deviceId = 0; deviceId < deviceCount; deviceId++) {
AudioItem *audioItem = new AudioItem();
AudioDeviceItem *audioItem = new AudioDeviceItem();
audioItem->audioInterface = audioInterface;
audioItem->deviceId = deviceId;
audioItem->text = audioInterface->getDeviceName(deviceId);
@@ -395,7 +473,8 @@ struct SampleRateItem : MenuItem {
AudioInterface *audioInterface;
float sampleRate;
void onAction(EventAction &e) override {
audioInterface->setSampleRate(sampleRate);
audioInterface->sampleRate = sampleRate;
audioInterface->openStream();
}
};

@@ -424,7 +503,8 @@ struct BlockSizeItem : MenuItem {
AudioInterface *audioInterface;
int blockSize;
void onAction(EventAction &e) override {
audioInterface->setBlockSize(blockSize);
audioInterface->blockSize = blockSize;
audioInterface->openStream();
}
};

@@ -471,6 +551,21 @@ AudioInterfaceWidget::AudioInterfaceWidget() {
float yPos = margin;
float xPos;

{
Label *label = new Label();
label->box.pos = Vec(margin, yPos);
label->text = "Audio driver";
addChild(label);
yPos += labelHeight + margin;

AudioDriverChoice *choice = new AudioDriverChoice();
choice->audioInterface = module;
choice->box.pos = Vec(margin, yPos);
choice->box.size.x = box.size.x - 2*margin;
addChild(choice);
yPos += choice->box.size.y + margin;
}

{
Label *label = new Label();
label->box.pos = Vec(margin, yPos);
@@ -478,8 +573,8 @@ AudioInterfaceWidget::AudioInterfaceWidget() {
addChild(label);
yPos += labelHeight + margin;

AudioChoice *choice = new AudioChoice();
choice->audioInterface = dynamic_cast<AudioInterface*>(module);
AudioDeviceChoice *choice = new AudioDeviceChoice();
choice->audioInterface = module;
choice->box.pos = Vec(margin, yPos);
choice->box.size.x = box.size.x - 2*margin;
addChild(choice);
@@ -494,7 +589,7 @@ AudioInterfaceWidget::AudioInterfaceWidget() {
yPos += labelHeight + margin;

SampleRateChoice *choice = new SampleRateChoice();
choice->audioInterface = dynamic_cast<AudioInterface*>(module);
choice->audioInterface = module;
choice->box.pos = Vec(margin, yPos);
choice->box.size.x = box.size.x - 2*margin;
addChild(choice);
@@ -509,7 +604,7 @@ AudioInterfaceWidget::AudioInterfaceWidget() {
yPos += labelHeight + margin;

BlockSizeChoice *choice = new BlockSizeChoice();
choice->audioInterface = dynamic_cast<AudioInterface*>(module);
choice->audioInterface = module;
choice->box.pos = Vec(margin, yPos);
choice->box.size.x = box.size.x - 2*margin;
addChild(choice);


Loading…
Cancel
Save