@@ -22,7 +22,7 @@ namespace audio { | |||||
struct Device; | struct Device; | ||||
struct Port; | struct Port; | ||||
/** An audio driver API containing any number of audio devices. | |||||
/** Wraps an audio driver API containing any number of audio devices. | |||||
*/ | */ | ||||
struct Driver { | struct Driver { | ||||
virtual ~Driver() {} | virtual ~Driver() {} | ||||
@@ -70,7 +70,7 @@ struct Driver { | |||||
/** A single audio device of a driver API. | /** A single audio device of a driver API. | ||||
Modules should | |||||
Modules and the UI should not interact with this API directly. Use Port instead. | |||||
Methods throw `rack::Exception` if the driver API has an exception. | Methods throw `rack::Exception` if the driver API has an exception. | ||||
*/ | */ | ||||
@@ -135,7 +135,7 @@ struct Device { | |||||
/** A handle to a Device, typically owned by modules to have shared access to a single Device. | /** A handle to a Device, typically owned by modules to have shared access to a single Device. | ||||
All Port methods safely wrap Drivers methods. | 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. | |||||
That is, if the active Device throws a `rack::Exception`, it is caught and logged inside all Port methods, so they do not throw exceptions. | |||||
*/ | */ | ||||
struct Port { | struct Port { | ||||
/** The first channel index of the device to process. */ | /** The first channel index of the device to process. */ | ||||
@@ -163,6 +163,27 @@ struct Exception : std::runtime_error { | |||||
}; | }; | ||||
/** Given a std::map, returns the value of the given key, or returns `def` if the key doesn't exist. | |||||
Does *not* add the default value to the map. | |||||
Posted to https://stackoverflow.com/a/63683271/272642. | |||||
Example: | |||||
std::map<std::string, int*> m; | |||||
int v = getWithDefault(m, "a", 3); | |||||
// v is 3 because the key "a" does not exist | |||||
int w = getWithDefault(m, "a"); | |||||
// w is 0 because no default value is given, so it assumes the default int. | |||||
*/ | |||||
template <typename C> | |||||
typename C::mapped_type getWithDefault(const C& m, const typename C::key_type& key, const typename C::mapped_type& def = typename C::mapped_type()) { | |||||
typename C::const_iterator it = m.find(key); | |||||
if (it == m.end()) | |||||
return def; | |||||
return it->second; | |||||
} | |||||
// config | // config | ||||
extern const std::string APP_NAME; | extern const std::string APP_NAME; | ||||
@@ -86,6 +86,8 @@ struct Input; | |||||
struct OutputDevice; | struct OutputDevice; | ||||
struct Output; | struct Output; | ||||
/** Wraps a MIDI driver API containing any number of MIDI devices. | |||||
*/ | |||||
struct Driver { | struct Driver { | ||||
virtual ~Driver() {} | virtual ~Driver() {} | ||||
/** Returns the name of the driver. E.g. "ALSA". */ | /** Returns the name of the driver. E.g. "ALSA". */ | ||||
@@ -129,6 +131,12 @@ struct Driver { | |||||
// Device | // Device | ||||
//////////////////// | //////////////////// | ||||
/** A single MIDI device of a driver API. | |||||
Modules and the UI should not interact with this API directly. Use Port instead. | |||||
Methods throw `rack::Exception` if the driver API has an exception. | |||||
*/ | |||||
struct Device { | struct Device { | ||||
virtual ~Device() {} | virtual ~Device() {} | ||||
virtual std::string getName() { | virtual std::string getName() { | ||||
@@ -160,6 +168,13 @@ struct OutputDevice : Device { | |||||
// Port | // 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 throws a `rack::Exception`, it is caught and logged inside all Port methods, so they do not throw exceptions. | |||||
Use Input or Output subclasses in your module, not Port directly. | |||||
*/ | |||||
struct Port { | struct Port { | ||||
/** For MIDI output, the channel to automatically set outbound messages. | /** For MIDI output, the channel to automatically set outbound messages. | ||||
If -1, the channel is not overwritten and must be set by MIDI generator. | If -1, the channel is not overwritten and must be set by MIDI generator. | ||||
@@ -180,28 +195,18 @@ struct Port { | |||||
Port(); | Port(); | ||||
virtual ~Port(); | virtual ~Port(); | ||||
Driver* getDriver() { | |||||
return driver; | |||||
} | |||||
int getDriverId() { | |||||
return driverId; | |||||
} | |||||
Driver* getDriver(); | |||||
int getDriverId(); | |||||
void setDriverId(int driverId); | void setDriverId(int driverId); | ||||
Device* getDevice() { | |||||
return device; | |||||
} | |||||
Device* getDevice(); | |||||
virtual std::vector<int> getDeviceIds() = 0; | virtual std::vector<int> getDeviceIds() = 0; | ||||
int getDeviceId() { | |||||
return deviceId; | |||||
} | |||||
int getDeviceId(); | |||||
virtual void setDeviceId(int deviceId) = 0; | virtual void setDeviceId(int deviceId) = 0; | ||||
virtual std::string getDeviceName(int deviceId) = 0; | virtual std::string getDeviceName(int deviceId) = 0; | ||||
virtual std::vector<int> getChannels() = 0; | virtual std::vector<int> getChannels() = 0; | ||||
int getChannel() { | |||||
return channel; | |||||
} | |||||
int getChannel(); | |||||
void setChannel(int channel); | void setChannel(int channel); | ||||
std::string getChannelName(int channel); | std::string getChannelName(int channel); | ||||
@@ -218,17 +223,9 @@ struct Input : Port { | |||||
~Input(); | ~Input(); | ||||
void reset(); | void reset(); | ||||
std::vector<int> getDeviceIds() override { | |||||
if (driver) | |||||
return driver->getInputDeviceIds(); | |||||
return {}; | |||||
} | |||||
std::vector<int> getDeviceIds() override; | |||||
void setDeviceId(int deviceId) override; | void setDeviceId(int deviceId) override; | ||||
std::string getDeviceName(int deviceId) override { | |||||
if (driver) | |||||
return driver->getInputDeviceName(deviceId); | |||||
return ""; | |||||
} | |||||
std::string getDeviceName(int deviceId) override; | |||||
std::vector<int> getChannels() override; | std::vector<int> getChannels() override; | ||||
@@ -251,17 +248,9 @@ struct Output : Port { | |||||
~Output(); | ~Output(); | ||||
void reset(); | void reset(); | ||||
std::vector<int> getDeviceIds() override { | |||||
if (driver) | |||||
return driver->getOutputDeviceIds(); | |||||
return {}; | |||||
} | |||||
std::vector<int> getDeviceIds() override; | |||||
void setDeviceId(int deviceId) override; | void setDeviceId(int deviceId) override; | ||||
std::string getDeviceName(int deviceId) override { | |||||
if (driver) | |||||
return driver->getInputDeviceName(deviceId); | |||||
return ""; | |||||
} | |||||
std::string getDeviceName(int deviceId) override; | |||||
std::vector<int> getChannels() override; | std::vector<int> getChannels() override; | ||||
@@ -106,14 +106,6 @@ static ModuleWidget* chooseModel(plugin::Model* model) { | |||||
return moduleWidget; | return moduleWidget; | ||||
} | } | ||||
template <typename K, typename V> | |||||
V get_default(const std::map<K, V>& m, const K& key, const V& def) { | |||||
auto it = m.find(key); | |||||
if (it == m.end()) | |||||
return def; | |||||
return it->second; | |||||
} | |||||
// Widgets | // Widgets | ||||
@@ -520,7 +512,7 @@ struct ModuleBrowser : widget::OpaqueWidget { | |||||
// // Sort by score | // // Sort by score | ||||
// modelContainer->children.sort([&](Widget *w1, Widget *w2) { | // modelContainer->children.sort([&](Widget *w1, Widget *w2) { | ||||
// // If score was not computed, scores[w] returns 0, but this doesn't matter because those widgets aren't visible. | // // If score was not computed, scores[w] returns 0, but this doesn't matter because those widgets aren't visible. | ||||
// return get_default(scores, w1, 0.f) > get_default(scores, w2, 0.f); | |||||
// return getWithDefault(scores, w1, 0.f) > getWithDefault(scores, w2, 0.f); | |||||
// }); | // }); | ||||
} | } | ||||
@@ -65,6 +65,14 @@ Port::Port() { | |||||
Port::~Port() { | Port::~Port() { | ||||
} | } | ||||
Driver* Port::getDriver() { | |||||
return driver; | |||||
} | |||||
int Port::getDriverId() { | |||||
return driverId; | |||||
} | |||||
void Port::setDriverId(int driverId) { | void Port::setDriverId(int driverId) { | ||||
// Unset device and driver | // Unset device and driver | ||||
setDeviceId(-1); | setDeviceId(-1); | ||||
@@ -83,6 +91,18 @@ void Port::setDriverId(int driverId) { | |||||
} | } | ||||
} | } | ||||
Device* Port::getDevice() { | |||||
return device; | |||||
} | |||||
int Port::getDeviceId() { | |||||
return deviceId; | |||||
} | |||||
int Port::getChannel() { | |||||
return channel; | |||||
} | |||||
void Port::setChannel(int channel) { | void Port::setChannel(int channel) { | ||||
this->channel = channel; | this->channel = channel; | ||||
} | } | ||||
@@ -151,18 +171,52 @@ void Input::reset() { | |||||
channel = -1; | channel = -1; | ||||
} | } | ||||
std::vector<int> Input::getDeviceIds() { | |||||
if (!driver) | |||||
return {}; | |||||
try { | |||||
return driver->getInputDeviceIds(); | |||||
} | |||||
catch (Exception& e) { | |||||
WARN("MIDI port could not get input device IDs: %s", e.what()); | |||||
return {}; | |||||
} | |||||
} | |||||
void Input::setDeviceId(int deviceId) { | void Input::setDeviceId(int deviceId) { | ||||
// Destroy device | // Destroy device | ||||
if (driver && this->deviceId >= 0) { | if (driver && this->deviceId >= 0) { | ||||
driver->unsubscribeInput(this->deviceId, this); | |||||
try { | |||||
driver->unsubscribeInput(this->deviceId, this); | |||||
} | |||||
catch (Exception& e) { | |||||
WARN("MIDI port could not unsubscribe from input: %s", e.what()); | |||||
} | |||||
} | } | ||||
device = inputDevice = NULL; | device = inputDevice = NULL; | ||||
this->deviceId = -1; | this->deviceId = -1; | ||||
// Create device | // Create device | ||||
if (driver && deviceId >= 0) { | if (driver && deviceId >= 0) { | ||||
device = inputDevice = driver->subscribeInput(deviceId, this); | |||||
this->deviceId = deviceId; | |||||
try { | |||||
device = inputDevice = driver->subscribeInput(deviceId, this); | |||||
this->deviceId = deviceId; | |||||
} | |||||
catch (Exception& e) { | |||||
WARN("MIDI port could not subscribe to input: %s", e.what()); | |||||
} | |||||
} | |||||
} | |||||
std::string Input::getDeviceName(int deviceId) { | |||||
if (!driver) | |||||
return ""; | |||||
try { | |||||
return driver->getInputDeviceName(deviceId); | |||||
} | |||||
catch (Exception& e) { | |||||
WARN("MIDI port could not get input device name: %s", e.what()); | |||||
return ""; | |||||
} | } | ||||
} | } | ||||
@@ -198,18 +252,52 @@ void Output::reset() { | |||||
channel = 0; | channel = 0; | ||||
} | } | ||||
std::vector<int> Output::getDeviceIds() { | |||||
if (!driver) | |||||
return {}; | |||||
try { | |||||
return driver->getOutputDeviceIds(); | |||||
} | |||||
catch (Exception& e) { | |||||
WARN("MIDI port could not get output device IDs: %s", e.what()); | |||||
return {}; | |||||
} | |||||
} | |||||
void Output::setDeviceId(int deviceId) { | void Output::setDeviceId(int deviceId) { | ||||
// Destroy device | // Destroy device | ||||
if (driver && this->deviceId >= 0) { | if (driver && this->deviceId >= 0) { | ||||
driver->unsubscribeOutput(this->deviceId, this); | |||||
try { | |||||
driver->unsubscribeOutput(this->deviceId, this); | |||||
} | |||||
catch (Exception& e) { | |||||
WARN("MIDI port could not unsubscribe from output: %s", e.what()); | |||||
} | |||||
} | } | ||||
device = outputDevice = NULL; | device = outputDevice = NULL; | ||||
this->deviceId = -1; | this->deviceId = -1; | ||||
// Create device | // Create device | ||||
if (driver && deviceId >= 0) { | if (driver && deviceId >= 0) { | ||||
device = outputDevice = driver->subscribeOutput(deviceId, this); | |||||
this->deviceId = deviceId; | |||||
try { | |||||
device = outputDevice = driver->subscribeOutput(deviceId, this); | |||||
this->deviceId = deviceId; | |||||
} | |||||
catch (Exception& e) { | |||||
WARN("MIDI port could not subscribe to output: %s", e.what()); | |||||
} | |||||
} | |||||
} | |||||
std::string Output::getDeviceName(int deviceId) { | |||||
if (driver) | |||||
return ""; | |||||
try { | |||||
return driver->getOutputDeviceName(deviceId); | |||||
} | |||||
catch (Exception& e) { | |||||
WARN("MIDI port could not get output device name: %s", e.what()); | |||||
return ""; | |||||
} | } | ||||
} | } | ||||
@@ -231,7 +319,14 @@ void Output::sendMessage(const Message &message) { | |||||
msg.setChannel(channel); | msg.setChannel(channel); | ||||
} | } | ||||
// DEBUG("sendMessage %02x %02x %02x", msg.cmd, msg.data1, msg.data2); | // DEBUG("sendMessage %02x %02x %02x", msg.cmd, msg.data1, msg.data2); | ||||
outputDevice->sendMessage(msg); | |||||
try { | |||||
outputDevice->sendMessage(msg); | |||||
} | |||||
catch (Exception& e) { | |||||
// Don't log error because it could flood the log. | |||||
// WARN("MIDI port could not be sent MIDI message: %s", e.what()); | |||||
// TODO Perhaps `setDevice(-1)` if sending message fails? | |||||
} | |||||
} | } | ||||
@@ -263,6 +358,7 @@ std::vector<int> getDriverIds() { | |||||
} | } | ||||
Driver* getDriver(int driverId) { | Driver* getDriver(int driverId) { | ||||
// Search for driver by ID | |||||
for (auto& pair : drivers) { | for (auto& pair : drivers) { | ||||
if (pair.first == driverId) | if (pair.first == driverId) | ||||
return pair.second; | return pair.second; | ||||
@@ -14,6 +14,7 @@ | |||||
#include <rtmidi.hpp> | #include <rtmidi.hpp> | ||||
#include <midi.hpp> | #include <midi.hpp> | ||||
#include <string.hpp> | |||||
#include <system.hpp> | #include <system.hpp> | ||||
@@ -26,11 +27,26 @@ struct RtMidiInputDevice : midi::InputDevice { | |||||
RtMidiInputDevice(int driverId, int deviceId) { | RtMidiInputDevice(int driverId, int deviceId) { | ||||
rtMidiIn = new RtMidiIn((RtMidi::Api) driverId, "VCV Rack"); | rtMidiIn = new RtMidiIn((RtMidi::Api) driverId, "VCV Rack"); | ||||
assert(rtMidiIn); | |||||
if (!rtMidiIn) { | |||||
throw Exception(string::f("Failed to create RtMidi input driver %d", driverId)); | |||||
} | |||||
rtMidiIn->ignoreTypes(false, false, false); | rtMidiIn->ignoreTypes(false, false, false); | ||||
rtMidiIn->setCallback(midiInputCallback, this); | rtMidiIn->setCallback(midiInputCallback, this); | ||||
name = rtMidiIn->getPortName(deviceId); | |||||
rtMidiIn->openPort(deviceId, "VCV Rack input"); | |||||
try { | |||||
name = rtMidiIn->getPortName(deviceId); | |||||
} | |||||
catch (RtMidiError& e) { | |||||
throw Exception(string::f("Failed to get RtMidi input device name: %s", e.what())); | |||||
} | |||||
try { | |||||
rtMidiIn->openPort(deviceId, "VCV Rack input"); | |||||
} | |||||
catch (RtMidiError& e) { | |||||
throw Exception(string::f("Failed to open RtMidi input device: %s", e.what())); | |||||
} | |||||
} | } | ||||
~RtMidiInputDevice() { | ~RtMidiInputDevice() { | ||||
@@ -78,15 +94,29 @@ struct RtMidiOutputDevice : midi::OutputDevice { | |||||
RtMidiOutputDevice(int driverId, int deviceId) : messageQueue(messageEarlier) { | RtMidiOutputDevice(int driverId, int deviceId) : messageQueue(messageEarlier) { | ||||
rtMidiOut = new RtMidiOut((RtMidi::Api) driverId, "VCV Rack"); | rtMidiOut = new RtMidiOut((RtMidi::Api) driverId, "VCV Rack"); | ||||
assert(rtMidiOut); | |||||
name = rtMidiOut->getPortName(deviceId); | |||||
rtMidiOut->openPort(deviceId, "VCV Rack output"); | |||||
if (!rtMidiOut) { | |||||
throw Exception(string::f("Failed to create RtMidi output driver %d", driverId)); | |||||
} | |||||
start(); | |||||
try { | |||||
name = rtMidiOut->getPortName(deviceId); | |||||
} | |||||
catch (RtMidiError& e) { | |||||
throw Exception(string::f("Failed to get RtMidi output device name: %s", e.what())); | |||||
} | |||||
try { | |||||
rtMidiOut->openPort(deviceId, "VCV Rack output"); | |||||
} | |||||
catch (RtMidiError& e) { | |||||
throw Exception(string::f("Failed to get RtMidi output device name: %s", e.what())); | |||||
} | |||||
startThread(); | |||||
} | } | ||||
~RtMidiOutputDevice() { | ~RtMidiOutputDevice() { | ||||
stop(); | |||||
stopThread(); | |||||
rtMidiOut->closePort(); | rtMidiOut->closePort(); | ||||
delete rtMidiOut; | delete rtMidiOut; | ||||
} | } | ||||
@@ -104,11 +134,11 @@ struct RtMidiOutputDevice : midi::OutputDevice { | |||||
// Consumer thread methods | // Consumer thread methods | ||||
void start() { | |||||
thread = std::thread(&RtMidiOutputDevice::run, this); | |||||
void startThread() { | |||||
thread = std::thread(&RtMidiOutputDevice::runThread, this); | |||||
} | } | ||||
void run() { | |||||
void runThread() { | |||||
std::unique_lock<decltype(mutex)> lock(mutex); | std::unique_lock<decltype(mutex)> lock(mutex); | ||||
while (!stopped) { | while (!stopped) { | ||||
if (messageQueue.empty()) { | if (messageQueue.empty()) { | ||||
@@ -141,7 +171,7 @@ struct RtMidiOutputDevice : midi::OutputDevice { | |||||
} | } | ||||
} | } | ||||
void stop() { | |||||
void stopThread() { | |||||
{ | { | ||||
std::lock_guard<decltype(mutex)> lock(mutex); | std::lock_guard<decltype(mutex)> lock(mutex); | ||||
stopped = true; | stopped = true; | ||||
@@ -163,9 +193,14 @@ struct RtMidiDriver : midi::Driver { | |||||
RtMidiDriver(int driverId) { | RtMidiDriver(int driverId) { | ||||
this->driverId = driverId; | this->driverId = driverId; | ||||
rtMidiIn = new RtMidiIn((RtMidi::Api) driverId); | rtMidiIn = new RtMidiIn((RtMidi::Api) driverId); | ||||
assert(rtMidiIn); | |||||
if (!rtMidiIn) { | |||||
throw Exception(string::f("Failed to create RtMidi input driver %d", driverId)); | |||||
} | |||||
rtMidiOut = new RtMidiOut((RtMidi::Api) driverId); | rtMidiOut = new RtMidiOut((RtMidi::Api) driverId); | ||||
assert(rtMidiOut); | |||||
if (!rtMidiOut) { | |||||
throw Exception(string::f("Failed to create RtMidi output driver %d", driverId)); | |||||
} | |||||
} | } | ||||
~RtMidiDriver() { | ~RtMidiDriver() { | ||||
@@ -188,7 +223,14 @@ struct RtMidiDriver : midi::Driver { | |||||
} | } | ||||
std::vector<int> getInputDeviceIds() override { | std::vector<int> getInputDeviceIds() override { | ||||
// TODO The IDs unfortunately jump around in RtMidi. Is there a way to keep them constant when a MIDI device is added/removed? | // TODO The IDs unfortunately jump around in RtMidi. Is there a way to keep them constant when a MIDI device is added/removed? | ||||
int count = rtMidiIn->getPortCount(); | |||||
int count; | |||||
try { | |||||
count = rtMidiIn->getPortCount(); | |||||
} | |||||
catch (RtMidiError& e) { | |||||
throw Exception(string::f("Failed to get RtMidi input device count: %s", e.what())); | |||||
} | |||||
std::vector<int> deviceIds; | std::vector<int> deviceIds; | ||||
for (int i = 0; i < count; i++) | for (int i = 0; i < count; i++) | ||||
deviceIds.push_back(i); | deviceIds.push_back(i); | ||||
@@ -196,18 +238,27 @@ struct RtMidiDriver : midi::Driver { | |||||
} | } | ||||
std::string getInputDeviceName(int deviceId) override { | std::string getInputDeviceName(int deviceId) override { | ||||
if (deviceId >= 0) { | |||||
if (deviceId < 0) | |||||
return ""; | |||||
try { | |||||
return rtMidiIn->getPortName(deviceId); | return rtMidiIn->getPortName(deviceId); | ||||
} | } | ||||
return ""; | |||||
catch (RtMidiError& e) { | |||||
throw Exception(string::f("Failed to get RtMidi input device name: %s", e.what())); | |||||
} | |||||
} | } | ||||
midi::InputDevice* subscribeInput(int deviceId, midi::Input* input) override { | midi::InputDevice* subscribeInput(int deviceId, midi::Input* input) override { | ||||
if (!(0 <= deviceId && deviceId < (int) rtMidiIn->getPortCount())) | if (!(0 <= deviceId && deviceId < (int) rtMidiIn->getPortCount())) | ||||
return NULL; | return NULL; | ||||
RtMidiInputDevice* device = inputDevices[deviceId]; | |||||
RtMidiInputDevice* device = getWithDefault(inputDevices, deviceId, NULL); | |||||
if (!device) { | if (!device) { | ||||
inputDevices[deviceId] = device = new RtMidiInputDevice(driverId, deviceId); | |||||
try { | |||||
inputDevices[deviceId] = device = new RtMidiInputDevice(driverId, deviceId); | |||||
} | |||||
catch (RtMidiError& e) { | |||||
throw Exception(string::f("Failed to create RtMidi input device: %s", e.what())); | |||||
} | |||||
} | } | ||||
device->subscribe(input); | device->subscribe(input); | ||||
@@ -224,12 +275,25 @@ struct RtMidiDriver : midi::Driver { | |||||
// Destroy device if nothing is subscribed anymore | // Destroy device if nothing is subscribed anymore | ||||
if (device->subscribed.empty()) { | if (device->subscribed.empty()) { | ||||
inputDevices.erase(it); | inputDevices.erase(it); | ||||
delete device; | |||||
try { | |||||
delete device; | |||||
} | |||||
catch (RtMidiError& e) { | |||||
throw Exception(string::f("Failed to delete RtMidi input device: %s", e.what())); | |||||
} | |||||
} | } | ||||
} | } | ||||
std::vector<int> getOutputDeviceIds() override { | std::vector<int> getOutputDeviceIds() override { | ||||
int count = rtMidiOut->getPortCount(); | |||||
// TODO The IDs unfortunately jump around in RtMidi. Is there a way to keep them constant when a MIDI device is added/removed? | |||||
int count; | |||||
try { | |||||
count = rtMidiOut->getPortCount(); | |||||
} | |||||
catch (RtMidiError& e) { | |||||
throw Exception(string::f("Failed to get RtMidi output device count: %s", e.what())); | |||||
} | |||||
std::vector<int> deviceIds; | std::vector<int> deviceIds; | ||||
for (int i = 0; i < count; i++) | for (int i = 0; i < count; i++) | ||||
deviceIds.push_back(i); | deviceIds.push_back(i); | ||||
@@ -237,18 +301,27 @@ struct RtMidiDriver : midi::Driver { | |||||
} | } | ||||
std::string getOutputDeviceName(int deviceId) override { | std::string getOutputDeviceName(int deviceId) override { | ||||
if (deviceId >= 0) { | |||||
if (deviceId < 0) | |||||
return ""; | |||||
try { | |||||
return rtMidiOut->getPortName(deviceId); | return rtMidiOut->getPortName(deviceId); | ||||
} | } | ||||
return ""; | |||||
catch (RtMidiError& e) { | |||||
throw Exception(string::f("Failed to get RtMidi output device count: %s", e.what())); | |||||
} | |||||
} | } | ||||
midi::OutputDevice* subscribeOutput(int deviceId, midi::Output* output) override { | midi::OutputDevice* subscribeOutput(int deviceId, midi::Output* output) override { | ||||
if (!(0 <= deviceId && deviceId < (int) rtMidiOut->getPortCount())) | if (!(0 <= deviceId && deviceId < (int) rtMidiOut->getPortCount())) | ||||
return NULL; | return NULL; | ||||
RtMidiOutputDevice* device = outputDevices[deviceId]; | |||||
RtMidiOutputDevice* device = getWithDefault(outputDevices, deviceId, NULL); | |||||
if (!device) { | if (!device) { | ||||
outputDevices[deviceId] = device = new RtMidiOutputDevice(driverId, deviceId); | |||||
try { | |||||
outputDevices[deviceId] = device = new RtMidiOutputDevice(driverId, deviceId); | |||||
} | |||||
catch (RtMidiError& e) { | |||||
throw Exception(string::f("Failed to create RtMidi output device: %s", e.what())); | |||||
} | |||||
} | } | ||||
device->subscribe(output); | device->subscribe(output); | ||||
@@ -265,7 +338,12 @@ struct RtMidiDriver : midi::Driver { | |||||
// Destroy device if nothing is subscribed anymore | // Destroy device if nothing is subscribed anymore | ||||
if (device->subscribed.empty()) { | if (device->subscribed.empty()) { | ||||
outputDevices.erase(it); | outputDevices.erase(it); | ||||
delete device; | |||||
try { | |||||
delete device; | |||||
} | |||||
catch (RtMidiError& e) { | |||||
throw Exception(string::f("Failed to delete RtMidi output device: %s", e.what())); | |||||
} | |||||
} | } | ||||
} | } | ||||
}; | }; | ||||