diff --git a/include/audio.hpp b/include/audio.hpp index 65150dc9..51615ce2 100644 --- a/include/audio.hpp +++ b/include/audio.hpp @@ -1,14 +1,8 @@ #pragma once #include #include - -#pragma GCC diagnostic push -#ifndef __clang__ - #pragma GCC diagnostic ignored "-Wsuggest-override" -#endif -#include -#pragma GCC diagnostic pop - +#include +#include namespace rack { @@ -18,54 +12,173 @@ namespace rack { namespace audio { +//////////////////// +// Driver +//////////////////// + +struct Port; +struct Device; + +struct Driver { + virtual ~Driver() {} + virtual std::string getName() { + return ""; + } + virtual std::vector 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 subscribed; + virtual ~Device() {} + // Called by Driver::subscribe(). + void subscribe(Port* port); + void unsubscribe(Port* port); + + // Called by Port. + virtual std::vector getSampleRates() { + return {}; + } + virtual int getSampleRate() { + return 0; + } + virtual void setSampleRate(int sampleRate) {} + + virtual std::vector 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 { - // Stream properties - int driverId = 0; - int deviceId = -1; + /** Not owned */ + Driver* driver = NULL; + Device* device = NULL; + + // Port settings int offset = 0; 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(); virtual ~Port(); std::vector getDriverIds(); - std::string getDriverName(int driverId); + int getDriverId() { + return 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 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); - void setDeviceId(int deviceId, int offset); - - std::vector getSampleRates(); - void setSampleRate(int sampleRate); - std::vector getBlockSizes(); - void setBlockSize(int blockSize); - void setChannels(int numOutputs, int numInputs); + std::vector 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 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(); 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 rack diff --git a/include/midi.hpp b/include/midi.hpp index f1bd35d4..a6ae79c1 100644 --- a/include/midi.hpp +++ b/include/midi.hpp @@ -62,7 +62,6 @@ struct Driver { virtual std::string getName() { return ""; } - virtual std::vector getInputDeviceIds() { return {}; } diff --git a/include/rtaudio.hpp b/include/rtaudio.hpp new file mode 100644 index 00000000..7a5da39c --- /dev/null +++ b/include/rtaudio.hpp @@ -0,0 +1,11 @@ +#pragma once +#include + + +namespace rack { + + +void rtaudioInit(); + + +} // namespace rack diff --git a/src/app/AudioWidget.cpp b/src/app/AudioWidget.cpp index 65667471..e4cc852b 100644 --- a/src/app/AudioWidget.cpp +++ b/src/app/AudioWidget.cpp @@ -27,13 +27,13 @@ struct AudioDriverChoice : LedDisplayChoice { item->port = port; item->driverId = driverId; item->text = port->getDriverName(driverId); - item->rightText = CHECKMARK(item->driverId == port->driverId); + item->rightText = CHECKMARK(item->driverId == port->getDriverId()); menu->addChild(item); } } void step() override { 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 != "") { text += driverName; color.a = 1.f; @@ -51,14 +51,13 @@ struct AudioDeviceItem : ui::MenuItem { int deviceId; int offset; void onAction(const event::Action& e) override { - port->setDeviceId(deviceId, offset); + port->setDeviceId(deviceId); + port->offset = offset; } }; struct AudioDeviceChoice : LedDisplayChoice { audio::Port* port; - /** Prevents devices with a ridiculous number of channels from being displayed */ - int maxTotalChannels = 128; void onAction(const event::Action& e) override { if (!port) @@ -66,24 +65,27 @@ struct AudioDeviceChoice : LedDisplayChoice { ui::Menu* menu = createMenu(); menu->addChild(createMenuLabel("Audio device")); - int deviceCount = port->getDeviceCount(); { AudioDeviceItem* item = new AudioDeviceItem; item->port = port; item->deviceId = -1; item->text = "(No device)"; - item->rightText = CHECKMARK(item->deviceId == port->deviceId); + item->rightText = CHECKMARK(item->deviceId == port->getDeviceId()); 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) { AudioDeviceItem* item = new AudioDeviceItem; item->port = port; item->deviceId = deviceId; item->offset = 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); } } @@ -128,14 +130,14 @@ struct AudioSampleRateChoice : LedDisplayChoice { item->port = port; item->sampleRate = sampleRate; 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); } } void step() override { text = (box.size.x >= 100.0) ? "Rate: " : ""; if (port) { - text += string::f("%g kHz", port->sampleRate / 1000.0); + text += string::f("%g kHz", port->getSampleRate() / 1000.0); } else { text += "0 kHz"; @@ -168,16 +170,16 @@ struct AudioBlockSizeChoice : LedDisplayChoice { AudioBlockSizeItem* item = new AudioBlockSizeItem; item->port = port; 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->rightText = CHECKMARK(item->blockSize == port->blockSize); + item->rightText = CHECKMARK(item->blockSize == port->getBlockSize()); menu->addChild(item); } } void step() override { text = (box.size.x >= 100.0) ? "Block size: " : ""; if (port) { - text += string::f("%d", port->blockSize); + text += string::f("%d", port->getBlockSize()); } else { text += "0"; diff --git a/src/audio.cpp b/src/audio.cpp index 60f6e958..b74f7632 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -1,320 +1,166 @@ #include #include -#include -#include namespace rack { namespace audio { -Port::Port() { - setDriverId(0); -} +static std::vector> drivers; -Port::~Port() { - closeStream(); -} -std::vector Port::getDriverIds() { - std::vector apis; - RtAudio::getCompiledApi(apis); - std::vector 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 Port::getDriverIds() { + std::vector 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 Port::getSampleRates() { - if (rtAudio) { - try { - RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(deviceId); - std::vector 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 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* 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()) 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, "maxChannels", json_integer(maxChannels)); - json_object_set_new(rootJ, "sampleRate", json_integer(sampleRate)); - json_object_set_new(rootJ, "blockSize", json_integer(blockSize)); return rootJ; } void Port::fromJson(json_t* rootJ) { - closeStream(); - json_t* driverJ = json_object_get(rootJ, "driver"); if (driverJ) setDriverId(json_number_value(driverJ)); @@ -323,31 +169,45 @@ 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 = 0; deviceId < getDeviceCount(); deviceId++) { + for (int deviceId : getDeviceIds()) { if (getDeviceName(deviceId) == deviceName) { - this->deviceId = deviceId; + setDeviceId(deviceId); 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"); if (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)); } diff --git a/src/core/AudioInterface.cpp b/src/core/AudioInterface.cpp index 66b2c47f..27f83882 100644 --- a/src/core/AudioInterface.cpp +++ b/src/core/AudioInterface.cpp @@ -37,20 +37,32 @@ struct AudioInterface : Module, audio::Port { dsp::SampleRateConverter inputSrc; dsp::SampleRateConverter outputSrc; + dsp::ClockDivider lightDivider; + + // Port variables + int requestedEngineFrames = 0; + int maxEngineFrames = 0; + AudioInterface() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); for (int i = 0; i < NUM_AUDIO_INPUTS; i++) configInput(AUDIO_INPUTS + i, string::f("To device %d", i + 1)); for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) configOutput(AUDIO_OUTPUTS + i, string::f("From device %d", i + 1)); + + lightDivider.setDivision(512); maxChannels = std::max(NUM_AUDIO_INPUTS, NUM_AUDIO_OUTPUTS); inputSrc.setQuality(6); outputSrc.setQuality(6); } ~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 { @@ -59,7 +71,7 @@ struct AudioInterface : Module, audio::Port { } void process(const ProcessArgs& args) override { - // Get inputs + // Push inputs to buffer if (!inputBuffer.full()) { dsp::Frame inputFrame; for (int i = 0; i < NUM_AUDIO_INPUTS; i++) { @@ -68,7 +80,7 @@ struct AudioInterface : Module, audio::Port { inputBuffer.push(inputFrame); } - // Set outputs + // Pull outputs from buffer if (!outputBuffer.empty()) { dsp::Frame outputFrame = outputBuffer.shift(); 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); } - void onReset() override { - setDeviceId(-1, 0); - } - // 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 if (!APP->engine->getPrimaryModule()) { APP->engine->setPrimaryModule(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 + int numInputs = getNumInputs(); int engineSampleRate = (int) APP->engine->getSampleRate(); + int sampleRate = getSampleRate(); double sampleRateRatio = (double) engineSampleRate / sampleRate; - outputSrc.setRates((int) sampleRate, engineSampleRate); + outputSrc.setRates(sampleRate, engineSampleRate); 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. - 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 (!isPrimary && (int) outputBuffer.size() > maxEngineFrames) { outputBuffer.clear(); // DEBUG("%p: flushing engine output", this); } - int requestedEngineFrames; if (numInputs > 0) { // audio input -> engine output dsp::Frame inputAudioBuffer[frames]; std::memset(inputAudioBuffer, 0, sizeof(inputAudioBuffer)); for (int i = 0; i < frames; i++) { 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; } } @@ -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. 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 if (isPrimary && requestedEngineFrames > 0) { 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) { // 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++) { float v = outputAudioBuffer[i].samples[j]; 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(); outputBuffer.clear(); } - - void onChannelsChange() override { - } }; diff --git a/src/main.cpp b/src/main.cpp index 6984bebe..d5ab0c81 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,8 @@ #include #include #include +#include +#include #include #include #include @@ -155,6 +157,8 @@ int main(int argc, char* argv[]) { INFO("Initializing environment"); random::init(); network::init(); + audio::init(); + rtaudioInit(); midi::init(); rtmidiInit(); keyboard::init(); @@ -215,6 +219,7 @@ int main(int argc, char* argv[]) { } plugin::destroy(); midi::destroy(); + audio::destroy(); INFO("Destroying logger"); logger::destroy(); diff --git a/src/midi.cpp b/src/midi.cpp index b555841c..82a1e2b0 100644 --- a/src/midi.cpp +++ b/src/midi.cpp @@ -1,15 +1,14 @@ #include #include #include +#include namespace rack { namespace midi { -/** Preserves the order of IDs */ -static std::vector driverIds; -static std::map drivers; +static std::vector> drivers; //////////////////// @@ -41,7 +40,6 @@ void OutputDevice::subscribe(Output* output) { } void OutputDevice::unsubscribe(Output* output) { - // Remove Output from subscriptions auto it = subscribed.find(output); if (it != subscribed.end()) subscribed.erase(it); @@ -52,30 +50,35 @@ void OutputDevice::unsubscribe(Output* output) { //////////////////// std::vector Port::getDriverIds() { + std::vector driverIds; + for (auto& pair : drivers) { + driverIds.push_back(pair.first); + } return driverIds; } 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) { // Unset device and driver setDeviceId(-1); - if (driver) { - driver = NULL; - } + driver = NULL; this->driverId = -1; // 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() { channel = -1; // 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 if (driver && this->deviceId >= 0) { driver->unsubscribeInput(this->deviceId, this); - inputDevice = NULL; } + inputDevice = NULL; this->deviceId = -1; // Create device @@ -211,8 +214,8 @@ Output::~Output() { void Output::reset() { channel = 0; // 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 if (driver && this->deviceId >= 0) { driver->unsubscribeOutput(this->deviceId, this); - outputDevice = NULL; } + outputDevice = NULL; this->deviceId = -1; // Create device @@ -273,7 +276,6 @@ void init() { } void destroy() { - driverIds.clear(); for (auto& pair : drivers) { delete pair.second; } @@ -282,8 +284,7 @@ void destroy() { void addDriver(int driverId, Driver* driver) { assert(driver); - driverIds.push_back(driverId); - drivers[driverId] = driver; + drivers.push_back(std::make_pair(driverId, driver)); } diff --git a/src/rtaudio.cpp b/src/rtaudio.cpp new file mode 100644 index 00000000..5979dcb0 --- /dev/null +++ b/src/rtaudio.cpp @@ -0,0 +1,242 @@ +#include +#include +#include +#include +#include +#include + +#pragma GCC diagnostic push +#ifndef __clang__ + #pragma GCC diagnostic ignored "-Wsuggest-override" +#endif +#include +#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 getSampleRates() override { + std::vector 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 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 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 getDeviceIds() override { + int count = rtAudio->getDeviceCount(); + std::vector 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 apis; + RtAudio::getCompiledApi(apis); + for (RtAudio::Api api : apis) { + RtAudioDriver* driver = new RtAudioDriver(api); + audio::addDriver((int) api, driver); + } +} + +} // namespace rack diff --git a/src/rtmidi.cpp b/src/rtmidi.cpp index df24af92..7f36af6a 100644 --- a/src/rtmidi.cpp +++ b/src/rtmidi.cpp @@ -90,6 +90,8 @@ struct RtMidiDriver : midi::Driver { } ~RtMidiDriver() { + assert(inputDevices.empty()); + assert(outputDevices.empty()); delete rtMidiIn; delete rtMidiOut; }