@@ -1,14 +1,8 @@ | |||
#pragma once | |||
#include <common.hpp> | |||
#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 { | |||
@@ -18,54 +12,173 @@ namespace rack { | |||
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 { | |||
// Stream properties | |||
int driverId = 0; | |||
int deviceId = -1; | |||
/** Not owned */ | |||
Driver* driver = NULL; | |||
Device* device = NULL; | |||
// Port settings | |||
int offset = 0; | |||
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(); | |||
virtual ~Port(); | |||
std::vector<int> getDriverIds(); | |||
std::string getDriverName(int driverId); | |||
int getDriverId() { | |||
return 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); | |||
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(); | |||
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 rack |
@@ -62,7 +62,6 @@ struct Driver { | |||
virtual std::string getName() { | |||
return ""; | |||
} | |||
virtual std::vector<int> getInputDeviceIds() { | |||
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->driverId = driverId; | |||
item->text = port->getDriverName(driverId); | |||
item->rightText = CHECKMARK(item->driverId == port->driverId); | |||
item->rightText = CHECKMARK(item->driverId == port->getDriverId()); | |||
menu->addChild(item); | |||
} | |||
} | |||
void step() override { | |||
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 != "") { | |||
text += driverName; | |||
color.a = 1.f; | |||
@@ -51,14 +51,13 @@ struct AudioDeviceItem : ui::MenuItem { | |||
int deviceId; | |||
int offset; | |||
void onAction(const event::Action& e) override { | |||
port->setDeviceId(deviceId, offset); | |||
port->setDeviceId(deviceId); | |||
port->offset = offset; | |||
} | |||
}; | |||
struct AudioDeviceChoice : LedDisplayChoice { | |||
audio::Port* port; | |||
/** Prevents devices with a ridiculous number of channels from being displayed */ | |||
int maxTotalChannels = 128; | |||
void onAction(const event::Action& e) override { | |||
if (!port) | |||
@@ -66,24 +65,27 @@ struct AudioDeviceChoice : LedDisplayChoice { | |||
ui::Menu* menu = createMenu(); | |||
menu->addChild(createMenuLabel("Audio device")); | |||
int deviceCount = port->getDeviceCount(); | |||
{ | |||
AudioDeviceItem* item = new AudioDeviceItem; | |||
item->port = port; | |||
item->deviceId = -1; | |||
item->text = "(No device)"; | |||
item->rightText = CHECKMARK(item->deviceId == port->deviceId); | |||
item->rightText = CHECKMARK(item->deviceId == port->getDeviceId()); | |||
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) { | |||
AudioDeviceItem* item = new AudioDeviceItem; | |||
item->port = port; | |||
item->deviceId = deviceId; | |||
item->offset = 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); | |||
} | |||
} | |||
@@ -128,14 +130,14 @@ struct AudioSampleRateChoice : LedDisplayChoice { | |||
item->port = port; | |||
item->sampleRate = sampleRate; | |||
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); | |||
} | |||
} | |||
void step() override { | |||
text = (box.size.x >= 100.0) ? "Rate: " : ""; | |||
if (port) { | |||
text += string::f("%g kHz", port->sampleRate / 1000.0); | |||
text += string::f("%g kHz", port->getSampleRate() / 1000.0); | |||
} | |||
else { | |||
text += "0 kHz"; | |||
@@ -168,16 +170,16 @@ struct AudioBlockSizeChoice : LedDisplayChoice { | |||
AudioBlockSizeItem* item = new AudioBlockSizeItem; | |||
item->port = port; | |||
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->rightText = CHECKMARK(item->blockSize == port->blockSize); | |||
item->rightText = CHECKMARK(item->blockSize == port->getBlockSize()); | |||
menu->addChild(item); | |||
} | |||
} | |||
void step() override { | |||
text = (box.size.x >= 100.0) ? "Block size: " : ""; | |||
if (port) { | |||
text += string::f("%d", port->blockSize); | |||
text += string::f("%d", port->getBlockSize()); | |||
} | |||
else { | |||
text += "0"; | |||
@@ -1,320 +1,166 @@ | |||
#include <audio.hpp> | |||
#include <string.hpp> | |||
#include <math.hpp> | |||
#include <system.hpp> | |||
namespace rack { | |||
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* 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()) | |||
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, "maxChannels", json_integer(maxChannels)); | |||
json_object_set_new(rootJ, "sampleRate", json_integer(sampleRate)); | |||
json_object_set_new(rootJ, "blockSize", json_integer(blockSize)); | |||
return rootJ; | |||
} | |||
void Port::fromJson(json_t* rootJ) { | |||
closeStream(); | |||
json_t* driverJ = json_object_get(rootJ, "driver"); | |||
if (driverJ) | |||
setDriverId(json_number_value(driverJ)); | |||
@@ -323,31 +169,45 @@ void Port::fromJson(json_t* rootJ) { | |||
if (deviceNameJ) { | |||
std::string deviceName = json_string_value(deviceNameJ); | |||
// Search for device ID with equal name | |||
for (int deviceId = 0; deviceId < getDeviceCount(); deviceId++) { | |||
for (int deviceId : getDeviceIds()) { | |||
if (getDeviceName(deviceId) == deviceName) { | |||
this->deviceId = deviceId; | |||
setDeviceId(deviceId); | |||
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"); | |||
if (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_OUTPUTS> outputSrc; | |||
dsp::ClockDivider lightDivider; | |||
// Port variables | |||
int requestedEngineFrames = 0; | |||
int maxEngineFrames = 0; | |||
AudioInterface() { | |||
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
for (int i = 0; i < NUM_AUDIO_INPUTS; i++) | |||
configInput(AUDIO_INPUTS + i, string::f("To device %d", i + 1)); | |||
for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) | |||
configOutput(AUDIO_OUTPUTS + i, string::f("From device %d", i + 1)); | |||
lightDivider.setDivision(512); | |||
maxChannels = std::max(NUM_AUDIO_INPUTS, NUM_AUDIO_OUTPUTS); | |||
inputSrc.setQuality(6); | |||
outputSrc.setQuality(6); | |||
} | |||
~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 { | |||
@@ -59,7 +71,7 @@ struct AudioInterface : Module, audio::Port { | |||
} | |||
void process(const ProcessArgs& args) override { | |||
// Get inputs | |||
// Push inputs to buffer | |||
if (!inputBuffer.full()) { | |||
dsp::Frame<NUM_AUDIO_INPUTS> inputFrame; | |||
for (int i = 0; i < NUM_AUDIO_INPUTS; i++) { | |||
@@ -68,7 +80,7 @@ struct AudioInterface : Module, audio::Port { | |||
inputBuffer.push(inputFrame); | |||
} | |||
// Set outputs | |||
// Pull outputs from buffer | |||
if (!outputBuffer.empty()) { | |||
dsp::Frame<NUM_AUDIO_OUTPUTS> outputFrame = outputBuffer.shift(); | |||
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); | |||
} | |||
void onReset() override { | |||
setDeviceId(-1, 0); | |||
} | |||
// 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 | |||
if (!APP->engine->getPrimaryModule()) { | |||
APP->engine->setPrimaryModule(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 | |||
int numInputs = getNumInputs(); | |||
int engineSampleRate = (int) APP->engine->getSampleRate(); | |||
int sampleRate = getSampleRate(); | |||
double sampleRateRatio = (double) engineSampleRate / sampleRate; | |||
outputSrc.setRates((int) sampleRate, engineSampleRate); | |||
outputSrc.setRates(sampleRate, engineSampleRate); | |||
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. | |||
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 (!isPrimary && (int) outputBuffer.size() > maxEngineFrames) { | |||
outputBuffer.clear(); | |||
// DEBUG("%p: flushing engine output", this); | |||
} | |||
int requestedEngineFrames; | |||
if (numInputs > 0) { | |||
// audio input -> engine output | |||
dsp::Frame<NUM_AUDIO_OUTPUTS> inputAudioBuffer[frames]; | |||
std::memset(inputAudioBuffer, 0, sizeof(inputAudioBuffer)); | |||
for (int i = 0; i < frames; i++) { | |||
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; | |||
} | |||
} | |||
@@ -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. | |||
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 | |||
if (isPrimary && requestedEngineFrames > 0) { | |||
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) { | |||
// 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++) { | |||
float v = outputAudioBuffer[i].samples[j]; | |||
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(); | |||
outputBuffer.clear(); | |||
} | |||
void onChannelsChange() override { | |||
} | |||
}; | |||
@@ -1,6 +1,8 @@ | |||
#include <common.hpp> | |||
#include <random.hpp> | |||
#include <asset.hpp> | |||
#include <audio.hpp> | |||
#include <rtaudio.hpp> | |||
#include <midi.hpp> | |||
#include <rtmidi.hpp> | |||
#include <keyboard.hpp> | |||
@@ -155,6 +157,8 @@ int main(int argc, char* argv[]) { | |||
INFO("Initializing environment"); | |||
random::init(); | |||
network::init(); | |||
audio::init(); | |||
rtaudioInit(); | |||
midi::init(); | |||
rtmidiInit(); | |||
keyboard::init(); | |||
@@ -215,6 +219,7 @@ int main(int argc, char* argv[]) { | |||
} | |||
plugin::destroy(); | |||
midi::destroy(); | |||
audio::destroy(); | |||
INFO("Destroying logger"); | |||
logger::destroy(); | |||
@@ -1,15 +1,14 @@ | |||
#include <midi.hpp> | |||
#include <string.hpp> | |||
#include <map> | |||
#include <utility> | |||
namespace rack { | |||
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) { | |||
// Remove Output from subscriptions | |||
auto it = subscribed.find(output); | |||
if (it != subscribed.end()) | |||
subscribed.erase(it); | |||
@@ -52,30 +50,35 @@ void OutputDevice::unsubscribe(Output* output) { | |||
//////////////////// | |||
std::vector<int> Port::getDriverIds() { | |||
std::vector<int> driverIds; | |||
for (auto& pair : drivers) { | |||
driverIds.push_back(pair.first); | |||
} | |||
return driverIds; | |||
} | |||
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) { | |||
// Unset device and driver | |||
setDeviceId(-1); | |||
if (driver) { | |||
driver = NULL; | |||
} | |||
driver = NULL; | |||
this->driverId = -1; | |||
// 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() { | |||
channel = -1; | |||
// 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 | |||
if (driver && this->deviceId >= 0) { | |||
driver->unsubscribeInput(this->deviceId, this); | |||
inputDevice = NULL; | |||
} | |||
inputDevice = NULL; | |||
this->deviceId = -1; | |||
// Create device | |||
@@ -211,8 +214,8 @@ Output::~Output() { | |||
void Output::reset() { | |||
channel = 0; | |||
// 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 | |||
if (driver && this->deviceId >= 0) { | |||
driver->unsubscribeOutput(this->deviceId, this); | |||
outputDevice = NULL; | |||
} | |||
outputDevice = NULL; | |||
this->deviceId = -1; | |||
// Create device | |||
@@ -273,7 +276,6 @@ void init() { | |||
} | |||
void destroy() { | |||
driverIds.clear(); | |||
for (auto& pair : drivers) { | |||
delete pair.second; | |||
} | |||
@@ -282,8 +284,7 @@ void destroy() { | |||
void addDriver(int driverId, Driver* 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() { | |||
assert(inputDevices.empty()); | |||
assert(outputDevices.empty()); | |||
delete rtMidiIn; | |||
delete rtMidiOut; | |||
} | |||