@@ -1,14 +1,8 @@ | |||||
#pragma once | #pragma once | ||||
#include <common.hpp> | #include <common.hpp> | ||||
#include <jansson.h> | #include <jansson.h> | ||||
#pragma GCC diagnostic push | |||||
#ifndef __clang__ | |||||
#pragma GCC diagnostic ignored "-Wsuggest-override" | |||||
#endif | |||||
#include <RtAudio.h> | |||||
#pragma GCC diagnostic pop | |||||
#include <vector> | |||||
#include <set> | |||||
namespace rack { | namespace rack { | ||||
@@ -18,54 +12,173 @@ namespace rack { | |||||
namespace audio { | namespace audio { | ||||
//////////////////// | |||||
// Driver | |||||
//////////////////// | |||||
struct Port; | |||||
struct Device; | |||||
struct Driver { | |||||
virtual ~Driver() {} | |||||
virtual std::string getName() { | |||||
return ""; | |||||
} | |||||
virtual std::vector<int> getDeviceIds() { | |||||
return {}; | |||||
} | |||||
virtual std::string getDeviceName(int deviceId) { | |||||
return ""; | |||||
} | |||||
virtual Device* subscribe(int deviceId, Port* port) { | |||||
return NULL; | |||||
} | |||||
virtual void unsubscribe(int deviceId, Port* port) {} | |||||
}; | |||||
//////////////////// | |||||
// Device | |||||
//////////////////// | |||||
struct Device { | |||||
std::set<Port*> subscribed; | |||||
virtual ~Device() {} | |||||
// Called by Driver::subscribe(). | |||||
void subscribe(Port* port); | |||||
void unsubscribe(Port* port); | |||||
// Called by Port. | |||||
virtual std::vector<int> getSampleRates() { | |||||
return {}; | |||||
} | |||||
virtual int getSampleRate() { | |||||
return 0; | |||||
} | |||||
virtual void setSampleRate(int sampleRate) {} | |||||
virtual std::vector<int> getBlockSizes() { | |||||
return {}; | |||||
} | |||||
virtual int getBlockSize() { | |||||
return 0; | |||||
} | |||||
virtual void setBlockSize(int blockSize) {} | |||||
virtual int getNumInputs() { | |||||
return 0; | |||||
} | |||||
virtual int getNumOutputs() { | |||||
return 0; | |||||
} | |||||
// Called by this Device class, forwards to subscribed Ports. | |||||
void processBuffer(const float* input, int inputStride, float* output, int outputStride, int frames); | |||||
void onOpenStream(); | |||||
void onCloseStream(); | |||||
}; | |||||
//////////////////// | |||||
// Port | |||||
//////////////////// | |||||
struct Port { | struct Port { | ||||
// Stream properties | |||||
int driverId = 0; | |||||
int deviceId = -1; | |||||
/** Not owned */ | |||||
Driver* driver = NULL; | |||||
Device* device = NULL; | |||||
// Port settings | |||||
int offset = 0; | int offset = 0; | ||||
int maxChannels = 8; | int maxChannels = 8; | ||||
int sampleRate = 44100; | |||||
int blockSize = 256; | |||||
int numOutputs = 0; | |||||
int numInputs = 0; | |||||
RtAudio* rtAudio = NULL; | |||||
/** Cached */ | |||||
RtAudio::DeviceInfo deviceInfo; | |||||
// private | |||||
int driverId = -1; | |||||
int deviceId = -1; | |||||
Port(); | Port(); | ||||
virtual ~Port(); | virtual ~Port(); | ||||
std::vector<int> getDriverIds(); | std::vector<int> getDriverIds(); | ||||
std::string getDriverName(int driverId); | |||||
int getDriverId() { | |||||
return driverId; | |||||
} | |||||
void setDriverId(int driverId); | void setDriverId(int driverId); | ||||
std::string getDriverName(int driverId); | |||||
int getDeviceCount(); | |||||
bool getDeviceInfo(int deviceId, RtAudio::DeviceInfo* deviceInfo); | |||||
/** Returns the number of inputs or outputs, whichever is greater */ | |||||
int getDeviceChannels(int deviceId); | |||||
std::string getDeviceName(int deviceId); | |||||
std::vector<int> getDeviceIds() { | |||||
if (!driver) | |||||
return {}; | |||||
return driver->getDeviceIds(); | |||||
} | |||||
int getDeviceId() { | |||||
return deviceId; | |||||
} | |||||
void setDeviceId(int deviceId); | |||||
std::string getDeviceName(int deviceId) { | |||||
if (!driver) | |||||
return ""; | |||||
return driver->getDeviceName(deviceId); | |||||
} | |||||
std::string getDeviceDetail(int deviceId, int offset); | std::string getDeviceDetail(int deviceId, int offset); | ||||
void setDeviceId(int deviceId, int offset); | |||||
std::vector<int> getSampleRates(); | |||||
void setSampleRate(int sampleRate); | |||||
std::vector<int> getBlockSizes(); | |||||
void setBlockSize(int blockSize); | |||||
void setChannels(int numOutputs, int numInputs); | |||||
std::vector<int> getSampleRates() { | |||||
if (!device) | |||||
return {}; | |||||
return device->getSampleRates(); | |||||
} | |||||
int getSampleRate() { | |||||
if (!device) | |||||
return 0; | |||||
return device->getSampleRate(); | |||||
} | |||||
void setSampleRate(int sampleRate) { | |||||
if (device) | |||||
device->setSampleRate(sampleRate); | |||||
} | |||||
std::vector<int> getBlockSizes() { | |||||
if (!device) | |||||
return {}; | |||||
return device->getBlockSizes(); | |||||
} | |||||
int getBlockSize() { | |||||
if (!device) | |||||
return 0; | |||||
return device->getBlockSize(); | |||||
} | |||||
void setBlockSize(int blockSize) { | |||||
if (device) | |||||
device->setBlockSize(blockSize); | |||||
} | |||||
int getNumInputs(); | |||||
int getNumOutputs(); | |||||
/** Must close the stream before opening */ | |||||
void openStream(); | |||||
void closeStream(); | |||||
virtual void processStream(const float* input, float* output, int frames) {} | |||||
virtual void onCloseStream() {} | |||||
virtual void onOpenStream() {} | |||||
virtual void onChannelsChange() {} | |||||
json_t* toJson(); | json_t* toJson(); | ||||
void fromJson(json_t* rootJ); | void fromJson(json_t* rootJ); | ||||
/** Callback for processing the audio stream. | |||||
`inputStride` and `outputStride` are the number of array elements between frames in the buffers. | |||||
*/ | |||||
virtual void processBuffer(const float* input, int inputStride, float* output, int outputStride, int frames) {} | |||||
/** Called before processBuffer() is called for all Ports of the same device. | |||||
Splitting the processBuffer() into these calls is useful for synchronizing Ports of the same device. | |||||
Called even if there are no inputs. | |||||
*/ | |||||
virtual void processInput(const float* input, int inputStride, int frames) {} | |||||
/** Called after processBuffer() is called for all Ports of the same device. | |||||
*/ | |||||
virtual void processOutput(float* output, int outputStride, int frames) {} | |||||
virtual void onOpenStream() {} | |||||
virtual void onCloseStream() {} | |||||
}; | }; | ||||
void init(); | |||||
void destroy(); | |||||
/** Registers a new audio driver. Takes pointer ownership. */ | |||||
void addDriver(int driverId, Driver* driver); | |||||
} // namespace audio | } // namespace audio | ||||
} // namespace rack | } // namespace rack |
@@ -62,7 +62,6 @@ struct Driver { | |||||
virtual std::string getName() { | virtual std::string getName() { | ||||
return ""; | return ""; | ||||
} | } | ||||
virtual std::vector<int> getInputDeviceIds() { | virtual std::vector<int> getInputDeviceIds() { | ||||
return {}; | return {}; | ||||
} | } | ||||
@@ -0,0 +1,11 @@ | |||||
#pragma once | |||||
#include <common.hpp> | |||||
namespace rack { | |||||
void rtaudioInit(); | |||||
} // namespace rack |
@@ -27,13 +27,13 @@ struct AudioDriverChoice : LedDisplayChoice { | |||||
item->port = port; | item->port = port; | ||||
item->driverId = driverId; | item->driverId = driverId; | ||||
item->text = port->getDriverName(driverId); | item->text = port->getDriverName(driverId); | ||||
item->rightText = CHECKMARK(item->driverId == port->driverId); | |||||
item->rightText = CHECKMARK(item->driverId == port->getDriverId()); | |||||
menu->addChild(item); | menu->addChild(item); | ||||
} | } | ||||
} | } | ||||
void step() override { | void step() override { | ||||
text = (box.size.x >= 200.0) ? "Driver: " : ""; | text = (box.size.x >= 200.0) ? "Driver: " : ""; | ||||
std::string driverName = (port) ? port->getDriverName(port->driverId) : ""; | |||||
std::string driverName = port ? port->getDriverName(port->getDriverId()) : ""; | |||||
if (driverName != "") { | if (driverName != "") { | ||||
text += driverName; | text += driverName; | ||||
color.a = 1.f; | color.a = 1.f; | ||||
@@ -51,14 +51,13 @@ struct AudioDeviceItem : ui::MenuItem { | |||||
int deviceId; | int deviceId; | ||||
int offset; | int offset; | ||||
void onAction(const event::Action& e) override { | void onAction(const event::Action& e) override { | ||||
port->setDeviceId(deviceId, offset); | |||||
port->setDeviceId(deviceId); | |||||
port->offset = offset; | |||||
} | } | ||||
}; | }; | ||||
struct AudioDeviceChoice : LedDisplayChoice { | struct AudioDeviceChoice : LedDisplayChoice { | ||||
audio::Port* port; | audio::Port* port; | ||||
/** Prevents devices with a ridiculous number of channels from being displayed */ | |||||
int maxTotalChannels = 128; | |||||
void onAction(const event::Action& e) override { | void onAction(const event::Action& e) override { | ||||
if (!port) | if (!port) | ||||
@@ -66,24 +65,27 @@ struct AudioDeviceChoice : LedDisplayChoice { | |||||
ui::Menu* menu = createMenu(); | ui::Menu* menu = createMenu(); | ||||
menu->addChild(createMenuLabel("Audio device")); | menu->addChild(createMenuLabel("Audio device")); | ||||
int deviceCount = port->getDeviceCount(); | |||||
{ | { | ||||
AudioDeviceItem* item = new AudioDeviceItem; | AudioDeviceItem* item = new AudioDeviceItem; | ||||
item->port = port; | item->port = port; | ||||
item->deviceId = -1; | item->deviceId = -1; | ||||
item->text = "(No device)"; | item->text = "(No device)"; | ||||
item->rightText = CHECKMARK(item->deviceId == port->deviceId); | |||||
item->rightText = CHECKMARK(item->deviceId == port->getDeviceId()); | |||||
menu->addChild(item); | menu->addChild(item); | ||||
} | } | ||||
for (int deviceId = 0; deviceId < deviceCount; deviceId++) { | |||||
int channels = std::min(maxTotalChannels, port->getDeviceChannels(deviceId)); | |||||
for (int deviceId : port->getDeviceIds()) { | |||||
int channels = std::max(port->getNumInputs(), port->getNumOutputs()); | |||||
/** Prevents devices with a ridiculous number of channels from being displayed */ | |||||
const int maxTotalChannels = 128; | |||||
channels = std::min(maxTotalChannels, channels); | |||||
for (int offset = 0; offset < channels; offset += port->maxChannels) { | for (int offset = 0; offset < channels; offset += port->maxChannels) { | ||||
AudioDeviceItem* item = new AudioDeviceItem; | AudioDeviceItem* item = new AudioDeviceItem; | ||||
item->port = port; | item->port = port; | ||||
item->deviceId = deviceId; | item->deviceId = deviceId; | ||||
item->offset = offset; | item->offset = offset; | ||||
item->text = port->getDeviceDetail(deviceId, offset); | item->text = port->getDeviceDetail(deviceId, offset); | ||||
item->rightText = CHECKMARK(item->deviceId == port->deviceId && item->offset == port->offset); | |||||
item->rightText = CHECKMARK(item->deviceId == port->getDeviceId() && item->offset == port->offset); | |||||
menu->addChild(item); | menu->addChild(item); | ||||
} | } | ||||
} | } | ||||
@@ -128,14 +130,14 @@ struct AudioSampleRateChoice : LedDisplayChoice { | |||||
item->port = port; | item->port = port; | ||||
item->sampleRate = sampleRate; | item->sampleRate = sampleRate; | ||||
item->text = string::f("%g kHz", sampleRate / 1000.0); | item->text = string::f("%g kHz", sampleRate / 1000.0); | ||||
item->rightText = CHECKMARK(item->sampleRate == port->sampleRate); | |||||
item->rightText = CHECKMARK(item->sampleRate == port->getSampleRate()); | |||||
menu->addChild(item); | menu->addChild(item); | ||||
} | } | ||||
} | } | ||||
void step() override { | void step() override { | ||||
text = (box.size.x >= 100.0) ? "Rate: " : ""; | text = (box.size.x >= 100.0) ? "Rate: " : ""; | ||||
if (port) { | if (port) { | ||||
text += string::f("%g kHz", port->sampleRate / 1000.0); | |||||
text += string::f("%g kHz", port->getSampleRate() / 1000.0); | |||||
} | } | ||||
else { | else { | ||||
text += "0 kHz"; | text += "0 kHz"; | ||||
@@ -168,16 +170,16 @@ struct AudioBlockSizeChoice : LedDisplayChoice { | |||||
AudioBlockSizeItem* item = new AudioBlockSizeItem; | AudioBlockSizeItem* item = new AudioBlockSizeItem; | ||||
item->port = port; | item->port = port; | ||||
item->blockSize = blockSize; | item->blockSize = blockSize; | ||||
float latency = (float) blockSize / port->sampleRate * 1000.0; | |||||
float latency = (float) blockSize / port->getSampleRate() * 1000.0; | |||||
item->text = string::f("%d (%.1f ms)", blockSize, latency); | item->text = string::f("%d (%.1f ms)", blockSize, latency); | ||||
item->rightText = CHECKMARK(item->blockSize == port->blockSize); | |||||
item->rightText = CHECKMARK(item->blockSize == port->getBlockSize()); | |||||
menu->addChild(item); | menu->addChild(item); | ||||
} | } | ||||
} | } | ||||
void step() override { | void step() override { | ||||
text = (box.size.x >= 100.0) ? "Block size: " : ""; | text = (box.size.x >= 100.0) ? "Block size: " : ""; | ||||
if (port) { | if (port) { | ||||
text += string::f("%d", port->blockSize); | |||||
text += string::f("%d", port->getBlockSize()); | |||||
} | } | ||||
else { | else { | ||||
text += "0"; | text += "0"; | ||||
@@ -1,320 +1,166 @@ | |||||
#include <audio.hpp> | #include <audio.hpp> | ||||
#include <string.hpp> | #include <string.hpp> | ||||
#include <math.hpp> | |||||
#include <system.hpp> | |||||
namespace rack { | namespace rack { | ||||
namespace audio { | namespace audio { | ||||
Port::Port() { | |||||
setDriverId(0); | |||||
} | |||||
static std::vector<std::pair<int, Driver*>> drivers; | |||||
Port::~Port() { | |||||
closeStream(); | |||||
} | |||||
std::vector<int> Port::getDriverIds() { | |||||
std::vector<RtAudio::Api> apis; | |||||
RtAudio::getCompiledApi(apis); | |||||
std::vector<int> drivers; | |||||
for (RtAudio::Api api : apis) { | |||||
drivers.push_back((int) api); | |||||
} | |||||
return drivers; | |||||
} | |||||
//////////////////// | |||||
// Device | |||||
//////////////////// | |||||
std::string Port::getDriverName(int driverId) { | |||||
switch (driverId) { | |||||
case 0: return "Default"; | |||||
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 Audio"; | |||||
default: return "Unknown"; | |||||
} | |||||
void Device::subscribe(Port* port) { | |||||
subscribed.insert(port); | |||||
} | } | ||||
void Port::setDriverId(int driverId) { | |||||
// Close device | |||||
setDeviceId(-1, 0); | |||||
void Device::unsubscribe(Port* port) { | |||||
auto it = subscribed.find(port); | |||||
if (it != subscribed.end()) | |||||
subscribed.erase(it); | |||||
} | |||||
// Close driver | |||||
if (rtAudio) { | |||||
delete rtAudio; | |||||
rtAudio = NULL; | |||||
void Device::processBuffer(const float* input, int inputStride, float* output, int outputStride, int frames) { | |||||
for (Port* port : subscribed) { | |||||
port->processInput(input + port->offset, inputStride, frames); | |||||
} | } | ||||
this->driverId = 0; | |||||
// Open driver | |||||
if (driverId >= 0) { | |||||
rtAudio = new RtAudio((RtAudio::Api) driverId); | |||||
this->driverId = (int) rtAudio->getCurrentApi(); | |||||
for (Port* port : subscribed) { | |||||
port->processBuffer(input + port->offset, inputStride, output + port->offset, outputStride, frames); | |||||
} | } | ||||
} | |||||
int Port::getDeviceCount() { | |||||
if (rtAudio) { | |||||
return rtAudio->getDeviceCount(); | |||||
for (Port* port : subscribed) { | |||||
port->processOutput(output + port->offset, outputStride, frames); | |||||
} | } | ||||
return 0; | |||||
} | } | ||||
bool Port::getDeviceInfo(int deviceId, RtAudio::DeviceInfo* deviceInfo) { | |||||
if (!deviceInfo) | |||||
return false; | |||||
if (rtAudio) { | |||||
if (deviceId == this->deviceId) { | |||||
*deviceInfo = this->deviceInfo; | |||||
return true; | |||||
} | |||||
else { | |||||
try { | |||||
*deviceInfo = rtAudio->getDeviceInfo(deviceId); | |||||
return true; | |||||
} | |||||
catch (RtAudioError& e) { | |||||
WARN("Failed to query RtAudio device: %s", e.what()); | |||||
} | |||||
} | |||||
void Device::onOpenStream() { | |||||
for (Port* port : subscribed) { | |||||
port->onOpenStream(); | |||||
} | } | ||||
return false; | |||||
} | } | ||||
int Port::getDeviceChannels(int deviceId) { | |||||
if (deviceId < 0) | |||||
return 0; | |||||
if (rtAudio) { | |||||
RtAudio::DeviceInfo deviceInfo; | |||||
if (getDeviceInfo(deviceId, &deviceInfo)) | |||||
return std::max((int) deviceInfo.inputChannels, (int) deviceInfo.outputChannels); | |||||
void Device::onCloseStream() { | |||||
for (Port* port : subscribed) { | |||||
port->onCloseStream(); | |||||
} | } | ||||
return 0; | |||||
} | } | ||||
std::string Port::getDeviceName(int deviceId) { | |||||
if (deviceId < 0) | |||||
return ""; | |||||
//////////////////// | |||||
// Port | |||||
//////////////////// | |||||
if (rtAudio) { | |||||
RtAudio::DeviceInfo deviceInfo; | |||||
if (getDeviceInfo(deviceId, &deviceInfo)) | |||||
return deviceInfo.name; | |||||
} | |||||
return ""; | |||||
Port::Port() { | |||||
setDriverId(-1); | |||||
} | } | ||||
std::string Port::getDeviceDetail(int deviceId, int offset) { | |||||
if (deviceId < 0) | |||||
return ""; | |||||
Port::~Port() { | |||||
setDriverId(-1); | |||||
} | |||||
if (rtAudio) { | |||||
RtAudio::DeviceInfo deviceInfo; | |||||
if (getDeviceInfo(deviceId, &deviceInfo)) { | |||||
std::string deviceDetail = string::f("%s (", deviceInfo.name.c_str()); | |||||
if (offset < (int) deviceInfo.inputChannels) | |||||
deviceDetail += string::f("%d-%d in", offset + 1, std::min(offset + maxChannels, (int) deviceInfo.inputChannels)); | |||||
if (offset < (int) deviceInfo.inputChannels && offset < (int) deviceInfo.outputChannels) | |||||
deviceDetail += ", "; | |||||
if (offset < (int) deviceInfo.outputChannels) | |||||
deviceDetail += string::f("%d-%d out", offset + 1, std::min(offset + maxChannels, (int) deviceInfo.outputChannels)); | |||||
deviceDetail += ")"; | |||||
return deviceDetail; | |||||
} | |||||
std::vector<int> Port::getDriverIds() { | |||||
std::vector<int> driverIds; | |||||
for (auto& pair : drivers) { | |||||
driverIds.push_back(pair.first); | |||||
} | } | ||||
return ""; | |||||
return driverIds; | |||||
} | } | ||||
void Port::setDeviceId(int deviceId, int offset) { | |||||
closeStream(); | |||||
this->deviceId = deviceId; | |||||
this->offset = offset; | |||||
openStream(); | |||||
} | |||||
void Port::setDriverId(int driverId) { | |||||
// Unset device and driver | |||||
setDeviceId(-1); | |||||
driver = NULL; | |||||
this->driverId = -1; | |||||
std::vector<int> Port::getSampleRates() { | |||||
if (rtAudio) { | |||||
try { | |||||
RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(deviceId); | |||||
std::vector<int> sampleRates(deviceInfo.sampleRates.begin(), deviceInfo.sampleRates.end()); | |||||
return sampleRates; | |||||
if (driverId == -1) { | |||||
// Set first driver as default | |||||
if (!drivers.empty()) { | |||||
driver = drivers[0].second; | |||||
this->driverId = drivers[0].first; | |||||
} | } | ||||
catch (RtAudioError& e) { | |||||
WARN("Failed to query RtAudio device: %s", e.what()); | |||||
} | |||||
else { | |||||
// Set driver with driverId | |||||
for (auto& pair : drivers) { | |||||
if (pair.first == driverId) { | |||||
driver = pair.second; | |||||
this->driverId = driverId; | |||||
break; | |||||
} | |||||
} | } | ||||
} | } | ||||
return {}; | |||||
} | } | ||||
void Port::setSampleRate(int sampleRate) { | |||||
if (sampleRate == this->sampleRate) | |||||
return; | |||||
closeStream(); | |||||
this->sampleRate = sampleRate; | |||||
openStream(); | |||||
} | |||||
std::vector<int> Port::getBlockSizes() { | |||||
if (rtAudio) { | |||||
return {64, 128, 256, 512, 1024, 2048, 4096}; | |||||
std::string Port::getDriverName(int driverId) { | |||||
for (auto& pair : drivers) { | |||||
if (pair.first == driverId) { | |||||
return pair.second->getName(); | |||||
} | |||||
} | } | ||||
return {}; | |||||
} | |||||
void Port::setBlockSize(int blockSize) { | |||||
if (blockSize == this->blockSize) | |||||
return; | |||||
closeStream(); | |||||
this->blockSize = blockSize; | |||||
openStream(); | |||||
} | |||||
void Port::setChannels(int numOutputs, int numInputs) { | |||||
this->numOutputs = numOutputs; | |||||
this->numInputs = numInputs; | |||||
onChannelsChange(); | |||||
return ""; | |||||
} | } | ||||
void Port::setDeviceId(int deviceId) { | |||||
// Destroy device | |||||
if (driver && this->deviceId >= 0) { | |||||
driver->unsubscribe(this->deviceId, this); | |||||
} | |||||
device = NULL; | |||||
this->deviceId = -1; | |||||
static int rtCallback(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void* userData) { | |||||
Port* port = (Port*) userData; | |||||
assert(port); | |||||
// Exploit the stream time to run code on startup of the audio thread | |||||
if (streamTime == 0.0) { | |||||
system::setThreadName("Audio"); | |||||
// system::setThreadRealTime(); | |||||
// Create device | |||||
if (driver && deviceId >= 0) { | |||||
device = driver->subscribe(deviceId, this); | |||||
this->deviceId = deviceId; | |||||
} | } | ||||
port->processStream((const float*) inputBuffer, (float*) outputBuffer, nFrames); | |||||
return 0; | |||||
} | } | ||||
void Port::openStream() { | |||||
if (deviceId < 0) | |||||
return; | |||||
if (rtAudio) { | |||||
// Open new device | |||||
try { | |||||
deviceInfo = rtAudio->getDeviceInfo(deviceId); | |||||
} | |||||
catch (RtAudioError& e) { | |||||
WARN("Failed to query RtAudio device: %s", e.what()); | |||||
return; | |||||
} | |||||
if (rtAudio->isStreamOpen()) | |||||
return; | |||||
setChannels(math::clamp((int) deviceInfo.outputChannels - offset, 0, maxChannels), math::clamp((int) deviceInfo.inputChannels - offset, 0, maxChannels)); | |||||
if (numOutputs == 0 && numInputs == 0) { | |||||
WARN("RtAudio device %d has 0 inputs and 0 outputs", deviceId); | |||||
return; | |||||
} | |||||
RtAudio::StreamParameters outParameters; | |||||
outParameters.deviceId = deviceId; | |||||
outParameters.nChannels = numOutputs; | |||||
outParameters.firstChannel = offset; | |||||
RtAudio::StreamParameters inParameters; | |||||
inParameters.deviceId = deviceId; | |||||
inParameters.nChannels = numInputs; | |||||
inParameters.firstChannel = offset; | |||||
RtAudio::StreamOptions options; | |||||
options.flags |= RTAUDIO_JACK_DONT_CONNECT; | |||||
options.streamName = "VCV Rack"; | |||||
int closestSampleRate = deviceInfo.preferredSampleRate; | |||||
for (int sr : deviceInfo.sampleRates) { | |||||
if (std::abs(sr - sampleRate) < std::abs(closestSampleRate - sampleRate)) { | |||||
closestSampleRate = sr; | |||||
} | |||||
} | |||||
try { | |||||
INFO("Opening audio RtAudio device %d with %d in %d out", deviceId, numInputs, numOutputs); | |||||
rtAudio->openStream( | |||||
numOutputs == 0 ? NULL : &outParameters, | |||||
numInputs == 0 ? NULL : &inParameters, | |||||
RTAUDIO_FLOAT32, closestSampleRate, (unsigned int*) &blockSize, | |||||
&rtCallback, this, &options, NULL); | |||||
} | |||||
catch (RtAudioError& e) { | |||||
WARN("Failed to open RtAudio stream: %s", e.what()); | |||||
return; | |||||
} | |||||
try { | |||||
INFO("Starting RtAudio stream %d", deviceId); | |||||
rtAudio->startStream(); | |||||
} | |||||
catch (RtAudioError& e) { | |||||
WARN("Failed to start RtAudio stream: %s", e.what()); | |||||
return; | |||||
} | |||||
// Update sample rate because this may have changed | |||||
this->sampleRate = rtAudio->getStreamSampleRate(); | |||||
onOpenStream(); | |||||
std::string Port::getDeviceDetail(int deviceId, int offset) { | |||||
if (!driver || !device) | |||||
return ""; | |||||
std::string text = getDeviceName(getDeviceId()); | |||||
text += " ("; | |||||
int numInputs = device->getNumInputs(); | |||||
int numOutputs = device->getNumOutputs(); | |||||
if (offset < numInputs) { | |||||
text += string::f("%d-%d in", offset + 1, std::min(offset + maxChannels, numInputs)); | |||||
} | |||||
if (offset < numInputs && offset < numOutputs) { | |||||
text += ", "; | |||||
} | |||||
if (offset < numOutputs) { | |||||
text += string::f("%d-%d out", offset + 1, std::min(offset + maxChannels, numOutputs)); | |||||
} | } | ||||
text += ")"; | |||||
return text; | |||||
} | } | ||||
void Port::closeStream() { | |||||
setChannels(0, 0); | |||||
if (rtAudio) { | |||||
if (rtAudio->isStreamRunning()) { | |||||
INFO("Stopping RtAudio stream %d", deviceId); | |||||
try { | |||||
rtAudio->stopStream(); | |||||
} | |||||
catch (RtAudioError& e) { | |||||
WARN("Failed to stop RtAudio stream %s", e.what()); | |||||
} | |||||
} | |||||
if (rtAudio->isStreamOpen()) { | |||||
INFO("Closing RtAudio stream %d", deviceId); | |||||
try { | |||||
rtAudio->closeStream(); | |||||
} | |||||
catch (RtAudioError& e) { | |||||
WARN("Failed to close RtAudio stream %s", e.what()); | |||||
} | |||||
} | |||||
deviceInfo = RtAudio::DeviceInfo(); | |||||
} | |||||
int Port::getNumInputs() { | |||||
if (!device) | |||||
return 0; | |||||
return std::min(device->getNumInputs() - offset, maxChannels); | |||||
} | |||||
onCloseStream(); | |||||
int Port::getNumOutputs() { | |||||
if (!device) | |||||
return 0; | |||||
return std::min(device->getNumOutputs() - offset, maxChannels); | |||||
} | } | ||||
json_t* Port::toJson() { | json_t* Port::toJson() { | ||||
json_t* rootJ = json_object(); | json_t* rootJ = json_object(); | ||||
json_object_set_new(rootJ, "driver", json_integer(driverId)); | |||||
std::string deviceName = getDeviceName(deviceId); | |||||
json_object_set_new(rootJ, "driver", json_integer(getDriverId())); | |||||
std::string deviceName = getDeviceName(getDeviceId()); | |||||
if (!deviceName.empty()) | if (!deviceName.empty()) | ||||
json_object_set_new(rootJ, "deviceName", json_string(deviceName.c_str())); | json_object_set_new(rootJ, "deviceName", json_string(deviceName.c_str())); | ||||
json_object_set_new(rootJ, "sampleRate", json_integer(getSampleRate())); | |||||
json_object_set_new(rootJ, "blockSize", json_integer(getBlockSize())); | |||||
json_object_set_new(rootJ, "offset", json_integer(offset)); | json_object_set_new(rootJ, "offset", json_integer(offset)); | ||||
json_object_set_new(rootJ, "maxChannels", json_integer(maxChannels)); | |||||
json_object_set_new(rootJ, "sampleRate", json_integer(sampleRate)); | |||||
json_object_set_new(rootJ, "blockSize", json_integer(blockSize)); | |||||
return rootJ; | return rootJ; | ||||
} | } | ||||
void Port::fromJson(json_t* rootJ) { | void Port::fromJson(json_t* rootJ) { | ||||
closeStream(); | |||||
json_t* driverJ = json_object_get(rootJ, "driver"); | json_t* driverJ = json_object_get(rootJ, "driver"); | ||||
if (driverJ) | if (driverJ) | ||||
setDriverId(json_number_value(driverJ)); | setDriverId(json_number_value(driverJ)); | ||||
@@ -323,31 +169,45 @@ void Port::fromJson(json_t* rootJ) { | |||||
if (deviceNameJ) { | if (deviceNameJ) { | ||||
std::string deviceName = json_string_value(deviceNameJ); | std::string deviceName = json_string_value(deviceNameJ); | ||||
// Search for device ID with equal name | // Search for device ID with equal name | ||||
for (int deviceId = 0; deviceId < getDeviceCount(); deviceId++) { | |||||
for (int deviceId : getDeviceIds()) { | |||||
if (getDeviceName(deviceId) == deviceName) { | if (getDeviceName(deviceId) == deviceName) { | ||||
this->deviceId = deviceId; | |||||
setDeviceId(deviceId); | |||||
break; | break; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
json_t* sampleRateJ = json_object_get(rootJ, "sampleRate"); | |||||
if (sampleRateJ) | |||||
setSampleRate(json_integer_value(sampleRateJ)); | |||||
json_t* blockSizeJ = json_object_get(rootJ, "blockSize"); | |||||
if (blockSizeJ) | |||||
setBlockSize(json_integer_value(blockSizeJ)); | |||||
json_t* offsetJ = json_object_get(rootJ, "offset"); | json_t* offsetJ = json_object_get(rootJ, "offset"); | ||||
if (offsetJ) | if (offsetJ) | ||||
offset = json_integer_value(offsetJ); | offset = json_integer_value(offsetJ); | ||||
} | |||||
json_t* maxChannelsJ = json_object_get(rootJ, "maxChannels"); | |||||
if (maxChannelsJ) | |||||
maxChannels = json_integer_value(maxChannelsJ); | |||||
json_t* sampleRateJ = json_object_get(rootJ, "sampleRate"); | |||||
if (sampleRateJ) | |||||
sampleRate = json_integer_value(sampleRateJ); | |||||
//////////////////// | |||||
// audio | |||||
//////////////////// | |||||
json_t* blockSizeJ = json_object_get(rootJ, "blockSize"); | |||||
if (blockSizeJ) | |||||
blockSize = json_integer_value(blockSizeJ); | |||||
void init() { | |||||
} | |||||
void destroy() { | |||||
for (auto& pair : drivers) { | |||||
delete pair.second; | |||||
} | |||||
drivers.clear(); | |||||
} | |||||
openStream(); | |||||
void addDriver(int driverId, Driver* driver) { | |||||
assert(driver); | |||||
drivers.push_back(std::make_pair(driverId, driver)); | |||||
} | } | ||||
@@ -37,20 +37,32 @@ struct AudioInterface : Module, audio::Port { | |||||
dsp::SampleRateConverter<NUM_AUDIO_INPUTS> inputSrc; | dsp::SampleRateConverter<NUM_AUDIO_INPUTS> inputSrc; | ||||
dsp::SampleRateConverter<NUM_AUDIO_OUTPUTS> outputSrc; | dsp::SampleRateConverter<NUM_AUDIO_OUTPUTS> outputSrc; | ||||
dsp::ClockDivider lightDivider; | |||||
// Port variables | |||||
int requestedEngineFrames = 0; | |||||
int maxEngineFrames = 0; | |||||
AudioInterface() { | AudioInterface() { | ||||
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | ||||
for (int i = 0; i < NUM_AUDIO_INPUTS; i++) | for (int i = 0; i < NUM_AUDIO_INPUTS; i++) | ||||
configInput(AUDIO_INPUTS + i, string::f("To device %d", i + 1)); | configInput(AUDIO_INPUTS + i, string::f("To device %d", i + 1)); | ||||
for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) | for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) | ||||
configOutput(AUDIO_OUTPUTS + i, string::f("From device %d", i + 1)); | configOutput(AUDIO_OUTPUTS + i, string::f("From device %d", i + 1)); | ||||
lightDivider.setDivision(512); | |||||
maxChannels = std::max(NUM_AUDIO_INPUTS, NUM_AUDIO_OUTPUTS); | maxChannels = std::max(NUM_AUDIO_INPUTS, NUM_AUDIO_OUTPUTS); | ||||
inputSrc.setQuality(6); | inputSrc.setQuality(6); | ||||
outputSrc.setQuality(6); | outputSrc.setQuality(6); | ||||
} | } | ||||
~AudioInterface() { | ~AudioInterface() { | ||||
// Close stream here before destructing AudioInterfacePort, so the mutexes are still valid when waiting to close. | |||||
setDeviceId(-1, 0); | |||||
// Close stream here before destructing AudioInterfacePort, so processBuffer() etc are not called on another thread while destructing. | |||||
setDriverId(-1); | |||||
} | |||||
void onReset() override { | |||||
setDriverId(-1); | |||||
} | } | ||||
void onSampleRateChange(const SampleRateChangeEvent& e) override { | void onSampleRateChange(const SampleRateChangeEvent& e) override { | ||||
@@ -59,7 +71,7 @@ struct AudioInterface : Module, audio::Port { | |||||
} | } | ||||
void process(const ProcessArgs& args) override { | void process(const ProcessArgs& args) override { | ||||
// Get inputs | |||||
// Push inputs to buffer | |||||
if (!inputBuffer.full()) { | if (!inputBuffer.full()) { | ||||
dsp::Frame<NUM_AUDIO_INPUTS> inputFrame; | dsp::Frame<NUM_AUDIO_INPUTS> inputFrame; | ||||
for (int i = 0; i < NUM_AUDIO_INPUTS; i++) { | for (int i = 0; i < NUM_AUDIO_INPUTS; i++) { | ||||
@@ -68,7 +80,7 @@ struct AudioInterface : Module, audio::Port { | |||||
inputBuffer.push(inputFrame); | inputBuffer.push(inputFrame); | ||||
} | } | ||||
// Set outputs | |||||
// Pull outputs from buffer | |||||
if (!outputBuffer.empty()) { | if (!outputBuffer.empty()) { | ||||
dsp::Frame<NUM_AUDIO_OUTPUTS> outputFrame = outputBuffer.shift(); | dsp::Frame<NUM_AUDIO_OUTPUTS> outputFrame = outputBuffer.shift(); | ||||
for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) { | for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) { | ||||
@@ -81,12 +93,16 @@ struct AudioInterface : Module, audio::Port { | |||||
} | } | ||||
} | } | ||||
// Turn on light if at least one port is enabled in the nearby pair | |||||
for (int i = 0; i < NUM_AUDIO_INPUTS / 2; i++) { | |||||
lights[INPUT_LIGHTS + i].setBrightness(numOutputs >= 2 * i + 1); | |||||
} | |||||
for (int i = 0; i < NUM_AUDIO_OUTPUTS / 2; i++) { | |||||
lights[OUTPUT_LIGHTS + i].setBrightness(numInputs >= 2 * i + 1); | |||||
if (lightDivider.process()) { | |||||
// Turn on light if at least one port is enabled in the nearby pair | |||||
int numInputs = getNumInputs(); | |||||
int numOutputs = getNumOutputs(); | |||||
for (int i = 0; i < NUM_AUDIO_INPUTS / 2; i++) { | |||||
lights[INPUT_LIGHTS + i].setBrightness(numOutputs >= 2 * i + 1); | |||||
} | |||||
for (int i = 0; i < NUM_AUDIO_OUTPUTS / 2; i++) { | |||||
lights[OUTPUT_LIGHTS + i].setBrightness(numInputs >= 2 * i + 1); | |||||
} | |||||
} | } | ||||
} | } | ||||
@@ -102,46 +118,38 @@ struct AudioInterface : Module, audio::Port { | |||||
audio::Port::fromJson(audioJ); | audio::Port::fromJson(audioJ); | ||||
} | } | ||||
void onReset() override { | |||||
setDeviceId(-1, 0); | |||||
} | |||||
// audio::Port | // audio::Port | ||||
void processStream(const float* input, float* output, int frames) override { | |||||
void processInput(const float* input, int inputStride, int frames) override { | |||||
// Claim primary module if there is none | // Claim primary module if there is none | ||||
if (!APP->engine->getPrimaryModule()) { | if (!APP->engine->getPrimaryModule()) { | ||||
APP->engine->setPrimaryModule(this); | APP->engine->setPrimaryModule(this); | ||||
} | } | ||||
bool isPrimary = (APP->engine->getPrimaryModule() == this); | bool isPrimary = (APP->engine->getPrimaryModule() == this); | ||||
// Clear output in case the audio driver uses this buffer in another thread before this method returns. (Not sure if any do this in practice.) | |||||
std::memset(output, 0, sizeof(float) * numOutputs * frames); | |||||
// Initialize sample rate converters | // Initialize sample rate converters | ||||
int numInputs = getNumInputs(); | |||||
int engineSampleRate = (int) APP->engine->getSampleRate(); | int engineSampleRate = (int) APP->engine->getSampleRate(); | ||||
int sampleRate = getSampleRate(); | |||||
double sampleRateRatio = (double) engineSampleRate / sampleRate; | double sampleRateRatio = (double) engineSampleRate / sampleRate; | ||||
outputSrc.setRates((int) sampleRate, engineSampleRate); | |||||
outputSrc.setRates(sampleRate, engineSampleRate); | |||||
outputSrc.setChannels(numInputs); | outputSrc.setChannels(numInputs); | ||||
inputSrc.setRates(engineSampleRate, (int) sampleRate); | |||||
inputSrc.setChannels(numOutputs); | |||||
// Consider engine buffers "too full" if they contain a bit more than the audio device's number of frames, converted to engine sample rate. | // Consider engine buffers "too full" if they contain a bit more than the audio device's number of frames, converted to engine sample rate. | ||||
int maxEngineFrames = (int) std::ceil(frames * sampleRateRatio * 1.5); | |||||
maxEngineFrames = (int) std::ceil(frames * sampleRateRatio * 1.5); | |||||
// If this is a secondary audio module and the engine output buffer is too full, flush it. | // If this is a secondary audio module and the engine output buffer is too full, flush it. | ||||
if (!isPrimary && (int) outputBuffer.size() > maxEngineFrames) { | if (!isPrimary && (int) outputBuffer.size() > maxEngineFrames) { | ||||
outputBuffer.clear(); | outputBuffer.clear(); | ||||
// DEBUG("%p: flushing engine output", this); | // DEBUG("%p: flushing engine output", this); | ||||
} | } | ||||
int requestedEngineFrames; | |||||
if (numInputs > 0) { | if (numInputs > 0) { | ||||
// audio input -> engine output | // audio input -> engine output | ||||
dsp::Frame<NUM_AUDIO_OUTPUTS> inputAudioBuffer[frames]; | dsp::Frame<NUM_AUDIO_OUTPUTS> inputAudioBuffer[frames]; | ||||
std::memset(inputAudioBuffer, 0, sizeof(inputAudioBuffer)); | std::memset(inputAudioBuffer, 0, sizeof(inputAudioBuffer)); | ||||
for (int i = 0; i < frames; i++) { | for (int i = 0; i < frames; i++) { | ||||
for (int j = 0; j < std::min(numInputs, NUM_AUDIO_OUTPUTS); j++) { | for (int j = 0; j < std::min(numInputs, NUM_AUDIO_OUTPUTS); j++) { | ||||
float v = input[i * numInputs + j]; | |||||
float v = input[i * inputStride + j]; | |||||
inputAudioBuffer[i].samples[j] = v; | inputAudioBuffer[i].samples[j] = v; | ||||
} | } | ||||
} | } | ||||
@@ -156,11 +164,23 @@ struct AudioInterface : Module, audio::Port { | |||||
// Upper bound on number of frames so that `outputAudioFrames >= frames` at the end of this method. | // Upper bound on number of frames so that `outputAudioFrames >= frames` at the end of this method. | ||||
requestedEngineFrames = (int) std::ceil(frames * sampleRateRatio) - inputBuffer.size(); | requestedEngineFrames = (int) std::ceil(frames * sampleRateRatio) - inputBuffer.size(); | ||||
} | } | ||||
} | |||||
void processBuffer(const float* input, int inputStride, float* output, int outputStride, int frames) override { | |||||
bool isPrimary = (APP->engine->getPrimaryModule() == this); | |||||
// Step engine | // Step engine | ||||
if (isPrimary && requestedEngineFrames > 0) { | if (isPrimary && requestedEngineFrames > 0) { | ||||
APP->engine->step(requestedEngineFrames); | APP->engine->step(requestedEngineFrames); | ||||
} | } | ||||
} | |||||
void processOutput(float* output, int outputStride, int frames) override { | |||||
bool isPrimary = (APP->engine->getPrimaryModule() == this); | |||||
int numOutputs = getNumOutputs(); | |||||
int engineSampleRate = (int) APP->engine->getSampleRate(); | |||||
int sampleRate = getSampleRate(); | |||||
inputSrc.setRates(engineSampleRate, sampleRate); | |||||
inputSrc.setChannels(numOutputs); | |||||
if (numOutputs > 0) { | if (numOutputs > 0) { | ||||
// engine input -> audio output | // engine input -> audio output | ||||
@@ -173,7 +193,7 @@ struct AudioInterface : Module, audio::Port { | |||||
for (int j = 0; j < std::min(numOutputs, NUM_AUDIO_INPUTS); j++) { | for (int j = 0; j < std::min(numOutputs, NUM_AUDIO_INPUTS); j++) { | ||||
float v = outputAudioBuffer[i].samples[j]; | float v = outputAudioBuffer[i].samples[j]; | ||||
v = clamp(v, -1.f, 1.f); | v = clamp(v, -1.f, 1.f); | ||||
output[i * numOutputs + j] = v; | |||||
output[i * outputStride + j] = v; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -196,9 +216,6 @@ struct AudioInterface : Module, audio::Port { | |||||
inputBuffer.clear(); | inputBuffer.clear(); | ||||
outputBuffer.clear(); | outputBuffer.clear(); | ||||
} | } | ||||
void onChannelsChange() override { | |||||
} | |||||
}; | }; | ||||
@@ -1,6 +1,8 @@ | |||||
#include <common.hpp> | #include <common.hpp> | ||||
#include <random.hpp> | #include <random.hpp> | ||||
#include <asset.hpp> | #include <asset.hpp> | ||||
#include <audio.hpp> | |||||
#include <rtaudio.hpp> | |||||
#include <midi.hpp> | #include <midi.hpp> | ||||
#include <rtmidi.hpp> | #include <rtmidi.hpp> | ||||
#include <keyboard.hpp> | #include <keyboard.hpp> | ||||
@@ -155,6 +157,8 @@ int main(int argc, char* argv[]) { | |||||
INFO("Initializing environment"); | INFO("Initializing environment"); | ||||
random::init(); | random::init(); | ||||
network::init(); | network::init(); | ||||
audio::init(); | |||||
rtaudioInit(); | |||||
midi::init(); | midi::init(); | ||||
rtmidiInit(); | rtmidiInit(); | ||||
keyboard::init(); | keyboard::init(); | ||||
@@ -215,6 +219,7 @@ int main(int argc, char* argv[]) { | |||||
} | } | ||||
plugin::destroy(); | plugin::destroy(); | ||||
midi::destroy(); | midi::destroy(); | ||||
audio::destroy(); | |||||
INFO("Destroying logger"); | INFO("Destroying logger"); | ||||
logger::destroy(); | logger::destroy(); | ||||
@@ -1,15 +1,14 @@ | |||||
#include <midi.hpp> | #include <midi.hpp> | ||||
#include <string.hpp> | #include <string.hpp> | ||||
#include <map> | #include <map> | ||||
#include <utility> | |||||
namespace rack { | namespace rack { | ||||
namespace midi { | namespace midi { | ||||
/** Preserves the order of IDs */ | |||||
static std::vector<int> driverIds; | |||||
static std::map<int, Driver*> drivers; | |||||
static std::vector<std::pair<int, Driver*>> drivers; | |||||
//////////////////// | //////////////////// | ||||
@@ -41,7 +40,6 @@ void OutputDevice::subscribe(Output* output) { | |||||
} | } | ||||
void OutputDevice::unsubscribe(Output* output) { | void OutputDevice::unsubscribe(Output* output) { | ||||
// Remove Output from subscriptions | |||||
auto it = subscribed.find(output); | auto it = subscribed.find(output); | ||||
if (it != subscribed.end()) | if (it != subscribed.end()) | ||||
subscribed.erase(it); | subscribed.erase(it); | ||||
@@ -52,30 +50,35 @@ void OutputDevice::unsubscribe(Output* output) { | |||||
//////////////////// | //////////////////// | ||||
std::vector<int> Port::getDriverIds() { | std::vector<int> Port::getDriverIds() { | ||||
std::vector<int> driverIds; | |||||
for (auto& pair : drivers) { | |||||
driverIds.push_back(pair.first); | |||||
} | |||||
return driverIds; | return driverIds; | ||||
} | } | ||||
std::string Port::getDriverName(int driverId) { | std::string Port::getDriverName(int driverId) { | ||||
auto it = drivers.find(driverId); | |||||
if (it == drivers.end()) | |||||
return ""; | |||||
return it->second->getName(); | |||||
for (auto& pair : drivers) { | |||||
if (pair.first == driverId) { | |||||
return pair.second->getName(); | |||||
} | |||||
} | |||||
return ""; | |||||
} | } | ||||
void Port::setDriverId(int driverId) { | void Port::setDriverId(int driverId) { | ||||
// Unset device and driver | // Unset device and driver | ||||
setDeviceId(-1); | setDeviceId(-1); | ||||
if (driver) { | |||||
driver = NULL; | |||||
} | |||||
driver = NULL; | |||||
this->driverId = -1; | this->driverId = -1; | ||||
// Set driver | // Set driver | ||||
auto it = drivers.find(driverId); | |||||
if (it != drivers.end()) { | |||||
driver = it->second; | |||||
this->driverId = driverId; | |||||
for (auto& pair : drivers) { | |||||
if (pair.first == driverId) { | |||||
driver = pair.second; | |||||
this->driverId = driverId; | |||||
break; | |||||
} | |||||
} | } | ||||
} | } | ||||
@@ -137,8 +140,8 @@ Input::~Input() { | |||||
void Input::reset() { | void Input::reset() { | ||||
channel = -1; | channel = -1; | ||||
// Set first driver as default | // Set first driver as default | ||||
if (driverIds.size() >= 1) { | |||||
setDriverId(driverIds[0]); | |||||
if (drivers.size() >= 1) { | |||||
setDriverId(drivers[0].first); | |||||
} | } | ||||
} | } | ||||
@@ -160,8 +163,8 @@ void Input::setDeviceId(int deviceId) { | |||||
// Destroy device | // Destroy device | ||||
if (driver && this->deviceId >= 0) { | if (driver && this->deviceId >= 0) { | ||||
driver->unsubscribeInput(this->deviceId, this); | driver->unsubscribeInput(this->deviceId, this); | ||||
inputDevice = NULL; | |||||
} | } | ||||
inputDevice = NULL; | |||||
this->deviceId = -1; | this->deviceId = -1; | ||||
// Create device | // Create device | ||||
@@ -211,8 +214,8 @@ Output::~Output() { | |||||
void Output::reset() { | void Output::reset() { | ||||
channel = 0; | channel = 0; | ||||
// Set first driver as default | // Set first driver as default | ||||
if (driverIds.size() >= 1) { | |||||
setDriverId(driverIds[0]); | |||||
if (drivers.size() >= 1) { | |||||
setDriverId(drivers[0].first); | |||||
} | } | ||||
} | } | ||||
@@ -234,8 +237,8 @@ void Output::setDeviceId(int deviceId) { | |||||
// Destroy device | // Destroy device | ||||
if (driver && this->deviceId >= 0) { | if (driver && this->deviceId >= 0) { | ||||
driver->unsubscribeOutput(this->deviceId, this); | driver->unsubscribeOutput(this->deviceId, this); | ||||
outputDevice = NULL; | |||||
} | } | ||||
outputDevice = NULL; | |||||
this->deviceId = -1; | this->deviceId = -1; | ||||
// Create device | // Create device | ||||
@@ -273,7 +276,6 @@ void init() { | |||||
} | } | ||||
void destroy() { | void destroy() { | ||||
driverIds.clear(); | |||||
for (auto& pair : drivers) { | for (auto& pair : drivers) { | ||||
delete pair.second; | delete pair.second; | ||||
} | } | ||||
@@ -282,8 +284,7 @@ void destroy() { | |||||
void addDriver(int driverId, Driver* driver) { | void addDriver(int driverId, Driver* driver) { | ||||
assert(driver); | assert(driver); | ||||
driverIds.push_back(driverId); | |||||
drivers[driverId] = driver; | |||||
drivers.push_back(std::make_pair(driverId, driver)); | |||||
} | } | ||||
@@ -0,0 +1,242 @@ | |||||
#include <rtaudio.hpp> | |||||
#include <audio.hpp> | |||||
#include <string.hpp> | |||||
#include <math.hpp> | |||||
#include <system.hpp> | |||||
#include <map> | |||||
#pragma GCC diagnostic push | |||||
#ifndef __clang__ | |||||
#pragma GCC diagnostic ignored "-Wsuggest-override" | |||||
#endif | |||||
#include <RtAudio.h> | |||||
#pragma GCC diagnostic pop | |||||
namespace rack { | |||||
struct RtAudioDevice : audio::Device { | |||||
RtAudio *rtAudio; | |||||
int deviceId; | |||||
RtAudio::DeviceInfo deviceInfo; | |||||
RtAudio::StreamParameters inputParameters; | |||||
RtAudio::StreamParameters outputParameters; | |||||
RtAudio::StreamOptions options; | |||||
int blockSize = 256; | |||||
int sampleRate = 44100; | |||||
RtAudioDevice(RtAudio::Api api, int deviceId) { | |||||
rtAudio = new RtAudio(api); | |||||
if (!rtAudio) { | |||||
throw Exception(string::f("Failed to create RtAudio driver %d", api)); | |||||
} | |||||
try { | |||||
deviceInfo = rtAudio->getDeviceInfo(deviceId); | |||||
} | |||||
catch (RtAudioError& e) { | |||||
WARN("Failed to query RtAudio device: %s", e.what()); | |||||
throw Exception(string::f("Failed to query RtAudio device: %s", e.what())); | |||||
} | |||||
this->deviceId = deviceId; | |||||
openStream(); | |||||
} | |||||
~RtAudioDevice() { | |||||
closeStream(); | |||||
delete rtAudio; | |||||
} | |||||
void openStream() { | |||||
// Open new device | |||||
if (deviceInfo.outputChannels == 0 && deviceInfo.inputChannels == 0) { | |||||
WARN("RtAudio device %d has 0 inputs and 0 outputs", deviceId); | |||||
return; | |||||
} | |||||
inputParameters = RtAudio::StreamParameters(); | |||||
inputParameters.deviceId = deviceId; | |||||
inputParameters.nChannels = deviceInfo.inputChannels; | |||||
inputParameters.firstChannel = 0; | |||||
outputParameters = RtAudio::StreamParameters(); | |||||
outputParameters.deviceId = deviceId; | |||||
outputParameters.nChannels = deviceInfo.outputChannels; | |||||
outputParameters.firstChannel = 0; | |||||
options = RtAudio::StreamOptions(); | |||||
options.flags |= RTAUDIO_JACK_DONT_CONNECT; | |||||
options.streamName = "VCV Rack"; | |||||
int closestSampleRate = deviceInfo.preferredSampleRate; | |||||
for (int sr : deviceInfo.sampleRates) { | |||||
if (std::abs(sr - sampleRate) < std::abs(closestSampleRate - sampleRate)) { | |||||
closestSampleRate = sr; | |||||
} | |||||
} | |||||
try { | |||||
INFO("Opening audio RtAudio device %d with %d in %d out", deviceId, inputParameters.nChannels, outputParameters.nChannels); | |||||
rtAudio->openStream( | |||||
outputParameters.nChannels == 0 ? NULL : &outputParameters, | |||||
inputParameters.nChannels == 0 ? NULL : &inputParameters, | |||||
RTAUDIO_FLOAT32, closestSampleRate, (unsigned int*) &blockSize, | |||||
&rtAudioCallback, this, &options, NULL); | |||||
} | |||||
catch (RtAudioError& e) { | |||||
WARN("Failed to open RtAudio stream: %s", e.what()); | |||||
return; | |||||
} | |||||
try { | |||||
INFO("Starting RtAudio stream %d", deviceId); | |||||
rtAudio->startStream(); | |||||
} | |||||
catch (RtAudioError& e) { | |||||
WARN("Failed to start RtAudio stream: %s", e.what()); | |||||
return; | |||||
} | |||||
// Update sample rate to actual value | |||||
sampleRate = rtAudio->getStreamSampleRate(); | |||||
onOpenStream(); | |||||
} | |||||
void closeStream() { | |||||
if (rtAudio->isStreamRunning()) { | |||||
INFO("Stopping RtAudio stream %d", deviceId); | |||||
try { | |||||
rtAudio->stopStream(); | |||||
} | |||||
catch (RtAudioError& e) { | |||||
WARN("Failed to stop RtAudio stream %s", e.what()); | |||||
} | |||||
} | |||||
if (rtAudio->isStreamOpen()) { | |||||
INFO("Closing RtAudio stream %d", deviceId); | |||||
try { | |||||
rtAudio->closeStream(); | |||||
} | |||||
catch (RtAudioError& e) { | |||||
WARN("Failed to close RtAudio stream %s", e.what()); | |||||
} | |||||
} | |||||
onCloseStream(); | |||||
} | |||||
std::vector<int> getSampleRates() override { | |||||
std::vector<int> sampleRates(deviceInfo.sampleRates.begin(), deviceInfo.sampleRates.end()); | |||||
return sampleRates; | |||||
} | |||||
int getSampleRate() override { | |||||
return sampleRate; | |||||
} | |||||
void setSampleRate(int sampleRate) override { | |||||
closeStream(); | |||||
this->sampleRate = sampleRate; | |||||
openStream(); | |||||
} | |||||
std::vector<int> getBlockSizes() override { | |||||
return {32, 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096}; | |||||
} | |||||
int getBlockSize() override { | |||||
return blockSize; | |||||
} | |||||
void setBlockSize(int blockSize) override { | |||||
closeStream(); | |||||
this->blockSize = blockSize; | |||||
openStream(); | |||||
} | |||||
int getNumInputs() override { | |||||
return inputParameters.nChannels; | |||||
} | |||||
int getNumOutputs() override { | |||||
return outputParameters.nChannels; | |||||
} | |||||
static int rtAudioCallback(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void* userData) { | |||||
RtAudioDevice* device = (RtAudioDevice*) userData; | |||||
assert(device); | |||||
int inputStride = device->getNumInputs(); | |||||
int outputStride = device->getNumOutputs(); | |||||
device->processBuffer((const float*) inputBuffer, inputStride, (float*) outputBuffer, outputStride, nFrames); | |||||
return 0; | |||||
} | |||||
}; | |||||
struct RtAudioDriver : audio::Driver { | |||||
// Just for querying device IDs names | |||||
RtAudio *rtAudio; | |||||
// deviceId -> Device | |||||
std::map<int, RtAudioDevice*> devices; | |||||
RtAudioDriver(RtAudio::Api api) { | |||||
rtAudio = new RtAudio(api); | |||||
} | |||||
~RtAudioDriver() { | |||||
assert(devices.empty()); | |||||
delete rtAudio; | |||||
} | |||||
std::string getName() override { | |||||
return RtAudio::getApiDisplayName(rtAudio->getCurrentApi()); | |||||
} | |||||
std::vector<int> getDeviceIds() override { | |||||
int count = rtAudio->getDeviceCount(); | |||||
std::vector<int> deviceIds; | |||||
for (int i = 0; i < count; i++) | |||||
deviceIds.push_back(i); | |||||
return deviceIds; | |||||
} | |||||
std::string getDeviceName(int deviceId) override { | |||||
if (deviceId >= 0) { | |||||
RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(deviceId); | |||||
return deviceInfo.name; | |||||
} | |||||
return ""; | |||||
} | |||||
audio::Device* subscribe(int deviceId, audio::Port* port) override { | |||||
RtAudioDevice* device = devices[deviceId]; | |||||
if (!device) { | |||||
devices[deviceId] = device = new RtAudioDevice(rtAudio->getCurrentApi(), deviceId); | |||||
// TODO Error check | |||||
} | |||||
device->subscribe(port); | |||||
return device; | |||||
} | |||||
void unsubscribe(int deviceId, audio::Port* port) override { | |||||
auto it = devices.find(deviceId); | |||||
if (it == devices.end()) | |||||
return; | |||||
RtAudioDevice* device = it->second; | |||||
device->unsubscribe(port); | |||||
if (device->subscribed.empty()) { | |||||
devices.erase(it); | |||||
delete device; | |||||
} | |||||
} | |||||
}; | |||||
void rtaudioInit() { | |||||
std::vector<RtAudio::Api> apis; | |||||
RtAudio::getCompiledApi(apis); | |||||
for (RtAudio::Api api : apis) { | |||||
RtAudioDriver* driver = new RtAudioDriver(api); | |||||
audio::addDriver((int) api, driver); | |||||
} | |||||
} | |||||
} // namespace rack |
@@ -90,6 +90,8 @@ struct RtMidiDriver : midi::Driver { | |||||
} | } | ||||
~RtMidiDriver() { | ~RtMidiDriver() { | ||||
assert(inputDevices.empty()); | |||||
assert(outputDevices.empty()); | |||||
delete rtMidiIn; | delete rtMidiIn; | ||||
delete rtMidiOut; | delete rtMidiOut; | ||||
} | } | ||||