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 install

RTAUDIO_FLAGS += -DRTAUDIO_BUILD_STATIC_LIBS=ON
RTAUDIO_FLAGS += -DRTAUDIO_BUILD_STATIC_LIBS=ON -DRTAUDIO_BUILD_TESTING=OFF
ifdef ARCH_LIN
RTAUDIO_FLAGS += -DRTAUDIO_API_ALSA=ON -DRTAUDIO_API_JACK=ON -DRTAUDIO_API_PULSE=OFF -DRTAUDIO_API_OSS=OFF
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 Port;

/** An audio driver API containing any number of audio devices.
*/
struct Driver {
virtual ~Driver() {}
/** Returns the name of the driver. E.g. "ALSA". */
virtual std::string getName() {
return "";
}
/** Returns a list of all device IDs that can be subscribed to. */
virtual std::vector<int> getDeviceIds() {
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) {
return "";
}
/** Returns the number of inputs of a device without obtaining it. */
virtual int getDeviceNumInputs(int deviceId) {
return 0;
}
/** Returns the number of output of a device without obtaining it. */
virtual int getDeviceNumOutputs(int deviceId) {
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);

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

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

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

Modules should

Methods throw `rack::Exception` if the driver API has an exception.
*/
struct Device {
std::set<Port*> subscribed;
virtual ~Device() {}
@@ -69,22 +90,36 @@ struct Device {
virtual int getNumOutputs() {
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);

/** 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() {
return {};
}
/** Returns the current sample rate. */
virtual int getSampleRate() {
return 0;
}
/** Sets the sample rate of the device, re-opening it if needed. */
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() {
return {};
}
/** Returns the current block size. */
virtual int getBlockSize() {
return 0;
}
/** Sets the block size of the device, re-opening it if needed. */
virtual void setBlockSize(int blockSize) {}

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

Driver* getDriver() {
return driver;
}
int getDriverId() {
return driverId;
}
Driver* getDriver();
int getDriverId();
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);

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 getNumOutputs();


+ 19
- 2
include/midi.hpp View File

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

struct Driver {
virtual ~Driver() {}
/** Returns the name of the driver. E.g. "ALSA". */
virtual std::string getName() {
return "";
}
/** Returns a list of all input device IDs that can be subscribed to. */
virtual std::vector<int> getInputDeviceIds() {
return {};
}
/** Returns the name of an input device without obtaining it. */
virtual std::string getInputDeviceName(int deviceId) {
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) {
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) {}

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

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

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

struct OutputDevice : Device {
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) {}
};



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

@@ -40,7 +40,8 @@ struct AudioDriverChoice : LedDisplayChoice {
text = "";
if (box.size.x >= 200.0)
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 != "") {
text += driverName;
color.a = 1.0;
@@ -73,7 +74,7 @@ struct AudioDeviceValueItem : ui::MenuItem {
};

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

{
@@ -85,8 +86,8 @@ static void appendAudioDeviceMenu(ui::Menu* menu, audio::Port* port) {
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
const int maxTotalChannels = port->maxChannels * 16;
channels = std::min(maxTotalChannels, channels);
@@ -96,7 +97,7 @@ static void appendAudioDeviceMenu(ui::Menu* menu, audio::Port* port) {
item->port = port;
item->deviceId = deviceId;
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);
menu->addChild(item);
}


+ 203
- 9
src/audio.cpp View File

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

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

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

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

void Port::setDriverId(int driverId) {
if (driverId == this->driverId)
return;
// Unset device and driver
setDeviceId(-1);
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) {
if (deviceId == this->deviceId)
return;
// Destroy device
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;
this->deviceId = -1;

// Create device
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() {
if (!device)
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() {
if (!device)
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() {
@@ -172,8 +363,11 @@ void Port::fromJson(json_t* rootJ) {
if (deviceNameJ) {
std::string deviceName = json_string_value(deviceNameJ);
// 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);
break;
}
@@ -194,7 +388,6 @@ void Port::fromJson(json_t* rootJ) {
offset = json_integer_value(offsetJ);
}


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


+ 5
- 9
src/rtaudio.cpp View File

@@ -39,7 +39,6 @@ struct RtAudioDevice : audio::Device {
deviceInfo = rtAudio->getDeviceInfo(deviceId);
}
catch (RtAudioError& e) {
WARN("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() {
// Open new device
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();
@@ -98,8 +96,7 @@ struct RtAudioDevice : audio::Device {
&rtAudioCallback, this, &options, NULL);
}
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);
@@ -107,8 +104,7 @@ struct RtAudioDevice : audio::Device {
rtAudio->startStream();
}
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
@@ -124,7 +120,7 @@ struct RtAudioDevice : audio::Device {
rtAudio->stopStream();
}
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()) {
@@ -133,7 +129,7 @@ struct RtAudioDevice : audio::Device {
rtAudio->closeStream();
}
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");


Loading…
Cancel
Save