Browse Source

Add `rack::Exception` catching to midi. Add exception throwing to RtMidi driver. Add `rack::getWithDefault()` to common.hpp.

tags/v2.0.0
Andrew Belt 4 years ago
parent
commit
76ece72ade
6 changed files with 256 additions and 80 deletions
  1. +3
    -3
      include/audio.hpp
  2. +21
    -0
      include/common.hpp
  3. +24
    -35
      include/midi.hpp
  4. +1
    -9
      src/app/ModuleBrowser.cpp
  5. +103
    -7
      src/midi.cpp
  6. +104
    -26
      src/rtmidi.cpp

+ 3
- 3
include/audio.hpp View File

@@ -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. */


+ 21
- 0
include/common.hpp View File

@@ -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;


+ 24
- 35
include/midi.hpp View File

@@ -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;



+ 1
- 9
src/app/ModuleBrowser.cpp View File

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



+ 103
- 7
src/midi.cpp View File

@@ -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;


+ 104
- 26
src/rtmidi.cpp View File

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


Loading…
Cancel
Save