| @@ -22,7 +22,7 @@ namespace audio { | |||
| struct Device; | |||
| 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 { | |||
| virtual ~Driver() {} | |||
| @@ -70,7 +70,7 @@ struct Driver { | |||
| /** 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. | |||
| */ | |||
| @@ -135,7 +135,7 @@ struct 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. | |||
| 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 { | |||
| /** 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 | |||
| extern const std::string APP_NAME; | |||
| @@ -86,6 +86,8 @@ struct Input; | |||
| struct OutputDevice; | |||
| struct Output; | |||
| /** Wraps a MIDI driver API containing any number of MIDI devices. | |||
| */ | |||
| struct Driver { | |||
| virtual ~Driver() {} | |||
| /** Returns the name of the driver. E.g. "ALSA". */ | |||
| @@ -129,6 +131,12 @@ struct Driver { | |||
| // 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 { | |||
| virtual ~Device() {} | |||
| virtual std::string getName() { | |||
| @@ -160,6 +168,13 @@ struct OutputDevice : 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 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 { | |||
| /** For MIDI output, the channel to automatically set outbound messages. | |||
| If -1, the channel is not overwritten and must be set by MIDI generator. | |||
| @@ -180,28 +195,18 @@ struct Port { | |||
| Port(); | |||
| virtual ~Port(); | |||
| Driver* getDriver() { | |||
| return driver; | |||
| } | |||
| int getDriverId() { | |||
| return driverId; | |||
| } | |||
| Driver* getDriver(); | |||
| int getDriverId(); | |||
| void setDriverId(int driverId); | |||
| Device* getDevice() { | |||
| return device; | |||
| } | |||
| Device* getDevice(); | |||
| virtual std::vector<int> getDeviceIds() = 0; | |||
| int getDeviceId() { | |||
| return deviceId; | |||
| } | |||
| int getDeviceId(); | |||
| virtual void setDeviceId(int deviceId) = 0; | |||
| virtual std::string getDeviceName(int deviceId) = 0; | |||
| virtual std::vector<int> getChannels() = 0; | |||
| int getChannel() { | |||
| return channel; | |||
| } | |||
| int getChannel(); | |||
| void setChannel(int channel); | |||
| std::string getChannelName(int channel); | |||
| @@ -218,17 +223,9 @@ struct Input : Port { | |||
| ~Input(); | |||
| void reset(); | |||
| std::vector<int> getDeviceIds() override { | |||
| if (driver) | |||
| return driver->getInputDeviceIds(); | |||
| return {}; | |||
| } | |||
| std::vector<int> getDeviceIds() 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; | |||
| @@ -251,17 +248,9 @@ struct Output : Port { | |||
| ~Output(); | |||
| void reset(); | |||
| std::vector<int> getDeviceIds() override { | |||
| if (driver) | |||
| return driver->getOutputDeviceIds(); | |||
| return {}; | |||
| } | |||
| std::vector<int> getDeviceIds() 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; | |||
| @@ -106,14 +106,6 @@ static ModuleWidget* chooseModel(plugin::Model* model) { | |||
| 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 | |||
| @@ -520,7 +512,7 @@ struct ModuleBrowser : widget::OpaqueWidget { | |||
| // // Sort by score | |||
| // 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. | |||
| // 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() { | |||
| } | |||
| Driver* Port::getDriver() { | |||
| return driver; | |||
| } | |||
| int Port::getDriverId() { | |||
| return driverId; | |||
| } | |||
| void Port::setDriverId(int driverId) { | |||
| // Unset device and driver | |||
| 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) { | |||
| this->channel = channel; | |||
| } | |||
| @@ -151,18 +171,52 @@ void Input::reset() { | |||
| 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) { | |||
| // Destroy device | |||
| 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; | |||
| this->deviceId = -1; | |||
| // Create device | |||
| 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; | |||
| } | |||
| 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) { | |||
| // Destroy device | |||
| 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; | |||
| this->deviceId = -1; | |||
| // Create device | |||
| 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); | |||
| } | |||
| // 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) { | |||
| // Search for driver by ID | |||
| for (auto& pair : drivers) { | |||
| if (pair.first == driverId) | |||
| return pair.second; | |||
| @@ -14,6 +14,7 @@ | |||
| #include <rtmidi.hpp> | |||
| #include <midi.hpp> | |||
| #include <string.hpp> | |||
| #include <system.hpp> | |||
| @@ -26,11 +27,26 @@ struct RtMidiInputDevice : midi::InputDevice { | |||
| RtMidiInputDevice(int driverId, int deviceId) { | |||
| 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->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() { | |||
| @@ -78,15 +94,29 @@ struct RtMidiOutputDevice : midi::OutputDevice { | |||
| RtMidiOutputDevice(int driverId, int deviceId) : messageQueue(messageEarlier) { | |||
| 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() { | |||
| stop(); | |||
| stopThread(); | |||
| rtMidiOut->closePort(); | |||
| delete rtMidiOut; | |||
| } | |||
| @@ -104,11 +134,11 @@ struct RtMidiOutputDevice : midi::OutputDevice { | |||
| // 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); | |||
| while (!stopped) { | |||
| if (messageQueue.empty()) { | |||
| @@ -141,7 +171,7 @@ struct RtMidiOutputDevice : midi::OutputDevice { | |||
| } | |||
| } | |||
| void stop() { | |||
| void stopThread() { | |||
| { | |||
| std::lock_guard<decltype(mutex)> lock(mutex); | |||
| stopped = true; | |||
| @@ -163,9 +193,14 @@ struct RtMidiDriver : midi::Driver { | |||
| RtMidiDriver(int driverId) { | |||
| this->driverId = 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); | |||
| assert(rtMidiOut); | |||
| if (!rtMidiOut) { | |||
| throw Exception(string::f("Failed to create RtMidi output driver %d", driverId)); | |||
| } | |||
| } | |||
| ~RtMidiDriver() { | |||
| @@ -188,7 +223,14 @@ struct RtMidiDriver : midi::Driver { | |||
| } | |||
| 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? | |||
| 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; | |||
| for (int i = 0; i < count; i++) | |||
| deviceIds.push_back(i); | |||
| @@ -196,18 +238,27 @@ struct RtMidiDriver : midi::Driver { | |||
| } | |||
| std::string getInputDeviceName(int deviceId) override { | |||
| if (deviceId >= 0) { | |||
| if (deviceId < 0) | |||
| return ""; | |||
| try { | |||
| 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 { | |||
| if (!(0 <= deviceId && deviceId < (int) rtMidiIn->getPortCount())) | |||
| return NULL; | |||
| RtMidiInputDevice* device = inputDevices[deviceId]; | |||
| RtMidiInputDevice* device = getWithDefault(inputDevices, deviceId, NULL); | |||
| 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); | |||
| @@ -224,12 +275,25 @@ struct RtMidiDriver : midi::Driver { | |||
| // Destroy device if nothing is subscribed anymore | |||
| if (device->subscribed.empty()) { | |||
| 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 { | |||
| 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; | |||
| for (int i = 0; i < count; i++) | |||
| deviceIds.push_back(i); | |||
| @@ -237,18 +301,27 @@ struct RtMidiDriver : midi::Driver { | |||
| } | |||
| std::string getOutputDeviceName(int deviceId) override { | |||
| if (deviceId >= 0) { | |||
| if (deviceId < 0) | |||
| return ""; | |||
| try { | |||
| 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 { | |||
| if (!(0 <= deviceId && deviceId < (int) rtMidiOut->getPortCount())) | |||
| return NULL; | |||
| RtMidiOutputDevice* device = outputDevices[deviceId]; | |||
| RtMidiOutputDevice* device = getWithDefault(outputDevices, deviceId, NULL); | |||
| 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); | |||
| @@ -265,7 +338,12 @@ struct RtMidiDriver : midi::Driver { | |||
| // Destroy device if nothing is subscribed anymore | |||
| if (device->subscribed.empty()) { | |||
| outputDevices.erase(it); | |||
| delete device; | |||
| try { | |||
| delete device; | |||
| } | |||
| catch (RtMidiError& e) { | |||
| throw Exception(string::f("Failed to delete RtMidi output device: %s", e.what())); | |||
| } | |||
| } | |||
| } | |||
| }; | |||