| @@ -1,14 +1,8 @@ | |||||
| #pragma once | #pragma once | ||||
| #include <common.hpp> | #include <common.hpp> | ||||
| #include <jansson.h> | #include <jansson.h> | ||||
| #pragma GCC diagnostic push | |||||
| #ifndef __clang__ | |||||
| #pragma GCC diagnostic ignored "-Wsuggest-override" | |||||
| #endif | |||||
| #include <RtAudio.h> | |||||
| #pragma GCC diagnostic pop | |||||
| #include <vector> | |||||
| #include <set> | |||||
| namespace rack { | namespace rack { | ||||
| @@ -18,54 +12,173 @@ namespace rack { | |||||
| namespace audio { | namespace audio { | ||||
| //////////////////// | |||||
| // Driver | |||||
| //////////////////// | |||||
| struct Port; | |||||
| struct Device; | |||||
| struct Driver { | |||||
| virtual ~Driver() {} | |||||
| virtual std::string getName() { | |||||
| return ""; | |||||
| } | |||||
| virtual std::vector<int> getDeviceIds() { | |||||
| return {}; | |||||
| } | |||||
| virtual std::string getDeviceName(int deviceId) { | |||||
| return ""; | |||||
| } | |||||
| virtual Device* subscribe(int deviceId, Port* port) { | |||||
| return NULL; | |||||
| } | |||||
| virtual void unsubscribe(int deviceId, Port* port) {} | |||||
| }; | |||||
| //////////////////// | |||||
| // Device | |||||
| //////////////////// | |||||
| struct Device { | |||||
| std::set<Port*> subscribed; | |||||
| virtual ~Device() {} | |||||
| // Called by Driver::subscribe(). | |||||
| void subscribe(Port* port); | |||||
| void unsubscribe(Port* port); | |||||
| // Called by Port. | |||||
| virtual std::vector<int> getSampleRates() { | |||||
| return {}; | |||||
| } | |||||
| virtual int getSampleRate() { | |||||
| return 0; | |||||
| } | |||||
| virtual void setSampleRate(int sampleRate) {} | |||||
| virtual std::vector<int> getBlockSizes() { | |||||
| return {}; | |||||
| } | |||||
| virtual int getBlockSize() { | |||||
| return 0; | |||||
| } | |||||
| virtual void setBlockSize(int blockSize) {} | |||||
| virtual int getNumInputs() { | |||||
| return 0; | |||||
| } | |||||
| virtual int getNumOutputs() { | |||||
| return 0; | |||||
| } | |||||
| // Called by this Device class, forwards to subscribed Ports. | |||||
| void processBuffer(const float* input, int inputStride, float* output, int outputStride, int frames); | |||||
| void onOpenStream(); | |||||
| void onCloseStream(); | |||||
| }; | |||||
| //////////////////// | |||||
| // Port | |||||
| //////////////////// | |||||
| struct Port { | struct Port { | ||||
| // Stream properties | |||||
| int driverId = 0; | |||||
| int deviceId = -1; | |||||
| /** Not owned */ | |||||
| Driver* driver = NULL; | |||||
| Device* device = NULL; | |||||
| // Port settings | |||||
| int offset = 0; | int offset = 0; | ||||
| int maxChannels = 8; | int maxChannels = 8; | ||||
| int sampleRate = 44100; | |||||
| int blockSize = 256; | |||||
| int numOutputs = 0; | |||||
| int numInputs = 0; | |||||
| RtAudio* rtAudio = NULL; | |||||
| /** Cached */ | |||||
| RtAudio::DeviceInfo deviceInfo; | |||||
| // private | |||||
| int driverId = -1; | |||||
| int deviceId = -1; | |||||
| Port(); | Port(); | ||||
| virtual ~Port(); | virtual ~Port(); | ||||
| std::vector<int> getDriverIds(); | std::vector<int> getDriverIds(); | ||||
| std::string getDriverName(int driverId); | |||||
| int getDriverId() { | |||||
| return driverId; | |||||
| } | |||||
| void setDriverId(int driverId); | void setDriverId(int driverId); | ||||
| std::string getDriverName(int driverId); | |||||
| int getDeviceCount(); | |||||
| bool getDeviceInfo(int deviceId, RtAudio::DeviceInfo* deviceInfo); | |||||
| /** Returns the number of inputs or outputs, whichever is greater */ | |||||
| int getDeviceChannels(int deviceId); | |||||
| std::string getDeviceName(int deviceId); | |||||
| std::vector<int> getDeviceIds() { | |||||
| if (!driver) | |||||
| return {}; | |||||
| return driver->getDeviceIds(); | |||||
| } | |||||
| int getDeviceId() { | |||||
| return deviceId; | |||||
| } | |||||
| void setDeviceId(int deviceId); | |||||
| std::string getDeviceName(int deviceId) { | |||||
| if (!driver) | |||||
| return ""; | |||||
| return driver->getDeviceName(deviceId); | |||||
| } | |||||
| std::string getDeviceDetail(int deviceId, int offset); | std::string getDeviceDetail(int deviceId, int offset); | ||||
| void setDeviceId(int deviceId, int offset); | |||||
| std::vector<int> getSampleRates(); | |||||
| void setSampleRate(int sampleRate); | |||||
| std::vector<int> getBlockSizes(); | |||||
| void setBlockSize(int blockSize); | |||||
| void setChannels(int numOutputs, int numInputs); | |||||
| 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 getNumInputs(); | |||||
| int getNumOutputs(); | |||||
| /** Must close the stream before opening */ | |||||
| void openStream(); | |||||
| void closeStream(); | |||||
| virtual void processStream(const float* input, float* output, int frames) {} | |||||
| virtual void onCloseStream() {} | |||||
| virtual void onOpenStream() {} | |||||
| virtual void onChannelsChange() {} | |||||
| json_t* toJson(); | json_t* toJson(); | ||||
| void fromJson(json_t* rootJ); | void fromJson(json_t* rootJ); | ||||
| /** Callback for processing the audio stream. | |||||
| `inputStride` and `outputStride` are the number of array elements between frames in the buffers. | |||||
| */ | |||||
| virtual void processBuffer(const float* input, int inputStride, float* output, int outputStride, int frames) {} | |||||
| /** Called before processBuffer() is called for all Ports of the same device. | |||||
| Splitting the processBuffer() into these calls is useful for synchronizing Ports of the same device. | |||||
| Called even if there are no inputs. | |||||
| */ | |||||
| virtual void processInput(const float* input, int inputStride, int frames) {} | |||||
| /** Called after processBuffer() is called for all Ports of the same device. | |||||
| */ | |||||
| virtual void processOutput(float* output, int outputStride, int frames) {} | |||||
| virtual void onOpenStream() {} | |||||
| virtual void onCloseStream() {} | |||||
| }; | }; | ||||
| void init(); | |||||
| void destroy(); | |||||
| /** Registers a new audio driver. Takes pointer ownership. */ | |||||
| void addDriver(int driverId, Driver* driver); | |||||
| } // namespace audio | } // namespace audio | ||||
| } // namespace rack | } // namespace rack | ||||
| @@ -62,7 +62,6 @@ struct Driver { | |||||
| virtual std::string getName() { | virtual std::string getName() { | ||||
| return ""; | return ""; | ||||
| } | } | ||||
| virtual std::vector<int> getInputDeviceIds() { | virtual std::vector<int> getInputDeviceIds() { | ||||
| return {}; | return {}; | ||||
| } | } | ||||
| @@ -0,0 +1,11 @@ | |||||
| #pragma once | |||||
| #include <common.hpp> | |||||
| namespace rack { | |||||
| void rtaudioInit(); | |||||
| } // namespace rack | |||||
| @@ -27,13 +27,13 @@ struct AudioDriverChoice : LedDisplayChoice { | |||||
| item->port = port; | item->port = port; | ||||
| item->driverId = driverId; | item->driverId = driverId; | ||||
| item->text = port->getDriverName(driverId); | item->text = port->getDriverName(driverId); | ||||
| item->rightText = CHECKMARK(item->driverId == port->driverId); | |||||
| item->rightText = CHECKMARK(item->driverId == port->getDriverId()); | |||||
| menu->addChild(item); | menu->addChild(item); | ||||
| } | } | ||||
| } | } | ||||
| void step() override { | void step() override { | ||||
| text = (box.size.x >= 200.0) ? "Driver: " : ""; | text = (box.size.x >= 200.0) ? "Driver: " : ""; | ||||
| std::string driverName = (port) ? port->getDriverName(port->driverId) : ""; | |||||
| std::string driverName = port ? port->getDriverName(port->getDriverId()) : ""; | |||||
| if (driverName != "") { | if (driverName != "") { | ||||
| text += driverName; | text += driverName; | ||||
| color.a = 1.f; | color.a = 1.f; | ||||
| @@ -51,14 +51,13 @@ struct AudioDeviceItem : ui::MenuItem { | |||||
| int deviceId; | int deviceId; | ||||
| int offset; | int offset; | ||||
| void onAction(const event::Action& e) override { | void onAction(const event::Action& e) override { | ||||
| port->setDeviceId(deviceId, offset); | |||||
| port->setDeviceId(deviceId); | |||||
| port->offset = offset; | |||||
| } | } | ||||
| }; | }; | ||||
| struct AudioDeviceChoice : LedDisplayChoice { | struct AudioDeviceChoice : LedDisplayChoice { | ||||
| audio::Port* port; | audio::Port* port; | ||||
| /** Prevents devices with a ridiculous number of channels from being displayed */ | |||||
| int maxTotalChannels = 128; | |||||
| void onAction(const event::Action& e) override { | void onAction(const event::Action& e) override { | ||||
| if (!port) | if (!port) | ||||
| @@ -66,24 +65,27 @@ struct AudioDeviceChoice : LedDisplayChoice { | |||||
| ui::Menu* menu = createMenu(); | ui::Menu* menu = createMenu(); | ||||
| menu->addChild(createMenuLabel("Audio device")); | menu->addChild(createMenuLabel("Audio device")); | ||||
| int deviceCount = port->getDeviceCount(); | |||||
| { | { | ||||
| AudioDeviceItem* item = new AudioDeviceItem; | AudioDeviceItem* item = new AudioDeviceItem; | ||||
| item->port = port; | item->port = port; | ||||
| item->deviceId = -1; | item->deviceId = -1; | ||||
| item->text = "(No device)"; | item->text = "(No device)"; | ||||
| item->rightText = CHECKMARK(item->deviceId == port->deviceId); | |||||
| item->rightText = CHECKMARK(item->deviceId == port->getDeviceId()); | |||||
| menu->addChild(item); | menu->addChild(item); | ||||
| } | } | ||||
| for (int deviceId = 0; deviceId < deviceCount; deviceId++) { | |||||
| int channels = std::min(maxTotalChannels, port->getDeviceChannels(deviceId)); | |||||
| for (int deviceId : port->getDeviceIds()) { | |||||
| int channels = std::max(port->getNumInputs(), port->getNumOutputs()); | |||||
| /** Prevents devices with a ridiculous number of channels from being displayed */ | |||||
| const int maxTotalChannels = 128; | |||||
| channels = std::min(maxTotalChannels, channels); | |||||
| for (int offset = 0; offset < channels; offset += port->maxChannels) { | for (int offset = 0; offset < channels; offset += port->maxChannels) { | ||||
| AudioDeviceItem* item = new AudioDeviceItem; | AudioDeviceItem* item = new AudioDeviceItem; | ||||
| item->port = port; | item->port = port; | ||||
| item->deviceId = deviceId; | item->deviceId = deviceId; | ||||
| item->offset = offset; | item->offset = offset; | ||||
| item->text = port->getDeviceDetail(deviceId, offset); | item->text = port->getDeviceDetail(deviceId, offset); | ||||
| item->rightText = CHECKMARK(item->deviceId == port->deviceId && item->offset == port->offset); | |||||
| item->rightText = CHECKMARK(item->deviceId == port->getDeviceId() && item->offset == port->offset); | |||||
| menu->addChild(item); | menu->addChild(item); | ||||
| } | } | ||||
| } | } | ||||
| @@ -128,14 +130,14 @@ struct AudioSampleRateChoice : LedDisplayChoice { | |||||
| item->port = port; | item->port = port; | ||||
| item->sampleRate = sampleRate; | item->sampleRate = sampleRate; | ||||
| item->text = string::f("%g kHz", sampleRate / 1000.0); | item->text = string::f("%g kHz", sampleRate / 1000.0); | ||||
| item->rightText = CHECKMARK(item->sampleRate == port->sampleRate); | |||||
| item->rightText = CHECKMARK(item->sampleRate == port->getSampleRate()); | |||||
| menu->addChild(item); | menu->addChild(item); | ||||
| } | } | ||||
| } | } | ||||
| void step() override { | void step() override { | ||||
| text = (box.size.x >= 100.0) ? "Rate: " : ""; | text = (box.size.x >= 100.0) ? "Rate: " : ""; | ||||
| if (port) { | if (port) { | ||||
| text += string::f("%g kHz", port->sampleRate / 1000.0); | |||||
| text += string::f("%g kHz", port->getSampleRate() / 1000.0); | |||||
| } | } | ||||
| else { | else { | ||||
| text += "0 kHz"; | text += "0 kHz"; | ||||
| @@ -168,16 +170,16 @@ struct AudioBlockSizeChoice : LedDisplayChoice { | |||||
| AudioBlockSizeItem* item = new AudioBlockSizeItem; | AudioBlockSizeItem* item = new AudioBlockSizeItem; | ||||
| item->port = port; | item->port = port; | ||||
| item->blockSize = blockSize; | item->blockSize = blockSize; | ||||
| float latency = (float) blockSize / port->sampleRate * 1000.0; | |||||
| float latency = (float) blockSize / port->getSampleRate() * 1000.0; | |||||
| item->text = string::f("%d (%.1f ms)", blockSize, latency); | item->text = string::f("%d (%.1f ms)", blockSize, latency); | ||||
| item->rightText = CHECKMARK(item->blockSize == port->blockSize); | |||||
| item->rightText = CHECKMARK(item->blockSize == port->getBlockSize()); | |||||
| menu->addChild(item); | menu->addChild(item); | ||||
| } | } | ||||
| } | } | ||||
| void step() override { | void step() override { | ||||
| text = (box.size.x >= 100.0) ? "Block size: " : ""; | text = (box.size.x >= 100.0) ? "Block size: " : ""; | ||||
| if (port) { | if (port) { | ||||
| text += string::f("%d", port->blockSize); | |||||
| text += string::f("%d", port->getBlockSize()); | |||||
| } | } | ||||
| else { | else { | ||||
| text += "0"; | text += "0"; | ||||
| @@ -1,320 +1,166 @@ | |||||
| #include <audio.hpp> | #include <audio.hpp> | ||||
| #include <string.hpp> | #include <string.hpp> | ||||
| #include <math.hpp> | |||||
| #include <system.hpp> | |||||
| namespace rack { | namespace rack { | ||||
| namespace audio { | namespace audio { | ||||
| Port::Port() { | |||||
| setDriverId(0); | |||||
| } | |||||
| static std::vector<std::pair<int, Driver*>> drivers; | |||||
| Port::~Port() { | |||||
| closeStream(); | |||||
| } | |||||
| std::vector<int> Port::getDriverIds() { | |||||
| std::vector<RtAudio::Api> apis; | |||||
| RtAudio::getCompiledApi(apis); | |||||
| std::vector<int> drivers; | |||||
| for (RtAudio::Api api : apis) { | |||||
| drivers.push_back((int) api); | |||||
| } | |||||
| return drivers; | |||||
| } | |||||
| //////////////////// | |||||
| // Device | |||||
| //////////////////// | |||||
| std::string Port::getDriverName(int driverId) { | |||||
| switch (driverId) { | |||||
| case 0: return "Default"; | |||||
| case RtAudio::LINUX_ALSA: return "ALSA"; | |||||
| case RtAudio::LINUX_PULSE: return "PulseAudio"; | |||||
| case RtAudio::LINUX_OSS: return "OSS"; | |||||
| case RtAudio::UNIX_JACK: return "JACK"; | |||||
| case RtAudio::MACOSX_CORE: return "Core Audio"; | |||||
| case RtAudio::WINDOWS_WASAPI: return "WASAPI"; | |||||
| case RtAudio::WINDOWS_ASIO: return "ASIO"; | |||||
| case RtAudio::WINDOWS_DS: return "DirectSound"; | |||||
| case RtAudio::RTAUDIO_DUMMY: return "Dummy Audio"; | |||||
| default: return "Unknown"; | |||||
| } | |||||
| void Device::subscribe(Port* port) { | |||||
| subscribed.insert(port); | |||||
| } | } | ||||
| void Port::setDriverId(int driverId) { | |||||
| // Close device | |||||
| setDeviceId(-1, 0); | |||||
| void Device::unsubscribe(Port* port) { | |||||
| auto it = subscribed.find(port); | |||||
| if (it != subscribed.end()) | |||||
| subscribed.erase(it); | |||||
| } | |||||
| // Close driver | |||||
| if (rtAudio) { | |||||
| delete rtAudio; | |||||
| rtAudio = NULL; | |||||
| void Device::processBuffer(const float* input, int inputStride, float* output, int outputStride, int frames) { | |||||
| for (Port* port : subscribed) { | |||||
| port->processInput(input + port->offset, inputStride, frames); | |||||
| } | } | ||||
| this->driverId = 0; | |||||
| // Open driver | |||||
| if (driverId >= 0) { | |||||
| rtAudio = new RtAudio((RtAudio::Api) driverId); | |||||
| this->driverId = (int) rtAudio->getCurrentApi(); | |||||
| for (Port* port : subscribed) { | |||||
| port->processBuffer(input + port->offset, inputStride, output + port->offset, outputStride, frames); | |||||
| } | } | ||||
| } | |||||
| int Port::getDeviceCount() { | |||||
| if (rtAudio) { | |||||
| return rtAudio->getDeviceCount(); | |||||
| for (Port* port : subscribed) { | |||||
| port->processOutput(output + port->offset, outputStride, frames); | |||||
| } | } | ||||
| return 0; | |||||
| } | } | ||||
| bool Port::getDeviceInfo(int deviceId, RtAudio::DeviceInfo* deviceInfo) { | |||||
| if (!deviceInfo) | |||||
| return false; | |||||
| if (rtAudio) { | |||||
| if (deviceId == this->deviceId) { | |||||
| *deviceInfo = this->deviceInfo; | |||||
| return true; | |||||
| } | |||||
| else { | |||||
| try { | |||||
| *deviceInfo = rtAudio->getDeviceInfo(deviceId); | |||||
| return true; | |||||
| } | |||||
| catch (RtAudioError& e) { | |||||
| WARN("Failed to query RtAudio device: %s", e.what()); | |||||
| } | |||||
| } | |||||
| void Device::onOpenStream() { | |||||
| for (Port* port : subscribed) { | |||||
| port->onOpenStream(); | |||||
| } | } | ||||
| return false; | |||||
| } | } | ||||
| int Port::getDeviceChannels(int deviceId) { | |||||
| if (deviceId < 0) | |||||
| return 0; | |||||
| if (rtAudio) { | |||||
| RtAudio::DeviceInfo deviceInfo; | |||||
| if (getDeviceInfo(deviceId, &deviceInfo)) | |||||
| return std::max((int) deviceInfo.inputChannels, (int) deviceInfo.outputChannels); | |||||
| void Device::onCloseStream() { | |||||
| for (Port* port : subscribed) { | |||||
| port->onCloseStream(); | |||||
| } | } | ||||
| return 0; | |||||
| } | } | ||||
| std::string Port::getDeviceName(int deviceId) { | |||||
| if (deviceId < 0) | |||||
| return ""; | |||||
| //////////////////// | |||||
| // Port | |||||
| //////////////////// | |||||
| if (rtAudio) { | |||||
| RtAudio::DeviceInfo deviceInfo; | |||||
| if (getDeviceInfo(deviceId, &deviceInfo)) | |||||
| return deviceInfo.name; | |||||
| } | |||||
| return ""; | |||||
| Port::Port() { | |||||
| setDriverId(-1); | |||||
| } | } | ||||
| std::string Port::getDeviceDetail(int deviceId, int offset) { | |||||
| if (deviceId < 0) | |||||
| return ""; | |||||
| Port::~Port() { | |||||
| setDriverId(-1); | |||||
| } | |||||
| if (rtAudio) { | |||||
| RtAudio::DeviceInfo deviceInfo; | |||||
| if (getDeviceInfo(deviceId, &deviceInfo)) { | |||||
| std::string deviceDetail = string::f("%s (", deviceInfo.name.c_str()); | |||||
| if (offset < (int) deviceInfo.inputChannels) | |||||
| deviceDetail += string::f("%d-%d in", offset + 1, std::min(offset + maxChannels, (int) deviceInfo.inputChannels)); | |||||
| if (offset < (int) deviceInfo.inputChannels && offset < (int) deviceInfo.outputChannels) | |||||
| deviceDetail += ", "; | |||||
| if (offset < (int) deviceInfo.outputChannels) | |||||
| deviceDetail += string::f("%d-%d out", offset + 1, std::min(offset + maxChannels, (int) deviceInfo.outputChannels)); | |||||
| deviceDetail += ")"; | |||||
| return deviceDetail; | |||||
| } | |||||
| std::vector<int> Port::getDriverIds() { | |||||
| std::vector<int> driverIds; | |||||
| for (auto& pair : drivers) { | |||||
| driverIds.push_back(pair.first); | |||||
| } | } | ||||
| return ""; | |||||
| return driverIds; | |||||
| } | } | ||||
| void Port::setDeviceId(int deviceId, int offset) { | |||||
| closeStream(); | |||||
| this->deviceId = deviceId; | |||||
| this->offset = offset; | |||||
| openStream(); | |||||
| } | |||||
| void Port::setDriverId(int driverId) { | |||||
| // Unset device and driver | |||||
| setDeviceId(-1); | |||||
| driver = NULL; | |||||
| this->driverId = -1; | |||||
| std::vector<int> Port::getSampleRates() { | |||||
| if (rtAudio) { | |||||
| try { | |||||
| RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(deviceId); | |||||
| std::vector<int> sampleRates(deviceInfo.sampleRates.begin(), deviceInfo.sampleRates.end()); | |||||
| return sampleRates; | |||||
| if (driverId == -1) { | |||||
| // Set first driver as default | |||||
| if (!drivers.empty()) { | |||||
| driver = drivers[0].second; | |||||
| this->driverId = drivers[0].first; | |||||
| } | } | ||||
| catch (RtAudioError& e) { | |||||
| WARN("Failed to query RtAudio device: %s", e.what()); | |||||
| } | |||||
| else { | |||||
| // Set driver with driverId | |||||
| for (auto& pair : drivers) { | |||||
| if (pair.first == driverId) { | |||||
| driver = pair.second; | |||||
| this->driverId = driverId; | |||||
| break; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| return {}; | |||||
| } | } | ||||
| void Port::setSampleRate(int sampleRate) { | |||||
| if (sampleRate == this->sampleRate) | |||||
| return; | |||||
| closeStream(); | |||||
| this->sampleRate = sampleRate; | |||||
| openStream(); | |||||
| } | |||||
| std::vector<int> Port::getBlockSizes() { | |||||
| if (rtAudio) { | |||||
| return {64, 128, 256, 512, 1024, 2048, 4096}; | |||||
| std::string Port::getDriverName(int driverId) { | |||||
| for (auto& pair : drivers) { | |||||
| if (pair.first == driverId) { | |||||
| return pair.second->getName(); | |||||
| } | |||||
| } | } | ||||
| return {}; | |||||
| } | |||||
| void Port::setBlockSize(int blockSize) { | |||||
| if (blockSize == this->blockSize) | |||||
| return; | |||||
| closeStream(); | |||||
| this->blockSize = blockSize; | |||||
| openStream(); | |||||
| } | |||||
| void Port::setChannels(int numOutputs, int numInputs) { | |||||
| this->numOutputs = numOutputs; | |||||
| this->numInputs = numInputs; | |||||
| onChannelsChange(); | |||||
| return ""; | |||||
| } | } | ||||
| void Port::setDeviceId(int deviceId) { | |||||
| // Destroy device | |||||
| if (driver && this->deviceId >= 0) { | |||||
| driver->unsubscribe(this->deviceId, this); | |||||
| } | |||||
| device = NULL; | |||||
| this->deviceId = -1; | |||||
| static int rtCallback(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void* userData) { | |||||
| Port* port = (Port*) userData; | |||||
| assert(port); | |||||
| // Exploit the stream time to run code on startup of the audio thread | |||||
| if (streamTime == 0.0) { | |||||
| system::setThreadName("Audio"); | |||||
| // system::setThreadRealTime(); | |||||
| // Create device | |||||
| if (driver && deviceId >= 0) { | |||||
| device = driver->subscribe(deviceId, this); | |||||
| this->deviceId = deviceId; | |||||
| } | } | ||||
| port->processStream((const float*) inputBuffer, (float*) outputBuffer, nFrames); | |||||
| return 0; | |||||
| } | } | ||||
| void Port::openStream() { | |||||
| if (deviceId < 0) | |||||
| return; | |||||
| if (rtAudio) { | |||||
| // Open new device | |||||
| try { | |||||
| deviceInfo = rtAudio->getDeviceInfo(deviceId); | |||||
| } | |||||
| catch (RtAudioError& e) { | |||||
| WARN("Failed to query RtAudio device: %s", e.what()); | |||||
| return; | |||||
| } | |||||
| if (rtAudio->isStreamOpen()) | |||||
| return; | |||||
| setChannels(math::clamp((int) deviceInfo.outputChannels - offset, 0, maxChannels), math::clamp((int) deviceInfo.inputChannels - offset, 0, maxChannels)); | |||||
| if (numOutputs == 0 && numInputs == 0) { | |||||
| WARN("RtAudio device %d has 0 inputs and 0 outputs", deviceId); | |||||
| return; | |||||
| } | |||||
| RtAudio::StreamParameters outParameters; | |||||
| outParameters.deviceId = deviceId; | |||||
| outParameters.nChannels = numOutputs; | |||||
| outParameters.firstChannel = offset; | |||||
| RtAudio::StreamParameters inParameters; | |||||
| inParameters.deviceId = deviceId; | |||||
| inParameters.nChannels = numInputs; | |||||
| inParameters.firstChannel = offset; | |||||
| RtAudio::StreamOptions options; | |||||
| options.flags |= RTAUDIO_JACK_DONT_CONNECT; | |||||
| options.streamName = "VCV Rack"; | |||||
| int closestSampleRate = deviceInfo.preferredSampleRate; | |||||
| for (int sr : deviceInfo.sampleRates) { | |||||
| if (std::abs(sr - sampleRate) < std::abs(closestSampleRate - sampleRate)) { | |||||
| closestSampleRate = sr; | |||||
| } | |||||
| } | |||||
| try { | |||||
| INFO("Opening audio RtAudio device %d with %d in %d out", deviceId, numInputs, numOutputs); | |||||
| rtAudio->openStream( | |||||
| numOutputs == 0 ? NULL : &outParameters, | |||||
| numInputs == 0 ? NULL : &inParameters, | |||||
| RTAUDIO_FLOAT32, closestSampleRate, (unsigned int*) &blockSize, | |||||
| &rtCallback, this, &options, NULL); | |||||
| } | |||||
| catch (RtAudioError& e) { | |||||
| WARN("Failed to open RtAudio stream: %s", e.what()); | |||||
| return; | |||||
| } | |||||
| try { | |||||
| INFO("Starting RtAudio stream %d", deviceId); | |||||
| rtAudio->startStream(); | |||||
| } | |||||
| catch (RtAudioError& e) { | |||||
| WARN("Failed to start RtAudio stream: %s", e.what()); | |||||
| return; | |||||
| } | |||||
| // Update sample rate because this may have changed | |||||
| this->sampleRate = rtAudio->getStreamSampleRate(); | |||||
| onOpenStream(); | |||||
| std::string Port::getDeviceDetail(int deviceId, int offset) { | |||||
| if (!driver || !device) | |||||
| return ""; | |||||
| std::string text = getDeviceName(getDeviceId()); | |||||
| text += " ("; | |||||
| int numInputs = device->getNumInputs(); | |||||
| int numOutputs = device->getNumOutputs(); | |||||
| if (offset < numInputs) { | |||||
| text += string::f("%d-%d in", offset + 1, std::min(offset + maxChannels, numInputs)); | |||||
| } | |||||
| if (offset < numInputs && offset < numOutputs) { | |||||
| text += ", "; | |||||
| } | |||||
| if (offset < numOutputs) { | |||||
| text += string::f("%d-%d out", offset + 1, std::min(offset + maxChannels, numOutputs)); | |||||
| } | } | ||||
| text += ")"; | |||||
| return text; | |||||
| } | } | ||||
| void Port::closeStream() { | |||||
| setChannels(0, 0); | |||||
| if (rtAudio) { | |||||
| if (rtAudio->isStreamRunning()) { | |||||
| INFO("Stopping RtAudio stream %d", deviceId); | |||||
| try { | |||||
| rtAudio->stopStream(); | |||||
| } | |||||
| catch (RtAudioError& e) { | |||||
| WARN("Failed to stop RtAudio stream %s", e.what()); | |||||
| } | |||||
| } | |||||
| if (rtAudio->isStreamOpen()) { | |||||
| INFO("Closing RtAudio stream %d", deviceId); | |||||
| try { | |||||
| rtAudio->closeStream(); | |||||
| } | |||||
| catch (RtAudioError& e) { | |||||
| WARN("Failed to close RtAudio stream %s", e.what()); | |||||
| } | |||||
| } | |||||
| deviceInfo = RtAudio::DeviceInfo(); | |||||
| } | |||||
| int Port::getNumInputs() { | |||||
| if (!device) | |||||
| return 0; | |||||
| return std::min(device->getNumInputs() - offset, maxChannels); | |||||
| } | |||||
| onCloseStream(); | |||||
| int Port::getNumOutputs() { | |||||
| if (!device) | |||||
| return 0; | |||||
| return std::min(device->getNumOutputs() - offset, maxChannels); | |||||
| } | } | ||||
| json_t* Port::toJson() { | json_t* Port::toJson() { | ||||
| json_t* rootJ = json_object(); | json_t* rootJ = json_object(); | ||||
| json_object_set_new(rootJ, "driver", json_integer(driverId)); | |||||
| std::string deviceName = getDeviceName(deviceId); | |||||
| json_object_set_new(rootJ, "driver", json_integer(getDriverId())); | |||||
| std::string deviceName = getDeviceName(getDeviceId()); | |||||
| if (!deviceName.empty()) | if (!deviceName.empty()) | ||||
| json_object_set_new(rootJ, "deviceName", json_string(deviceName.c_str())); | json_object_set_new(rootJ, "deviceName", json_string(deviceName.c_str())); | ||||
| json_object_set_new(rootJ, "sampleRate", json_integer(getSampleRate())); | |||||
| json_object_set_new(rootJ, "blockSize", json_integer(getBlockSize())); | |||||
| json_object_set_new(rootJ, "offset", json_integer(offset)); | json_object_set_new(rootJ, "offset", json_integer(offset)); | ||||
| json_object_set_new(rootJ, "maxChannels", json_integer(maxChannels)); | |||||
| json_object_set_new(rootJ, "sampleRate", json_integer(sampleRate)); | |||||
| json_object_set_new(rootJ, "blockSize", json_integer(blockSize)); | |||||
| return rootJ; | return rootJ; | ||||
| } | } | ||||
| void Port::fromJson(json_t* rootJ) { | void Port::fromJson(json_t* rootJ) { | ||||
| closeStream(); | |||||
| json_t* driverJ = json_object_get(rootJ, "driver"); | json_t* driverJ = json_object_get(rootJ, "driver"); | ||||
| if (driverJ) | if (driverJ) | ||||
| setDriverId(json_number_value(driverJ)); | setDriverId(json_number_value(driverJ)); | ||||
| @@ -323,31 +169,45 @@ 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 = 0; deviceId < getDeviceCount(); deviceId++) { | |||||
| for (int deviceId : getDeviceIds()) { | |||||
| if (getDeviceName(deviceId) == deviceName) { | if (getDeviceName(deviceId) == deviceName) { | ||||
| this->deviceId = deviceId; | |||||
| setDeviceId(deviceId); | |||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| json_t* sampleRateJ = json_object_get(rootJ, "sampleRate"); | |||||
| if (sampleRateJ) | |||||
| setSampleRate(json_integer_value(sampleRateJ)); | |||||
| json_t* blockSizeJ = json_object_get(rootJ, "blockSize"); | |||||
| if (blockSizeJ) | |||||
| setBlockSize(json_integer_value(blockSizeJ)); | |||||
| json_t* offsetJ = json_object_get(rootJ, "offset"); | json_t* offsetJ = json_object_get(rootJ, "offset"); | ||||
| if (offsetJ) | if (offsetJ) | ||||
| offset = json_integer_value(offsetJ); | offset = json_integer_value(offsetJ); | ||||
| } | |||||
| json_t* maxChannelsJ = json_object_get(rootJ, "maxChannels"); | |||||
| if (maxChannelsJ) | |||||
| maxChannels = json_integer_value(maxChannelsJ); | |||||
| json_t* sampleRateJ = json_object_get(rootJ, "sampleRate"); | |||||
| if (sampleRateJ) | |||||
| sampleRate = json_integer_value(sampleRateJ); | |||||
| //////////////////// | |||||
| // audio | |||||
| //////////////////// | |||||
| json_t* blockSizeJ = json_object_get(rootJ, "blockSize"); | |||||
| if (blockSizeJ) | |||||
| blockSize = json_integer_value(blockSizeJ); | |||||
| void init() { | |||||
| } | |||||
| void destroy() { | |||||
| for (auto& pair : drivers) { | |||||
| delete pair.second; | |||||
| } | |||||
| drivers.clear(); | |||||
| } | |||||
| openStream(); | |||||
| void addDriver(int driverId, Driver* driver) { | |||||
| assert(driver); | |||||
| drivers.push_back(std::make_pair(driverId, driver)); | |||||
| } | } | ||||
| @@ -37,20 +37,32 @@ struct AudioInterface : Module, audio::Port { | |||||
| dsp::SampleRateConverter<NUM_AUDIO_INPUTS> inputSrc; | dsp::SampleRateConverter<NUM_AUDIO_INPUTS> inputSrc; | ||||
| dsp::SampleRateConverter<NUM_AUDIO_OUTPUTS> outputSrc; | dsp::SampleRateConverter<NUM_AUDIO_OUTPUTS> outputSrc; | ||||
| dsp::ClockDivider lightDivider; | |||||
| // Port variables | |||||
| int requestedEngineFrames = 0; | |||||
| int maxEngineFrames = 0; | |||||
| AudioInterface() { | AudioInterface() { | ||||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | ||||
| for (int i = 0; i < NUM_AUDIO_INPUTS; i++) | for (int i = 0; i < NUM_AUDIO_INPUTS; i++) | ||||
| configInput(AUDIO_INPUTS + i, string::f("To device %d", i + 1)); | configInput(AUDIO_INPUTS + i, string::f("To device %d", i + 1)); | ||||
| for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) | for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) | ||||
| configOutput(AUDIO_OUTPUTS + i, string::f("From device %d", i + 1)); | configOutput(AUDIO_OUTPUTS + i, string::f("From device %d", i + 1)); | ||||
| lightDivider.setDivision(512); | |||||
| maxChannels = std::max(NUM_AUDIO_INPUTS, NUM_AUDIO_OUTPUTS); | maxChannels = std::max(NUM_AUDIO_INPUTS, NUM_AUDIO_OUTPUTS); | ||||
| inputSrc.setQuality(6); | inputSrc.setQuality(6); | ||||
| outputSrc.setQuality(6); | outputSrc.setQuality(6); | ||||
| } | } | ||||
| ~AudioInterface() { | ~AudioInterface() { | ||||
| // Close stream here before destructing AudioInterfacePort, so the mutexes are still valid when waiting to close. | |||||
| setDeviceId(-1, 0); | |||||
| // Close stream here before destructing AudioInterfacePort, so processBuffer() etc are not called on another thread while destructing. | |||||
| setDriverId(-1); | |||||
| } | |||||
| void onReset() override { | |||||
| setDriverId(-1); | |||||
| } | } | ||||
| void onSampleRateChange(const SampleRateChangeEvent& e) override { | void onSampleRateChange(const SampleRateChangeEvent& e) override { | ||||
| @@ -59,7 +71,7 @@ struct AudioInterface : Module, audio::Port { | |||||
| } | } | ||||
| void process(const ProcessArgs& args) override { | void process(const ProcessArgs& args) override { | ||||
| // Get inputs | |||||
| // Push inputs to buffer | |||||
| if (!inputBuffer.full()) { | if (!inputBuffer.full()) { | ||||
| dsp::Frame<NUM_AUDIO_INPUTS> inputFrame; | dsp::Frame<NUM_AUDIO_INPUTS> inputFrame; | ||||
| for (int i = 0; i < NUM_AUDIO_INPUTS; i++) { | for (int i = 0; i < NUM_AUDIO_INPUTS; i++) { | ||||
| @@ -68,7 +80,7 @@ struct AudioInterface : Module, audio::Port { | |||||
| inputBuffer.push(inputFrame); | inputBuffer.push(inputFrame); | ||||
| } | } | ||||
| // Set outputs | |||||
| // Pull outputs from buffer | |||||
| if (!outputBuffer.empty()) { | if (!outputBuffer.empty()) { | ||||
| dsp::Frame<NUM_AUDIO_OUTPUTS> outputFrame = outputBuffer.shift(); | dsp::Frame<NUM_AUDIO_OUTPUTS> outputFrame = outputBuffer.shift(); | ||||
| for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) { | for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) { | ||||
| @@ -81,12 +93,16 @@ struct AudioInterface : Module, audio::Port { | |||||
| } | } | ||||
| } | } | ||||
| // Turn on light if at least one port is enabled in the nearby pair | |||||
| for (int i = 0; i < NUM_AUDIO_INPUTS / 2; i++) { | |||||
| lights[INPUT_LIGHTS + i].setBrightness(numOutputs >= 2 * i + 1); | |||||
| } | |||||
| for (int i = 0; i < NUM_AUDIO_OUTPUTS / 2; i++) { | |||||
| lights[OUTPUT_LIGHTS + i].setBrightness(numInputs >= 2 * i + 1); | |||||
| if (lightDivider.process()) { | |||||
| // Turn on light if at least one port is enabled in the nearby pair | |||||
| int numInputs = getNumInputs(); | |||||
| int numOutputs = getNumOutputs(); | |||||
| for (int i = 0; i < NUM_AUDIO_INPUTS / 2; i++) { | |||||
| lights[INPUT_LIGHTS + i].setBrightness(numOutputs >= 2 * i + 1); | |||||
| } | |||||
| for (int i = 0; i < NUM_AUDIO_OUTPUTS / 2; i++) { | |||||
| lights[OUTPUT_LIGHTS + i].setBrightness(numInputs >= 2 * i + 1); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -102,46 +118,38 @@ struct AudioInterface : Module, audio::Port { | |||||
| audio::Port::fromJson(audioJ); | audio::Port::fromJson(audioJ); | ||||
| } | } | ||||
| void onReset() override { | |||||
| setDeviceId(-1, 0); | |||||
| } | |||||
| // audio::Port | // audio::Port | ||||
| void processStream(const float* input, float* output, int frames) override { | |||||
| void processInput(const float* input, int inputStride, int frames) override { | |||||
| // Claim primary module if there is none | // Claim primary module if there is none | ||||
| if (!APP->engine->getPrimaryModule()) { | if (!APP->engine->getPrimaryModule()) { | ||||
| APP->engine->setPrimaryModule(this); | APP->engine->setPrimaryModule(this); | ||||
| } | } | ||||
| bool isPrimary = (APP->engine->getPrimaryModule() == this); | bool isPrimary = (APP->engine->getPrimaryModule() == this); | ||||
| // Clear output in case the audio driver uses this buffer in another thread before this method returns. (Not sure if any do this in practice.) | |||||
| std::memset(output, 0, sizeof(float) * numOutputs * frames); | |||||
| // Initialize sample rate converters | // Initialize sample rate converters | ||||
| int numInputs = getNumInputs(); | |||||
| int engineSampleRate = (int) APP->engine->getSampleRate(); | int engineSampleRate = (int) APP->engine->getSampleRate(); | ||||
| int sampleRate = getSampleRate(); | |||||
| double sampleRateRatio = (double) engineSampleRate / sampleRate; | double sampleRateRatio = (double) engineSampleRate / sampleRate; | ||||
| outputSrc.setRates((int) sampleRate, engineSampleRate); | |||||
| outputSrc.setRates(sampleRate, engineSampleRate); | |||||
| outputSrc.setChannels(numInputs); | outputSrc.setChannels(numInputs); | ||||
| inputSrc.setRates(engineSampleRate, (int) sampleRate); | |||||
| inputSrc.setChannels(numOutputs); | |||||
| // Consider engine buffers "too full" if they contain a bit more than the audio device's number of frames, converted to engine sample rate. | // Consider engine buffers "too full" if they contain a bit more than the audio device's number of frames, converted to engine sample rate. | ||||
| int maxEngineFrames = (int) std::ceil(frames * sampleRateRatio * 1.5); | |||||
| maxEngineFrames = (int) std::ceil(frames * sampleRateRatio * 1.5); | |||||
| // If this is a secondary audio module and the engine output buffer is too full, flush it. | // If this is a secondary audio module and the engine output buffer is too full, flush it. | ||||
| if (!isPrimary && (int) outputBuffer.size() > maxEngineFrames) { | if (!isPrimary && (int) outputBuffer.size() > maxEngineFrames) { | ||||
| outputBuffer.clear(); | outputBuffer.clear(); | ||||
| // DEBUG("%p: flushing engine output", this); | // DEBUG("%p: flushing engine output", this); | ||||
| } | } | ||||
| int requestedEngineFrames; | |||||
| if (numInputs > 0) { | if (numInputs > 0) { | ||||
| // audio input -> engine output | // audio input -> engine output | ||||
| dsp::Frame<NUM_AUDIO_OUTPUTS> inputAudioBuffer[frames]; | dsp::Frame<NUM_AUDIO_OUTPUTS> inputAudioBuffer[frames]; | ||||
| std::memset(inputAudioBuffer, 0, sizeof(inputAudioBuffer)); | std::memset(inputAudioBuffer, 0, sizeof(inputAudioBuffer)); | ||||
| for (int i = 0; i < frames; i++) { | for (int i = 0; i < frames; i++) { | ||||
| for (int j = 0; j < std::min(numInputs, NUM_AUDIO_OUTPUTS); j++) { | for (int j = 0; j < std::min(numInputs, NUM_AUDIO_OUTPUTS); j++) { | ||||
| float v = input[i * numInputs + j]; | |||||
| float v = input[i * inputStride + j]; | |||||
| inputAudioBuffer[i].samples[j] = v; | inputAudioBuffer[i].samples[j] = v; | ||||
| } | } | ||||
| } | } | ||||
| @@ -156,11 +164,23 @@ struct AudioInterface : Module, audio::Port { | |||||
| // Upper bound on number of frames so that `outputAudioFrames >= frames` at the end of this method. | // Upper bound on number of frames so that `outputAudioFrames >= frames` at the end of this method. | ||||
| requestedEngineFrames = (int) std::ceil(frames * sampleRateRatio) - inputBuffer.size(); | requestedEngineFrames = (int) std::ceil(frames * sampleRateRatio) - inputBuffer.size(); | ||||
| } | } | ||||
| } | |||||
| void processBuffer(const float* input, int inputStride, float* output, int outputStride, int frames) override { | |||||
| bool isPrimary = (APP->engine->getPrimaryModule() == this); | |||||
| // Step engine | // Step engine | ||||
| if (isPrimary && requestedEngineFrames > 0) { | if (isPrimary && requestedEngineFrames > 0) { | ||||
| APP->engine->step(requestedEngineFrames); | APP->engine->step(requestedEngineFrames); | ||||
| } | } | ||||
| } | |||||
| void processOutput(float* output, int outputStride, int frames) override { | |||||
| bool isPrimary = (APP->engine->getPrimaryModule() == this); | |||||
| int numOutputs = getNumOutputs(); | |||||
| int engineSampleRate = (int) APP->engine->getSampleRate(); | |||||
| int sampleRate = getSampleRate(); | |||||
| inputSrc.setRates(engineSampleRate, sampleRate); | |||||
| inputSrc.setChannels(numOutputs); | |||||
| if (numOutputs > 0) { | if (numOutputs > 0) { | ||||
| // engine input -> audio output | // engine input -> audio output | ||||
| @@ -173,7 +193,7 @@ struct AudioInterface : Module, audio::Port { | |||||
| for (int j = 0; j < std::min(numOutputs, NUM_AUDIO_INPUTS); j++) { | for (int j = 0; j < std::min(numOutputs, NUM_AUDIO_INPUTS); j++) { | ||||
| float v = outputAudioBuffer[i].samples[j]; | float v = outputAudioBuffer[i].samples[j]; | ||||
| v = clamp(v, -1.f, 1.f); | v = clamp(v, -1.f, 1.f); | ||||
| output[i * numOutputs + j] = v; | |||||
| output[i * outputStride + j] = v; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -196,9 +216,6 @@ struct AudioInterface : Module, audio::Port { | |||||
| inputBuffer.clear(); | inputBuffer.clear(); | ||||
| outputBuffer.clear(); | outputBuffer.clear(); | ||||
| } | } | ||||
| void onChannelsChange() override { | |||||
| } | |||||
| }; | }; | ||||
| @@ -1,6 +1,8 @@ | |||||
| #include <common.hpp> | #include <common.hpp> | ||||
| #include <random.hpp> | #include <random.hpp> | ||||
| #include <asset.hpp> | #include <asset.hpp> | ||||
| #include <audio.hpp> | |||||
| #include <rtaudio.hpp> | |||||
| #include <midi.hpp> | #include <midi.hpp> | ||||
| #include <rtmidi.hpp> | #include <rtmidi.hpp> | ||||
| #include <keyboard.hpp> | #include <keyboard.hpp> | ||||
| @@ -155,6 +157,8 @@ int main(int argc, char* argv[]) { | |||||
| INFO("Initializing environment"); | INFO("Initializing environment"); | ||||
| random::init(); | random::init(); | ||||
| network::init(); | network::init(); | ||||
| audio::init(); | |||||
| rtaudioInit(); | |||||
| midi::init(); | midi::init(); | ||||
| rtmidiInit(); | rtmidiInit(); | ||||
| keyboard::init(); | keyboard::init(); | ||||
| @@ -215,6 +219,7 @@ int main(int argc, char* argv[]) { | |||||
| } | } | ||||
| plugin::destroy(); | plugin::destroy(); | ||||
| midi::destroy(); | midi::destroy(); | ||||
| audio::destroy(); | |||||
| INFO("Destroying logger"); | INFO("Destroying logger"); | ||||
| logger::destroy(); | logger::destroy(); | ||||
| @@ -1,15 +1,14 @@ | |||||
| #include <midi.hpp> | #include <midi.hpp> | ||||
| #include <string.hpp> | #include <string.hpp> | ||||
| #include <map> | #include <map> | ||||
| #include <utility> | |||||
| namespace rack { | namespace rack { | ||||
| namespace midi { | namespace midi { | ||||
| /** Preserves the order of IDs */ | |||||
| static std::vector<int> driverIds; | |||||
| static std::map<int, Driver*> drivers; | |||||
| static std::vector<std::pair<int, Driver*>> drivers; | |||||
| //////////////////// | //////////////////// | ||||
| @@ -41,7 +40,6 @@ void OutputDevice::subscribe(Output* output) { | |||||
| } | } | ||||
| void OutputDevice::unsubscribe(Output* output) { | void OutputDevice::unsubscribe(Output* output) { | ||||
| // Remove Output from subscriptions | |||||
| auto it = subscribed.find(output); | auto it = subscribed.find(output); | ||||
| if (it != subscribed.end()) | if (it != subscribed.end()) | ||||
| subscribed.erase(it); | subscribed.erase(it); | ||||
| @@ -52,30 +50,35 @@ void OutputDevice::unsubscribe(Output* output) { | |||||
| //////////////////// | //////////////////// | ||||
| std::vector<int> Port::getDriverIds() { | std::vector<int> Port::getDriverIds() { | ||||
| std::vector<int> driverIds; | |||||
| for (auto& pair : drivers) { | |||||
| driverIds.push_back(pair.first); | |||||
| } | |||||
| return driverIds; | return driverIds; | ||||
| } | } | ||||
| std::string Port::getDriverName(int driverId) { | std::string Port::getDriverName(int driverId) { | ||||
| auto it = drivers.find(driverId); | |||||
| if (it == drivers.end()) | |||||
| return ""; | |||||
| return it->second->getName(); | |||||
| for (auto& pair : drivers) { | |||||
| if (pair.first == driverId) { | |||||
| return pair.second->getName(); | |||||
| } | |||||
| } | |||||
| return ""; | |||||
| } | } | ||||
| void Port::setDriverId(int driverId) { | void Port::setDriverId(int driverId) { | ||||
| // Unset device and driver | // Unset device and driver | ||||
| setDeviceId(-1); | setDeviceId(-1); | ||||
| if (driver) { | |||||
| driver = NULL; | |||||
| } | |||||
| driver = NULL; | |||||
| this->driverId = -1; | this->driverId = -1; | ||||
| // Set driver | // Set driver | ||||
| auto it = drivers.find(driverId); | |||||
| if (it != drivers.end()) { | |||||
| driver = it->second; | |||||
| this->driverId = driverId; | |||||
| for (auto& pair : drivers) { | |||||
| if (pair.first == driverId) { | |||||
| driver = pair.second; | |||||
| this->driverId = driverId; | |||||
| break; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -137,8 +140,8 @@ Input::~Input() { | |||||
| void Input::reset() { | void Input::reset() { | ||||
| channel = -1; | channel = -1; | ||||
| // Set first driver as default | // Set first driver as default | ||||
| if (driverIds.size() >= 1) { | |||||
| setDriverId(driverIds[0]); | |||||
| if (drivers.size() >= 1) { | |||||
| setDriverId(drivers[0].first); | |||||
| } | } | ||||
| } | } | ||||
| @@ -160,8 +163,8 @@ void Input::setDeviceId(int deviceId) { | |||||
| // Destroy device | // Destroy device | ||||
| if (driver && this->deviceId >= 0) { | if (driver && this->deviceId >= 0) { | ||||
| driver->unsubscribeInput(this->deviceId, this); | driver->unsubscribeInput(this->deviceId, this); | ||||
| inputDevice = NULL; | |||||
| } | } | ||||
| inputDevice = NULL; | |||||
| this->deviceId = -1; | this->deviceId = -1; | ||||
| // Create device | // Create device | ||||
| @@ -211,8 +214,8 @@ Output::~Output() { | |||||
| void Output::reset() { | void Output::reset() { | ||||
| channel = 0; | channel = 0; | ||||
| // Set first driver as default | // Set first driver as default | ||||
| if (driverIds.size() >= 1) { | |||||
| setDriverId(driverIds[0]); | |||||
| if (drivers.size() >= 1) { | |||||
| setDriverId(drivers[0].first); | |||||
| } | } | ||||
| } | } | ||||
| @@ -234,8 +237,8 @@ void Output::setDeviceId(int deviceId) { | |||||
| // Destroy device | // Destroy device | ||||
| if (driver && this->deviceId >= 0) { | if (driver && this->deviceId >= 0) { | ||||
| driver->unsubscribeOutput(this->deviceId, this); | driver->unsubscribeOutput(this->deviceId, this); | ||||
| outputDevice = NULL; | |||||
| } | } | ||||
| outputDevice = NULL; | |||||
| this->deviceId = -1; | this->deviceId = -1; | ||||
| // Create device | // Create device | ||||
| @@ -273,7 +276,6 @@ void init() { | |||||
| } | } | ||||
| void destroy() { | void destroy() { | ||||
| driverIds.clear(); | |||||
| for (auto& pair : drivers) { | for (auto& pair : drivers) { | ||||
| delete pair.second; | delete pair.second; | ||||
| } | } | ||||
| @@ -282,8 +284,7 @@ void destroy() { | |||||
| void addDriver(int driverId, Driver* driver) { | void addDriver(int driverId, Driver* driver) { | ||||
| assert(driver); | assert(driver); | ||||
| driverIds.push_back(driverId); | |||||
| drivers[driverId] = driver; | |||||
| drivers.push_back(std::make_pair(driverId, driver)); | |||||
| } | } | ||||
| @@ -0,0 +1,242 @@ | |||||
| #include <rtaudio.hpp> | |||||
| #include <audio.hpp> | |||||
| #include <string.hpp> | |||||
| #include <math.hpp> | |||||
| #include <system.hpp> | |||||
| #include <map> | |||||
| #pragma GCC diagnostic push | |||||
| #ifndef __clang__ | |||||
| #pragma GCC diagnostic ignored "-Wsuggest-override" | |||||
| #endif | |||||
| #include <RtAudio.h> | |||||
| #pragma GCC diagnostic pop | |||||
| namespace rack { | |||||
| struct RtAudioDevice : audio::Device { | |||||
| RtAudio *rtAudio; | |||||
| int deviceId; | |||||
| RtAudio::DeviceInfo deviceInfo; | |||||
| RtAudio::StreamParameters inputParameters; | |||||
| RtAudio::StreamParameters outputParameters; | |||||
| RtAudio::StreamOptions options; | |||||
| int blockSize = 256; | |||||
| int sampleRate = 44100; | |||||
| RtAudioDevice(RtAudio::Api api, int deviceId) { | |||||
| rtAudio = new RtAudio(api); | |||||
| if (!rtAudio) { | |||||
| throw Exception(string::f("Failed to create RtAudio driver %d", api)); | |||||
| } | |||||
| try { | |||||
| 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())); | |||||
| } | |||||
| this->deviceId = deviceId; | |||||
| openStream(); | |||||
| } | |||||
| ~RtAudioDevice() { | |||||
| closeStream(); | |||||
| delete rtAudio; | |||||
| } | |||||
| void openStream() { | |||||
| // Open new device | |||||
| if (deviceInfo.outputChannels == 0 && deviceInfo.inputChannels == 0) { | |||||
| WARN("RtAudio device %d has 0 inputs and 0 outputs", deviceId); | |||||
| return; | |||||
| } | |||||
| inputParameters = RtAudio::StreamParameters(); | |||||
| inputParameters.deviceId = deviceId; | |||||
| inputParameters.nChannels = deviceInfo.inputChannels; | |||||
| inputParameters.firstChannel = 0; | |||||
| outputParameters = RtAudio::StreamParameters(); | |||||
| outputParameters.deviceId = deviceId; | |||||
| outputParameters.nChannels = deviceInfo.outputChannels; | |||||
| outputParameters.firstChannel = 0; | |||||
| options = RtAudio::StreamOptions(); | |||||
| options.flags |= RTAUDIO_JACK_DONT_CONNECT; | |||||
| options.streamName = "VCV Rack"; | |||||
| int closestSampleRate = deviceInfo.preferredSampleRate; | |||||
| for (int sr : deviceInfo.sampleRates) { | |||||
| if (std::abs(sr - sampleRate) < std::abs(closestSampleRate - sampleRate)) { | |||||
| closestSampleRate = sr; | |||||
| } | |||||
| } | |||||
| try { | |||||
| INFO("Opening audio RtAudio device %d with %d in %d out", deviceId, inputParameters.nChannels, outputParameters.nChannels); | |||||
| rtAudio->openStream( | |||||
| outputParameters.nChannels == 0 ? NULL : &outputParameters, | |||||
| inputParameters.nChannels == 0 ? NULL : &inputParameters, | |||||
| RTAUDIO_FLOAT32, closestSampleRate, (unsigned int*) &blockSize, | |||||
| &rtAudioCallback, this, &options, NULL); | |||||
| } | |||||
| catch (RtAudioError& e) { | |||||
| WARN("Failed to open RtAudio stream: %s", e.what()); | |||||
| return; | |||||
| } | |||||
| try { | |||||
| INFO("Starting RtAudio stream %d", deviceId); | |||||
| rtAudio->startStream(); | |||||
| } | |||||
| catch (RtAudioError& e) { | |||||
| WARN("Failed to start RtAudio stream: %s", e.what()); | |||||
| return; | |||||
| } | |||||
| // Update sample rate to actual value | |||||
| sampleRate = rtAudio->getStreamSampleRate(); | |||||
| onOpenStream(); | |||||
| } | |||||
| void closeStream() { | |||||
| if (rtAudio->isStreamRunning()) { | |||||
| INFO("Stopping RtAudio stream %d", deviceId); | |||||
| try { | |||||
| rtAudio->stopStream(); | |||||
| } | |||||
| catch (RtAudioError& e) { | |||||
| WARN("Failed to stop RtAudio stream %s", e.what()); | |||||
| } | |||||
| } | |||||
| if (rtAudio->isStreamOpen()) { | |||||
| INFO("Closing RtAudio stream %d", deviceId); | |||||
| try { | |||||
| rtAudio->closeStream(); | |||||
| } | |||||
| catch (RtAudioError& e) { | |||||
| WARN("Failed to close RtAudio stream %s", e.what()); | |||||
| } | |||||
| } | |||||
| onCloseStream(); | |||||
| } | |||||
| std::vector<int> getSampleRates() override { | |||||
| std::vector<int> sampleRates(deviceInfo.sampleRates.begin(), deviceInfo.sampleRates.end()); | |||||
| return sampleRates; | |||||
| } | |||||
| int getSampleRate() override { | |||||
| return sampleRate; | |||||
| } | |||||
| void setSampleRate(int sampleRate) override { | |||||
| closeStream(); | |||||
| this->sampleRate = sampleRate; | |||||
| openStream(); | |||||
| } | |||||
| std::vector<int> getBlockSizes() override { | |||||
| return {32, 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096}; | |||||
| } | |||||
| int getBlockSize() override { | |||||
| return blockSize; | |||||
| } | |||||
| void setBlockSize(int blockSize) override { | |||||
| closeStream(); | |||||
| this->blockSize = blockSize; | |||||
| openStream(); | |||||
| } | |||||
| int getNumInputs() override { | |||||
| return inputParameters.nChannels; | |||||
| } | |||||
| int getNumOutputs() override { | |||||
| return outputParameters.nChannels; | |||||
| } | |||||
| static int rtAudioCallback(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void* userData) { | |||||
| RtAudioDevice* device = (RtAudioDevice*) userData; | |||||
| assert(device); | |||||
| int inputStride = device->getNumInputs(); | |||||
| int outputStride = device->getNumOutputs(); | |||||
| device->processBuffer((const float*) inputBuffer, inputStride, (float*) outputBuffer, outputStride, nFrames); | |||||
| return 0; | |||||
| } | |||||
| }; | |||||
| struct RtAudioDriver : audio::Driver { | |||||
| // Just for querying device IDs names | |||||
| RtAudio *rtAudio; | |||||
| // deviceId -> Device | |||||
| std::map<int, RtAudioDevice*> devices; | |||||
| RtAudioDriver(RtAudio::Api api) { | |||||
| rtAudio = new RtAudio(api); | |||||
| } | |||||
| ~RtAudioDriver() { | |||||
| assert(devices.empty()); | |||||
| delete rtAudio; | |||||
| } | |||||
| std::string getName() override { | |||||
| return RtAudio::getApiDisplayName(rtAudio->getCurrentApi()); | |||||
| } | |||||
| std::vector<int> getDeviceIds() override { | |||||
| int count = rtAudio->getDeviceCount(); | |||||
| std::vector<int> deviceIds; | |||||
| for (int i = 0; i < count; i++) | |||||
| deviceIds.push_back(i); | |||||
| return deviceIds; | |||||
| } | |||||
| std::string getDeviceName(int deviceId) override { | |||||
| if (deviceId >= 0) { | |||||
| RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(deviceId); | |||||
| return deviceInfo.name; | |||||
| } | |||||
| return ""; | |||||
| } | |||||
| audio::Device* subscribe(int deviceId, audio::Port* port) override { | |||||
| RtAudioDevice* device = devices[deviceId]; | |||||
| if (!device) { | |||||
| devices[deviceId] = device = new RtAudioDevice(rtAudio->getCurrentApi(), deviceId); | |||||
| // TODO Error check | |||||
| } | |||||
| device->subscribe(port); | |||||
| return device; | |||||
| } | |||||
| void unsubscribe(int deviceId, audio::Port* port) override { | |||||
| auto it = devices.find(deviceId); | |||||
| if (it == devices.end()) | |||||
| return; | |||||
| RtAudioDevice* device = it->second; | |||||
| device->unsubscribe(port); | |||||
| if (device->subscribed.empty()) { | |||||
| devices.erase(it); | |||||
| delete device; | |||||
| } | |||||
| } | |||||
| }; | |||||
| void rtaudioInit() { | |||||
| std::vector<RtAudio::Api> apis; | |||||
| RtAudio::getCompiledApi(apis); | |||||
| for (RtAudio::Api api : apis) { | |||||
| RtAudioDriver* driver = new RtAudioDriver(api); | |||||
| audio::addDriver((int) api, driver); | |||||
| } | |||||
| } | |||||
| } // namespace rack | |||||
| @@ -90,6 +90,8 @@ struct RtMidiDriver : midi::Driver { | |||||
| } | } | ||||
| ~RtMidiDriver() { | ~RtMidiDriver() { | ||||
| assert(inputDevices.empty()); | |||||
| assert(outputDevices.empty()); | |||||
| delete rtMidiIn; | delete rtMidiIn; | ||||
| delete rtMidiOut; | delete rtMidiOut; | ||||
| } | } | ||||