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