@@ -207,7 +207,7 @@ $(rtmidi): | rtmidi-4.0.0 | |||
$(MAKE) -C rtmidi-4.0.0 | |||
$(MAKE) -C rtmidi-4.0.0 install | |||
RTAUDIO_FLAGS += -DRTAUDIO_BUILD_STATIC_LIBS=ON | |||
RTAUDIO_FLAGS += -DRTAUDIO_BUILD_STATIC_LIBS=ON -DRTAUDIO_BUILD_TESTING=OFF | |||
ifdef ARCH_LIN | |||
RTAUDIO_FLAGS += -DRTAUDIO_API_ALSA=ON -DRTAUDIO_API_JACK=ON -DRTAUDIO_API_PULSE=OFF -DRTAUDIO_API_OSS=OFF | |||
endif | |||
@@ -1 +1 @@ | |||
Subproject commit 07b1c6228fec77a207afd5d5cccc36681e90d319 | |||
Subproject commit e9915064859381a7a616b088dadbaee81bc0e0df |
@@ -22,30 +22,45 @@ namespace audio { | |||
struct Device; | |||
struct Port; | |||
/** An audio driver API containing any number of audio devices. | |||
*/ | |||
struct Driver { | |||
virtual ~Driver() {} | |||
/** Returns the name of the driver. E.g. "ALSA". */ | |||
virtual std::string getName() { | |||
return ""; | |||
} | |||
/** Returns a list of all device IDs that can be subscribed to. */ | |||
virtual std::vector<int> getDeviceIds() { | |||
return {}; | |||
} | |||
/** Gets the name of a device without subscribing to it. */ | |||
/** Returns the name of a device without obtaining it. */ | |||
virtual std::string getDeviceName(int deviceId) { | |||
return ""; | |||
} | |||
/** Returns the number of inputs of a device without obtaining it. */ | |||
virtual int getDeviceNumInputs(int deviceId) { | |||
return 0; | |||
} | |||
/** Returns the number of output of a device without obtaining it. */ | |||
virtual int getDeviceNumOutputs(int deviceId) { | |||
return 0; | |||
} | |||
/** Returns a detailed description of the device without obtaining it. | |||
`offset` specifies the first channel (zero-indexed). | |||
E.g. "MySoundcard (1-2 in, 1-2 out)" | |||
*/ | |||
std::string getDeviceDetail(int deviceId, int offset, int maxChannels); | |||
/** Adds the given port as a reference holder of a device and returns the it. | |||
Creates the Device if no ports are subscribed before calling. | |||
*/ | |||
virtual Device* subscribe(int deviceId, Port* port) { | |||
return NULL; | |||
} | |||
/** Removes the give port as a reference holder of a device. | |||
Deletes the Device if no ports are subscribed after calling. | |||
*/ | |||
virtual void unsubscribe(int deviceId, Port* port) {} | |||
}; | |||
@@ -53,6 +68,12 @@ struct Driver { | |||
// Device | |||
//////////////////// | |||
/** A single audio device of a driver API. | |||
Modules should | |||
Methods throw `rack::Exception` if the driver API has an exception. | |||
*/ | |||
struct Device { | |||
std::set<Port*> subscribed; | |||
virtual ~Device() {} | |||
@@ -69,22 +90,36 @@ struct Device { | |||
virtual int getNumOutputs() { | |||
return 0; | |||
} | |||
/** Returns a detailed description of the device. | |||
`offset` specifies the first channel (zero-indexed). | |||
E.g. "MySoundcard (1-2 in, 1-2 out)" | |||
*/ | |||
std::string getDetail(int offset, int maxChannels); | |||
/** Returns a list of all valid (user-selectable) sample rates. | |||
The device may accept sample rates not in this list, but it *must* accept sample rates in the list. | |||
*/ | |||
virtual std::vector<int> getSampleRates() { | |||
return {}; | |||
} | |||
/** Returns the current sample rate. */ | |||
virtual int getSampleRate() { | |||
return 0; | |||
} | |||
/** Sets the sample rate of the device, re-opening it if needed. */ | |||
virtual void setSampleRate(int sampleRate) {} | |||
/** Returns a list of all valid (user-selectable) block sizes. | |||
The device may accept block sizes not in this list, but it *must* accept block sizes in the list. | |||
*/ | |||
virtual std::vector<int> getBlockSizes() { | |||
return {}; | |||
} | |||
/** Returns the current block size. */ | |||
virtual int getBlockSize() { | |||
return 0; | |||
} | |||
/** Sets the block size of the device, re-opening it if needed. */ | |||
virtual void setBlockSize(int blockSize) {} | |||
// Called by this Device class, forwards to subscribed Ports. | |||
@@ -97,6 +132,11 @@ struct Device { | |||
// Port | |||
//////////////////// | |||
/** A handle to a Device, typically owned by modules to have shared access to a single Device. | |||
All Port methods safely wrap Drivers methods. | |||
That is, if the active Device thrown a `rack::Exception`, it is caught and logged inside all Port methods, so you can consider them nothrow. | |||
*/ | |||
struct Port { | |||
/** The first channel index of the device to process. */ | |||
int offset = 0; | |||
@@ -115,51 +155,27 @@ struct Port { | |||
virtual ~Port(); | |||
void reset(); | |||
Driver* getDriver() { | |||
return driver; | |||
} | |||
int getDriverId() { | |||
return driverId; | |||
} | |||
Driver* getDriver(); | |||
int getDriverId(); | |||
void setDriverId(int driverId); | |||
std::string getDriverName(); | |||
Device* getDevice() { | |||
return device; | |||
} | |||
int getDeviceId() { | |||
return deviceId; | |||
} | |||
Device* getDevice(); | |||
std::vector<int> getDeviceIds(); | |||
int getDeviceId(); | |||
void setDeviceId(int deviceId); | |||
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 getDeviceNumInputs(int deviceId); | |||
int getDeviceNumOutputs(int deviceId); | |||
std::string getDeviceName(int deviceId); | |||
std::string getDeviceDetail(int deviceId, int offset); | |||
std::vector<int> getSampleRates(); | |||
int getSampleRate(); | |||
void setSampleRate(int sampleRate); | |||
std::vector<int> getBlockSizes(); | |||
int getBlockSize(); | |||
void setBlockSize(int blockSize); | |||
int getNumInputs(); | |||
int getNumOutputs(); | |||
@@ -88,20 +88,31 @@ struct Output; | |||
struct Driver { | |||
virtual ~Driver() {} | |||
/** Returns the name of the driver. E.g. "ALSA". */ | |||
virtual std::string getName() { | |||
return ""; | |||
} | |||
/** Returns a list of all input device IDs that can be subscribed to. */ | |||
virtual std::vector<int> getInputDeviceIds() { | |||
return {}; | |||
} | |||
/** Returns the name of an input device without obtaining it. */ | |||
virtual std::string getInputDeviceName(int deviceId) { | |||
return ""; | |||
} | |||
/** Adds the given port as a reference holder of a device and returns the it. | |||
Creates the Device if no ports are subscribed before calling. | |||
*/ | |||
virtual InputDevice* subscribeInput(int deviceId, Input* input) { | |||
return NULL; | |||
} | |||
/** Removes the give port as a reference holder of a device. | |||
Deletes the Device if no ports are subscribed after calling. | |||
*/ | |||
virtual void unsubscribeInput(int deviceId, Input* input) {} | |||
// The following behave identically as the above methods except for outputs. | |||
virtual std::vector<int> getOutputDeviceIds() { | |||
return {}; | |||
} | |||
@@ -127,15 +138,21 @@ struct Device { | |||
struct InputDevice : Device { | |||
std::set<Input*> subscribed; | |||
/** Not public. Use Driver::subscribeInput(). */ | |||
void subscribe(Input* input); | |||
/** Not public. Use Driver::unsubscribeInput(). */ | |||
void unsubscribe(Input* input); | |||
/** Called when a MIDI message is received from the device. */ | |||
void onMessage(const Message &message); | |||
}; | |||
struct OutputDevice : Device { | |||
std::set<Output*> subscribed; | |||
void subscribe(Output* input); | |||
void unsubscribe(Output* input); | |||
/** Not public. Use Driver::subscribeOutput(). */ | |||
void subscribe(Output* output); | |||
/** Not public. Use Driver::unsubscribeOutput(). */ | |||
void unsubscribe(Output* output); | |||
/** Sends a MIDI message to the device. */ | |||
virtual void sendMessage(const Message &message) {} | |||
}; | |||
@@ -40,7 +40,8 @@ struct AudioDriverChoice : LedDisplayChoice { | |||
text = ""; | |||
if (box.size.x >= 200.0) | |||
text += "Driver: "; | |||
std::string driverName = (port && port->driver) ? port->getDriver()->getName() : ""; | |||
audio::Driver* driver = port ? port->getDriver() : NULL; | |||
std::string driverName = driver ? driver->getName() : ""; | |||
if (driverName != "") { | |||
text += driverName; | |||
color.a = 1.0; | |||
@@ -73,7 +74,7 @@ struct AudioDeviceValueItem : ui::MenuItem { | |||
}; | |||
static void appendAudioDeviceMenu(ui::Menu* menu, audio::Port* port) { | |||
if (!port || !port->driver) | |||
if (!port) | |||
return; | |||
{ | |||
@@ -85,8 +86,8 @@ static void appendAudioDeviceMenu(ui::Menu* menu, audio::Port* port) { | |||
menu->addChild(item); | |||
} | |||
for (int deviceId : port->driver->getDeviceIds()) { | |||
int channels = std::max(port->driver->getDeviceNumInputs(deviceId), port->driver->getDeviceNumOutputs(deviceId)); | |||
for (int deviceId : port->getDeviceIds()) { | |||
int channels = std::max(port->getDeviceNumInputs(deviceId), port->getDeviceNumOutputs(deviceId)); | |||
// Prevents devices with a ridiculous number of channels from being displayed | |||
const int maxTotalChannels = port->maxChannels * 16; | |||
channels = std::min(maxTotalChannels, channels); | |||
@@ -96,7 +97,7 @@ static void appendAudioDeviceMenu(ui::Menu* menu, audio::Port* port) { | |||
item->port = port; | |||
item->deviceId = deviceId; | |||
item->offset = offset; | |||
item->text = port->driver->getDeviceDetail(deviceId, offset, port->maxChannels); | |||
item->text = port->getDeviceDetail(deviceId, offset); | |||
item->rightText = CHECKMARK(item->deviceId == port->getDeviceId() && item->offset == port->offset); | |||
menu->addChild(item); | |||
} | |||
@@ -96,11 +96,27 @@ Port::~Port() { | |||
} | |||
void Port::reset() { | |||
setDriverId(-1); | |||
// Get default driver | |||
int firstDriverId = -1; | |||
std::vector<int> driverIds = getDriverIds(); | |||
if (!driverIds.empty()) | |||
firstDriverId = driverIds[0]; | |||
setDriverId(firstDriverId); | |||
offset = 0; | |||
} | |||
Driver* Port::getDriver() { | |||
return driver; | |||
} | |||
int Port::getDriverId() { | |||
return driverId; | |||
} | |||
void Port::setDriverId(int driverId) { | |||
if (driverId == this->driverId) | |||
return; | |||
// Unset device and driver | |||
setDeviceId(-1); | |||
driver = NULL; | |||
@@ -118,31 +134,206 @@ void Port::setDriverId(int driverId) { | |||
} | |||
} | |||
std::string Port::getDriverName() { | |||
if (!driver) | |||
return ""; | |||
try { | |||
return driver->getName(); | |||
} | |||
catch (Exception& e) { | |||
WARN("Audio port could not get driver name: %s", e.what()); | |||
return ""; | |||
} | |||
} | |||
Device* Port::getDevice() { | |||
return device; | |||
} | |||
std::vector<int> Port::getDeviceIds() { | |||
if (!driver) | |||
return {}; | |||
try { | |||
return driver->getDeviceIds(); | |||
} | |||
catch (Exception& e) { | |||
WARN("Audio port could not get device IDs: %s", e.what()); | |||
return {}; | |||
} | |||
} | |||
int Port::getDeviceId() { | |||
return deviceId; | |||
} | |||
void Port::setDeviceId(int deviceId) { | |||
if (deviceId == this->deviceId) | |||
return; | |||
// Destroy device | |||
if (driver && this->deviceId >= 0) { | |||
driver->unsubscribe(this->deviceId, this); | |||
try { | |||
driver->unsubscribe(this->deviceId, this); | |||
} | |||
catch (Exception& e) { | |||
WARN("Audio port could not unsubscribe from device: %s", e.what()); | |||
} | |||
} | |||
device = NULL; | |||
this->deviceId = -1; | |||
// Create device | |||
if (driver && deviceId >= 0) { | |||
device = driver->subscribe(deviceId, this); | |||
this->deviceId = deviceId; | |||
try { | |||
device = driver->subscribe(deviceId, this); | |||
this->deviceId = deviceId; | |||
} | |||
catch (Exception& e) { | |||
WARN("Audio port could not subscribe to device: %s", e.what()); | |||
} | |||
} | |||
} | |||
int Port::getDeviceNumInputs(int deviceId) { | |||
if (!driver) | |||
return 0; | |||
try { | |||
return driver->getDeviceNumInputs(deviceId); | |||
} | |||
catch (Exception& e) { | |||
WARN("Audio port could not get device number of inputs: %s", e.what()); | |||
return 0; | |||
} | |||
} | |||
int Port::getDeviceNumOutputs(int deviceId) { | |||
if (!driver) | |||
return 0; | |||
try { | |||
return driver->getDeviceNumOutputs(deviceId); | |||
} | |||
catch (Exception& e) { | |||
WARN("Audio port could not get device number of outputs: %s", e.what()); | |||
return 0; | |||
} | |||
} | |||
std::string Port::getDeviceName(int deviceId) { | |||
if (!driver) | |||
return ""; | |||
try { | |||
return driver->getDeviceName(deviceId); | |||
} | |||
catch (Exception& e) { | |||
WARN("Audio port could not get device name: %s", e.what()); | |||
return 0; | |||
} | |||
} | |||
std::string Port::getDeviceDetail(int deviceId, int offset) { | |||
if (!driver) | |||
return ""; | |||
try { | |||
// Use maxChannels from Port. | |||
return driver->getDeviceDetail(deviceId, offset, maxChannels); | |||
} | |||
catch (Exception& e) { | |||
WARN("Audio port could not get device detail: %s", e.what()); | |||
return 0; | |||
} | |||
} | |||
std::vector<int> Port::getSampleRates() { | |||
if (!device) | |||
return {}; | |||
try { | |||
return device->getSampleRates(); | |||
} | |||
catch (Exception& e) { | |||
WARN("Audio port could not get device sample rates: %s", e.what()); | |||
return {}; | |||
} | |||
} | |||
int Port::getSampleRate() { | |||
if (!device) | |||
return 0; | |||
try { | |||
return device->getSampleRate(); | |||
} | |||
catch (Exception& e) { | |||
WARN("Audio port could not get device sample rate: %s", e.what()); | |||
return 0; | |||
} | |||
} | |||
void Port::setSampleRate(int sampleRate) { | |||
if (!device) | |||
return; | |||
try { | |||
device->setSampleRate(sampleRate); | |||
} | |||
catch (Exception& e) { | |||
WARN("Audio port could not set device sample rate: %s", e.what()); | |||
} | |||
} | |||
std::vector<int> Port::getBlockSizes() { | |||
if (!device) | |||
return {}; | |||
try { | |||
return device->getBlockSizes(); | |||
} | |||
catch (Exception& e) { | |||
WARN("Audio port could not get device block sizes: %s", e.what()); | |||
return {}; | |||
} | |||
} | |||
int Port::getBlockSize() { | |||
if (!device) | |||
return 0; | |||
try { | |||
return device->getBlockSize(); | |||
} | |||
catch (Exception& e) { | |||
WARN("Audio port could not get device block size: %s", e.what()); | |||
return 0; | |||
} | |||
} | |||
void Port::setBlockSize(int blockSize) { | |||
if (!device) | |||
return; | |||
try { | |||
device->setBlockSize(blockSize); | |||
} | |||
catch (Exception& e) { | |||
WARN("Audio port could not set device block size: %s", e.what()); | |||
} | |||
} | |||
int Port::getNumInputs() { | |||
if (!device) | |||
return 0; | |||
return std::min(device->getNumInputs() - offset, maxChannels); | |||
try { | |||
return std::min(device->getNumInputs() - offset, maxChannels); | |||
} | |||
catch (Exception& e) { | |||
WARN("Audio port could not get device number of inputs: %s", e.what()); | |||
return 0; | |||
} | |||
} | |||
int Port::getNumOutputs() { | |||
if (!device) | |||
return 0; | |||
return std::min(device->getNumOutputs() - offset, maxChannels); | |||
try { | |||
return std::min(device->getNumOutputs() - offset, maxChannels); | |||
} | |||
catch (Exception& e) { | |||
WARN("Audio port could not get device number of outputs: %s", e.what()); | |||
return 0; | |||
} | |||
} | |||
json_t* Port::toJson() { | |||
@@ -172,8 +363,11 @@ 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 : driver->getDeviceIds()) { | |||
if (driver->getDeviceName(deviceId) == deviceName) { | |||
for (int deviceId : getDeviceIds()) { | |||
std::string deviceNameCurr = getDeviceName(deviceId); | |||
if (deviceNameCurr == "") | |||
continue; | |||
if (deviceNameCurr == deviceName) { | |||
setDeviceId(deviceId); | |||
break; | |||
} | |||
@@ -194,7 +388,6 @@ void Port::fromJson(json_t* rootJ) { | |||
offset = json_integer_value(offsetJ); | |||
} | |||
//////////////////// | |||
// audio | |||
//////////////////// | |||
@@ -223,6 +416,7 @@ std::vector<int> getDriverIds() { | |||
} | |||
Driver* getDriver(int driverId) { | |||
// Search for driver by ID | |||
for (auto& pair : drivers) { | |||
if (pair.first == driverId) | |||
return pair.second; | |||
@@ -39,7 +39,6 @@ struct RtAudioDevice : audio::Device { | |||
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())); | |||
} | |||
@@ -55,8 +54,7 @@ struct RtAudioDevice : audio::Device { | |||
void openStream() { | |||
// Open new device | |||
if (deviceInfo.outputChannels == 0 && deviceInfo.inputChannels == 0) { | |||
WARN("RtAudio device %d has 0 inputs and 0 outputs", deviceId); | |||
return; | |||
throw Exception(string::f("RtAudio device %d has 0 inputs and 0 outputs", deviceId)); | |||
} | |||
inputParameters = RtAudio::StreamParameters(); | |||
@@ -98,8 +96,7 @@ struct RtAudioDevice : audio::Device { | |||
&rtAudioCallback, this, &options, NULL); | |||
} | |||
catch (RtAudioError& e) { | |||
WARN("Failed to open RtAudio stream: %s", e.what()); | |||
return; | |||
throw Exception(string::f("Failed to open RtAudio stream: %s", e.what())); | |||
} | |||
INFO("Starting RtAudio stream %d", deviceId); | |||
@@ -107,8 +104,7 @@ struct RtAudioDevice : audio::Device { | |||
rtAudio->startStream(); | |||
} | |||
catch (RtAudioError& e) { | |||
WARN("Failed to start RtAudio stream: %s", e.what()); | |||
return; | |||
throw Exception(string::f("Failed to start RtAudio stream: %s", e.what())); | |||
} | |||
// Update sample rate to actual value | |||
@@ -124,7 +120,7 @@ struct RtAudioDevice : audio::Device { | |||
rtAudio->stopStream(); | |||
} | |||
catch (RtAudioError& e) { | |||
WARN("Failed to stop RtAudio stream %s", e.what()); | |||
throw Exception(string::f("Failed to stop RtAudio stream %s", e.what())); | |||
} | |||
} | |||
if (rtAudio->isStreamOpen()) { | |||
@@ -133,7 +129,7 @@ struct RtAudioDevice : audio::Device { | |||
rtAudio->closeStream(); | |||
} | |||
catch (RtAudioError& e) { | |||
WARN("Failed to close RtAudio stream %s", e.what()); | |||
throw Exception(string::f("Failed to close RtAudio stream %s", e.what())); | |||
} | |||
} | |||
INFO("Closed RtAudio stream"); | |||