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