| @@ -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); | |||