| @@ -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 @@ | |||
| Subproject commit 07b1c6228fec77a207afd5d5cccc36681e90d319 | |||
| Subproject commit e9915064859381a7a616b088dadbaee81bc0e0df | |||
| @@ -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(); | |||
| @@ -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) {} | |||
| }; | |||
| @@ -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); | |||
| } | |||
| @@ -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; | |||
| @@ -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"); | |||