Browse Source

Add exception handling to audio::Port and RtAudioDevice. Add more Port methods that wrap Device/Driver methods.

tags/v2.0.0
Andrew Belt 4 years ago
parent
commit
c140186885
7 changed files with 295 additions and 71 deletions
  1. +1
    -1
      dep/Makefile
  2. +1
    -1
      dep/rtaudio
  3. +60
    -44
      include/audio.hpp
  4. +19
    -2
      include/midi.hpp
  5. +6
    -5
      src/app/AudioWidget.cpp
  6. +203
    -9
      src/audio.cpp
  7. +5
    -9
      src/rtaudio.cpp

+ 1
- 1
dep/Makefile View File

@@ -207,7 +207,7 @@ $(rtmidi): | rtmidi-4.0.0
$(MAKE) -C rtmidi-4.0.0 $(MAKE) -C rtmidi-4.0.0
$(MAKE) -C rtmidi-4.0.0 install $(MAKE) -C rtmidi-4.0.0 install


RTAUDIO_FLAGS += -DRTAUDIO_BUILD_STATIC_LIBS=ON
RTAUDIO_FLAGS += -DRTAUDIO_BUILD_STATIC_LIBS=ON -DRTAUDIO_BUILD_TESTING=OFF
ifdef ARCH_LIN ifdef ARCH_LIN
RTAUDIO_FLAGS += -DRTAUDIO_API_ALSA=ON -DRTAUDIO_API_JACK=ON -DRTAUDIO_API_PULSE=OFF -DRTAUDIO_API_OSS=OFF RTAUDIO_FLAGS += -DRTAUDIO_API_ALSA=ON -DRTAUDIO_API_JACK=ON -DRTAUDIO_API_PULSE=OFF -DRTAUDIO_API_OSS=OFF
endif endif


+ 1
- 1
dep/rtaudio

@@ -1 +1 @@
Subproject commit 07b1c6228fec77a207afd5d5cccc36681e90d319
Subproject commit e9915064859381a7a616b088dadbaee81bc0e0df

+ 60
- 44
include/audio.hpp View File

@@ -22,30 +22,45 @@ namespace audio {
struct Device; struct Device;
struct Port; struct Port;


/** An audio driver API containing any number of audio devices.
*/
struct Driver { struct Driver {
virtual ~Driver() {} virtual ~Driver() {}
/** Returns the name of the driver. E.g. "ALSA". */
virtual std::string getName() { virtual std::string getName() {
return ""; return "";
} }
/** Returns a list of all device IDs that can be subscribed to. */
virtual std::vector<int> getDeviceIds() { virtual std::vector<int> getDeviceIds() {
return {}; return {};
} }

/** Gets the name of a device without subscribing to it. */
/** Returns the name of a device without obtaining it. */
virtual std::string getDeviceName(int deviceId) { virtual std::string getDeviceName(int deviceId) {
return ""; return "";
} }
/** Returns the number of inputs of a device without obtaining it. */
virtual int getDeviceNumInputs(int deviceId) { virtual int getDeviceNumInputs(int deviceId) {
return 0; return 0;
} }
/** Returns the number of output of a device without obtaining it. */
virtual int getDeviceNumOutputs(int deviceId) { virtual int getDeviceNumOutputs(int deviceId) {
return 0; return 0;
} }
/** Returns a detailed description of the device without obtaining it.
`offset` specifies the first channel (zero-indexed).
E.g. "MySoundcard (1-2 in, 1-2 out)"
*/
std::string getDeviceDetail(int deviceId, int offset, int maxChannels); std::string getDeviceDetail(int deviceId, int offset, int maxChannels);


/** Adds the given port as a reference holder of a device and returns the it.
Creates the Device if no ports are subscribed before calling.
*/
virtual Device* subscribe(int deviceId, Port* port) { virtual Device* subscribe(int deviceId, Port* port) {
return NULL; return NULL;
} }
/** Removes the give port as a reference holder of a device.
Deletes the Device if no ports are subscribed after calling.
*/
virtual void unsubscribe(int deviceId, Port* port) {} virtual void unsubscribe(int deviceId, Port* port) {}
}; };


@@ -53,6 +68,12 @@ struct Driver {
// Device // Device
//////////////////// ////////////////////


/** A single audio device of a driver API.

Modules should

Methods throw `rack::Exception` if the driver API has an exception.
*/
struct Device { struct Device {
std::set<Port*> subscribed; std::set<Port*> subscribed;
virtual ~Device() {} virtual ~Device() {}
@@ -69,22 +90,36 @@ struct Device {
virtual int getNumOutputs() { virtual int getNumOutputs() {
return 0; return 0;
} }
/** Returns a detailed description of the device.
`offset` specifies the first channel (zero-indexed).
E.g. "MySoundcard (1-2 in, 1-2 out)"
*/
std::string getDetail(int offset, int maxChannels); std::string getDetail(int offset, int maxChannels);


/** Returns a list of all valid (user-selectable) sample rates.
The device may accept sample rates not in this list, but it *must* accept sample rates in the list.
*/
virtual std::vector<int> getSampleRates() { virtual std::vector<int> getSampleRates() {
return {}; return {};
} }
/** Returns the current sample rate. */
virtual int getSampleRate() { virtual int getSampleRate() {
return 0; return 0;
} }
/** Sets the sample rate of the device, re-opening it if needed. */
virtual void setSampleRate(int sampleRate) {} virtual void setSampleRate(int sampleRate) {}


/** Returns a list of all valid (user-selectable) block sizes.
The device may accept block sizes not in this list, but it *must* accept block sizes in the list.
*/
virtual std::vector<int> getBlockSizes() { virtual std::vector<int> getBlockSizes() {
return {}; return {};
} }
/** Returns the current block size. */
virtual int getBlockSize() { virtual int getBlockSize() {
return 0; return 0;
} }
/** Sets the block size of the device, re-opening it if needed. */
virtual void setBlockSize(int blockSize) {} virtual void setBlockSize(int blockSize) {}


// Called by this Device class, forwards to subscribed Ports. // Called by this Device class, forwards to subscribed Ports.
@@ -97,6 +132,11 @@ struct 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 thrown a `rack::Exception`, it is caught and logged inside all Port methods, so you can consider them nothrow.
*/
struct Port { struct Port {
/** The first channel index of the device to process. */ /** The first channel index of the device to process. */
int offset = 0; int offset = 0;
@@ -115,51 +155,27 @@ struct Port {
virtual ~Port(); virtual ~Port();
void reset(); void reset();


Driver* getDriver() {
return driver;
}
int getDriverId() {
return driverId;
}
Driver* getDriver();
int getDriverId();
void setDriverId(int driverId); void setDriverId(int driverId);
std::string getDriverName();


Device* getDevice() {
return device;
}
int getDeviceId() {
return deviceId;
}
Device* getDevice();
std::vector<int> getDeviceIds();
int getDeviceId();
void setDeviceId(int deviceId); void setDeviceId(int deviceId);

std::vector<int> getSampleRates() {
if (!device)
return {};
return device->getSampleRates();
}
int getSampleRate() {
if (!device)
return 0;
return device->getSampleRate();
}
void setSampleRate(int sampleRate) {
if (device)
device->setSampleRate(sampleRate);
}

std::vector<int> getBlockSizes() {
if (!device)
return {};
return device->getBlockSizes();
}
int getBlockSize() {
if (!device)
return 0;
return device->getBlockSize();
}
void setBlockSize(int blockSize) {
if (device)
device->setBlockSize(blockSize);
}
int getDeviceNumInputs(int deviceId);
int getDeviceNumOutputs(int deviceId);
std::string getDeviceName(int deviceId);
std::string getDeviceDetail(int deviceId, int offset);

std::vector<int> getSampleRates();
int getSampleRate();
void setSampleRate(int sampleRate);

std::vector<int> getBlockSizes();
int getBlockSize();
void setBlockSize(int blockSize);


int getNumInputs(); int getNumInputs();
int getNumOutputs(); int getNumOutputs();


+ 19
- 2
include/midi.hpp View File

@@ -88,20 +88,31 @@ struct Output;


struct Driver { struct Driver {
virtual ~Driver() {} virtual ~Driver() {}
/** Returns the name of the driver. E.g. "ALSA". */
virtual std::string getName() { virtual std::string getName() {
return ""; return "";
} }
/** Returns a list of all input device IDs that can be subscribed to. */
virtual std::vector<int> getInputDeviceIds() { virtual std::vector<int> getInputDeviceIds() {
return {}; return {};
} }
/** Returns the name of an input device without obtaining it. */
virtual std::string getInputDeviceName(int deviceId) { virtual std::string getInputDeviceName(int deviceId) {
return ""; return "";
} }
/** Adds the given port as a reference holder of a device and returns the it.
Creates the Device if no ports are subscribed before calling.
*/
virtual InputDevice* subscribeInput(int deviceId, Input* input) { virtual InputDevice* subscribeInput(int deviceId, Input* input) {
return NULL; return NULL;
} }
/** Removes the give port as a reference holder of a device.
Deletes the Device if no ports are subscribed after calling.
*/
virtual void unsubscribeInput(int deviceId, Input* input) {} virtual void unsubscribeInput(int deviceId, Input* input) {}


// The following behave identically as the above methods except for outputs.

virtual std::vector<int> getOutputDeviceIds() { virtual std::vector<int> getOutputDeviceIds() {
return {}; return {};
} }
@@ -127,15 +138,21 @@ struct Device {


struct InputDevice : Device { struct InputDevice : Device {
std::set<Input*> subscribed; std::set<Input*> subscribed;
/** Not public. Use Driver::subscribeInput(). */
void subscribe(Input* input); void subscribe(Input* input);
/** Not public. Use Driver::unsubscribeInput(). */
void unsubscribe(Input* input); void unsubscribe(Input* input);
/** Called when a MIDI message is received from the device. */
void onMessage(const Message &message); void onMessage(const Message &message);
}; };


struct OutputDevice : Device { struct OutputDevice : Device {
std::set<Output*> subscribed; std::set<Output*> subscribed;
void subscribe(Output* input);
void unsubscribe(Output* input);
/** Not public. Use Driver::subscribeOutput(). */
void subscribe(Output* output);
/** Not public. Use Driver::unsubscribeOutput(). */
void unsubscribe(Output* output);
/** Sends a MIDI message to the device. */
virtual void sendMessage(const Message &message) {} virtual void sendMessage(const Message &message) {}
}; };




+ 6
- 5
src/app/AudioWidget.cpp View File

@@ -40,7 +40,8 @@ struct AudioDriverChoice : LedDisplayChoice {
text = ""; text = "";
if (box.size.x >= 200.0) if (box.size.x >= 200.0)
text += "Driver: "; text += "Driver: ";
std::string driverName = (port && port->driver) ? port->getDriver()->getName() : "";
audio::Driver* driver = port ? port->getDriver() : NULL;
std::string driverName = driver ? driver->getName() : "";
if (driverName != "") { if (driverName != "") {
text += driverName; text += driverName;
color.a = 1.0; color.a = 1.0;
@@ -73,7 +74,7 @@ struct AudioDeviceValueItem : ui::MenuItem {
}; };


static void appendAudioDeviceMenu(ui::Menu* menu, audio::Port* port) { static void appendAudioDeviceMenu(ui::Menu* menu, audio::Port* port) {
if (!port || !port->driver)
if (!port)
return; return;


{ {
@@ -85,8 +86,8 @@ static void appendAudioDeviceMenu(ui::Menu* menu, audio::Port* port) {
menu->addChild(item); menu->addChild(item);
} }


for (int deviceId : port->driver->getDeviceIds()) {
int channels = std::max(port->driver->getDeviceNumInputs(deviceId), port->driver->getDeviceNumOutputs(deviceId));
for (int deviceId : port->getDeviceIds()) {
int channels = std::max(port->getDeviceNumInputs(deviceId), port->getDeviceNumOutputs(deviceId));
// Prevents devices with a ridiculous number of channels from being displayed // Prevents devices with a ridiculous number of channels from being displayed
const int maxTotalChannels = port->maxChannels * 16; const int maxTotalChannels = port->maxChannels * 16;
channels = std::min(maxTotalChannels, channels); channels = std::min(maxTotalChannels, channels);
@@ -96,7 +97,7 @@ static void appendAudioDeviceMenu(ui::Menu* menu, audio::Port* port) {
item->port = port; item->port = port;
item->deviceId = deviceId; item->deviceId = deviceId;
item->offset = offset; item->offset = offset;
item->text = port->driver->getDeviceDetail(deviceId, offset, port->maxChannels);
item->text = port->getDeviceDetail(deviceId, offset);
item->rightText = CHECKMARK(item->deviceId == port->getDeviceId() && item->offset == port->offset); item->rightText = CHECKMARK(item->deviceId == port->getDeviceId() && item->offset == port->offset);
menu->addChild(item); menu->addChild(item);
} }


+ 203
- 9
src/audio.cpp View File

@@ -96,11 +96,27 @@ Port::~Port() {
} }


void Port::reset() { void Port::reset() {
setDriverId(-1);
// Get default driver
int firstDriverId = -1;
std::vector<int> driverIds = getDriverIds();
if (!driverIds.empty())
firstDriverId = driverIds[0];

setDriverId(firstDriverId);
offset = 0; offset = 0;
} }


Driver* Port::getDriver() {
return driver;
}

int Port::getDriverId() {
return driverId;
}

void Port::setDriverId(int driverId) { void Port::setDriverId(int driverId) {
if (driverId == this->driverId)
return;
// Unset device and driver // Unset device and driver
setDeviceId(-1); setDeviceId(-1);
driver = NULL; driver = NULL;
@@ -118,31 +134,206 @@ void Port::setDriverId(int driverId) {
} }
} }


std::string Port::getDriverName() {
if (!driver)
return "";
try {
return driver->getName();
}
catch (Exception& e) {
WARN("Audio port could not get driver name: %s", e.what());
return "";
}
}

Device* Port::getDevice() {
return device;
}

std::vector<int> Port::getDeviceIds() {
if (!driver)
return {};
try {
return driver->getDeviceIds();
}
catch (Exception& e) {
WARN("Audio port could not get device IDs: %s", e.what());
return {};
}
}

int Port::getDeviceId() {
return deviceId;
}

void Port::setDeviceId(int deviceId) { void Port::setDeviceId(int deviceId) {
if (deviceId == this->deviceId)
return;
// Destroy device // Destroy device
if (driver && this->deviceId >= 0) { if (driver && this->deviceId >= 0) {
driver->unsubscribe(this->deviceId, this);
try {
driver->unsubscribe(this->deviceId, this);
}
catch (Exception& e) {
WARN("Audio port could not unsubscribe from device: %s", e.what());
}
} }
device = NULL; device = NULL;
this->deviceId = -1; this->deviceId = -1;


// Create device // Create device
if (driver && deviceId >= 0) { if (driver && deviceId >= 0) {
device = driver->subscribe(deviceId, this);
this->deviceId = deviceId;
try {
device = driver->subscribe(deviceId, this);
this->deviceId = deviceId;
}
catch (Exception& e) {
WARN("Audio port could not subscribe to device: %s", e.what());
}
}
}

int Port::getDeviceNumInputs(int deviceId) {
if (!driver)
return 0;
try {
return driver->getDeviceNumInputs(deviceId);
}
catch (Exception& e) {
WARN("Audio port could not get device number of inputs: %s", e.what());
return 0;
}
}

int Port::getDeviceNumOutputs(int deviceId) {
if (!driver)
return 0;
try {
return driver->getDeviceNumOutputs(deviceId);
}
catch (Exception& e) {
WARN("Audio port could not get device number of outputs: %s", e.what());
return 0;
}
}

std::string Port::getDeviceName(int deviceId) {
if (!driver)
return "";
try {
return driver->getDeviceName(deviceId);
}
catch (Exception& e) {
WARN("Audio port could not get device name: %s", e.what());
return 0;
}
}

std::string Port::getDeviceDetail(int deviceId, int offset) {
if (!driver)
return "";
try {
// Use maxChannels from Port.
return driver->getDeviceDetail(deviceId, offset, maxChannels);
}
catch (Exception& e) {
WARN("Audio port could not get device detail: %s", e.what());
return 0;
}
}

std::vector<int> Port::getSampleRates() {
if (!device)
return {};
try {
return device->getSampleRates();
}
catch (Exception& e) {
WARN("Audio port could not get device sample rates: %s", e.what());
return {};
}
}

int Port::getSampleRate() {
if (!device)
return 0;
try {
return device->getSampleRate();
}
catch (Exception& e) {
WARN("Audio port could not get device sample rate: %s", e.what());
return 0;
}
}

void Port::setSampleRate(int sampleRate) {
if (!device)
return;
try {
device->setSampleRate(sampleRate);
}
catch (Exception& e) {
WARN("Audio port could not set device sample rate: %s", e.what());
}
}

std::vector<int> Port::getBlockSizes() {
if (!device)
return {};
try {
return device->getBlockSizes();
}
catch (Exception& e) {
WARN("Audio port could not get device block sizes: %s", e.what());
return {};
}
}

int Port::getBlockSize() {
if (!device)
return 0;
try {
return device->getBlockSize();
}
catch (Exception& e) {
WARN("Audio port could not get device block size: %s", e.what());
return 0;
}
}

void Port::setBlockSize(int blockSize) {
if (!device)
return;
try {
device->setBlockSize(blockSize);
}
catch (Exception& e) {
WARN("Audio port could not set device block size: %s", e.what());
} }
} }


int Port::getNumInputs() { int Port::getNumInputs() {
if (!device) if (!device)
return 0; return 0;
return std::min(device->getNumInputs() - offset, maxChannels);
try {
return std::min(device->getNumInputs() - offset, maxChannels);
}
catch (Exception& e) {
WARN("Audio port could not get device number of inputs: %s", e.what());
return 0;
}
} }


int Port::getNumOutputs() { int Port::getNumOutputs() {
if (!device) if (!device)
return 0; return 0;
return std::min(device->getNumOutputs() - offset, maxChannels);
try {
return std::min(device->getNumOutputs() - offset, maxChannels);
}
catch (Exception& e) {
WARN("Audio port could not get device number of outputs: %s", e.what());
return 0;
}
} }


json_t* Port::toJson() { json_t* Port::toJson() {
@@ -172,8 +363,11 @@ void Port::fromJson(json_t* rootJ) {
if (deviceNameJ) { if (deviceNameJ) {
std::string deviceName = json_string_value(deviceNameJ); std::string deviceName = json_string_value(deviceNameJ);
// Search for device ID with equal name // Search for device ID with equal name
for (int deviceId : driver->getDeviceIds()) {
if (driver->getDeviceName(deviceId) == deviceName) {
for (int deviceId : getDeviceIds()) {
std::string deviceNameCurr = getDeviceName(deviceId);
if (deviceNameCurr == "")
continue;
if (deviceNameCurr == deviceName) {
setDeviceId(deviceId); setDeviceId(deviceId);
break; break;
} }
@@ -194,7 +388,6 @@ void Port::fromJson(json_t* rootJ) {
offset = json_integer_value(offsetJ); offset = json_integer_value(offsetJ);
} }



//////////////////// ////////////////////
// audio // audio
//////////////////// ////////////////////
@@ -223,6 +416,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;


+ 5
- 9
src/rtaudio.cpp View File

@@ -39,7 +39,6 @@ struct RtAudioDevice : audio::Device {
deviceInfo = rtAudio->getDeviceInfo(deviceId); deviceInfo = rtAudio->getDeviceInfo(deviceId);
} }
catch (RtAudioError& e) { catch (RtAudioError& e) {
WARN("Failed to query RtAudio device: %s", e.what());
throw Exception(string::f("Failed to query RtAudio device: %s", e.what())); throw Exception(string::f("Failed to query RtAudio device: %s", e.what()));
} }


@@ -55,8 +54,7 @@ struct RtAudioDevice : audio::Device {
void openStream() { void openStream() {
// Open new device // Open new device
if (deviceInfo.outputChannels == 0 && deviceInfo.inputChannels == 0) { if (deviceInfo.outputChannels == 0 && deviceInfo.inputChannels == 0) {
WARN("RtAudio device %d has 0 inputs and 0 outputs", deviceId);
return;
throw Exception(string::f("RtAudio device %d has 0 inputs and 0 outputs", deviceId));
} }


inputParameters = RtAudio::StreamParameters(); inputParameters = RtAudio::StreamParameters();
@@ -98,8 +96,7 @@ struct RtAudioDevice : audio::Device {
&rtAudioCallback, this, &options, NULL); &rtAudioCallback, this, &options, NULL);
} }
catch (RtAudioError& e) { catch (RtAudioError& e) {
WARN("Failed to open RtAudio stream: %s", e.what());
return;
throw Exception(string::f("Failed to open RtAudio stream: %s", e.what()));
} }


INFO("Starting RtAudio stream %d", deviceId); INFO("Starting RtAudio stream %d", deviceId);
@@ -107,8 +104,7 @@ struct RtAudioDevice : audio::Device {
rtAudio->startStream(); rtAudio->startStream();
} }
catch (RtAudioError& e) { catch (RtAudioError& e) {
WARN("Failed to start RtAudio stream: %s", e.what());
return;
throw Exception(string::f("Failed to start RtAudio stream: %s", e.what()));
} }


// Update sample rate to actual value // Update sample rate to actual value
@@ -124,7 +120,7 @@ struct RtAudioDevice : audio::Device {
rtAudio->stopStream(); rtAudio->stopStream();
} }
catch (RtAudioError& e) { catch (RtAudioError& e) {
WARN("Failed to stop RtAudio stream %s", e.what());
throw Exception(string::f("Failed to stop RtAudio stream %s", e.what()));
} }
} }
if (rtAudio->isStreamOpen()) { if (rtAudio->isStreamOpen()) {
@@ -133,7 +129,7 @@ struct RtAudioDevice : audio::Device {
rtAudio->closeStream(); rtAudio->closeStream();
} }
catch (RtAudioError& e) { catch (RtAudioError& e) {
WARN("Failed to close RtAudio stream %s", e.what());
throw Exception(string::f("Failed to close RtAudio stream %s", e.what()));
} }
} }
INFO("Closed RtAudio stream"); INFO("Closed RtAudio stream");


Loading…
Cancel
Save