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