diff --git a/src/core/AudioInterface.cpp b/src/core/AudioInterface.cpp index bb33b9e9..cd4b8996 100644 --- a/src/core/AudioInterface.cpp +++ b/src/core/AudioInterface.cpp @@ -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, (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 getAvailableDrivers() { + std::vector apis; + RtAudio::getCompiledApi(apis); + std::vector 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 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(_spinTime)); \ + auto currTime = std::chrono::high_resolution_clock::now(); \ + float totalTime = std::chrono::duration(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(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(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(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 AudioInterface::getSampleRates() { std::vector allowedSampleRates = {44100, 48000, 88200, 96000, 176400, 192000}; - if (deviceId < 0) + if (!stream || deviceId < 0) return allowedSampleRates; try { std::vector 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 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(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(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(module); + choice->audioInterface = module; choice->box.pos = Vec(margin, yPos); choice->box.size.x = box.size.x - 2*margin; addChild(choice);