| @@ -18,22 +18,82 @@ | |||||
| namespace rack { | namespace rack { | ||||
| static const std::map<RtAudio::Api, std::string> RTAUDIO_API_NAMES = { | |||||
| {RtAudio::LINUX_ALSA, "ALSA"}, | |||||
| {RtAudio::UNIX_JACK, "JACK"}, | |||||
| {RtAudio::LINUX_PULSE, "PulseAudio"}, | |||||
| {RtAudio::LINUX_OSS, "OSS"}, | |||||
| {RtAudio::WINDOWS_WASAPI, "WASAPI"}, | |||||
| {RtAudio::WINDOWS_ASIO, "ASIO"}, | |||||
| {RtAudio::WINDOWS_DS, "DirectSound"}, | |||||
| {RtAudio::MACOSX_CORE, "Core Audio"}, | |||||
| {RtAudio::RTAUDIO_DUMMY, "Dummy"}, | |||||
| {RtAudio::UNSPECIFIED, "Unspecified"}, | |||||
| struct RtAudioDevice; | |||||
| struct RtAudioDriver : audio::Driver { | |||||
| RtAudio::Api api; | |||||
| std::string name; | |||||
| RtAudio* rtAudio = NULL; | |||||
| // deviceId -> Device | |||||
| std::map<int, RtAudioDevice*> devices; | |||||
| RtAudioDriver(RtAudio::Api api, std::string name) { | |||||
| this->api = api; | |||||
| this->name = name; | |||||
| INFO("Creating RtAudio %s driver", name.c_str()); | |||||
| rtAudio = new RtAudio(api, [](RtAudioErrorType type, const std::string& errorText) { | |||||
| WARN("RtAudio error %d: %s", type, errorText.c_str()); | |||||
| }); | |||||
| rtAudio->showWarnings(false); | |||||
| } | |||||
| ~RtAudioDriver() { | |||||
| assert(devices.empty()); | |||||
| if (rtAudio) | |||||
| delete rtAudio; | |||||
| } | |||||
| std::string getName() override { | |||||
| return name; | |||||
| } | |||||
| std::vector<int> getDeviceIds() override { | |||||
| std::vector<int> deviceIds; | |||||
| if (rtAudio) { | |||||
| for (unsigned int id : rtAudio->getDeviceIds()) { | |||||
| deviceIds.push_back(id); | |||||
| } | |||||
| } | |||||
| return deviceIds; | |||||
| } | |||||
| std::string getDeviceName(int deviceId) override { | |||||
| if (rtAudio) { | |||||
| RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(deviceId); | |||||
| if (deviceInfo.ID > 0) | |||||
| return deviceInfo.name; | |||||
| } | |||||
| return ""; | |||||
| } | |||||
| int getDeviceNumInputs(int deviceId) override { | |||||
| if (rtAudio) { | |||||
| RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(deviceId); | |||||
| if (deviceInfo.ID > 0) | |||||
| return deviceInfo.inputChannels; | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| int getDeviceNumOutputs(int deviceId) override { | |||||
| if (rtAudio) { | |||||
| RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(deviceId); | |||||
| if (deviceInfo.ID > 0) | |||||
| return deviceInfo.outputChannels; | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| audio::Device* subscribe(int deviceId, audio::Port* port) override; | |||||
| void unsubscribe(int deviceId, audio::Port* port) override; | |||||
| }; | }; | ||||
| struct RtAudioDevice : audio::Device { | struct RtAudioDevice : audio::Device { | ||||
| RtAudio::Api api; | |||||
| RtAudioDriver* driver; | |||||
| int deviceId; | int deviceId; | ||||
| RtAudio* rtAudio; | RtAudio* rtAudio; | ||||
| RtAudio::DeviceInfo deviceInfo; | RtAudio::DeviceInfo deviceInfo; | ||||
| @@ -43,13 +103,13 @@ struct RtAudioDevice : audio::Device { | |||||
| int blockSize = 0; | int blockSize = 0; | ||||
| float sampleRate = 0; | float sampleRate = 0; | ||||
| RtAudioDevice(RtAudio::Api api, int deviceId) { | |||||
| this->api = api; | |||||
| RtAudioDevice(RtAudioDriver* driver, int deviceId) { | |||||
| this->driver = driver; | |||||
| this->deviceId = deviceId; | this->deviceId = deviceId; | ||||
| // Create RtAudio object | // Create RtAudio object | ||||
| INFO("Creating RtAudio %s device", RTAUDIO_API_NAMES.at(api).c_str()); | |||||
| rtAudio = new RtAudio(api, [](RtAudioErrorType type, const std::string& errorText) { | |||||
| INFO("Creating RtAudio %s device", driver->getName().c_str()); | |||||
| rtAudio = new RtAudio(driver->api, [](RtAudioErrorType type, const std::string& errorText) { | |||||
| WARN("RtAudio error %d: %s", type, errorText.c_str()); | WARN("RtAudio error %d: %s", type, errorText.c_str()); | ||||
| }); | }); | ||||
| @@ -59,7 +119,7 @@ struct RtAudioDevice : audio::Device { | |||||
| // Query device ID | // Query device ID | ||||
| deviceInfo = rtAudio->getDeviceInfo(deviceId); | deviceInfo = rtAudio->getDeviceInfo(deviceId); | ||||
| if (deviceInfo.ID == 0) | if (deviceInfo.ID == 0) | ||||
| throw Exception("Failed to query RtAudio %s device %d", RTAUDIO_API_NAMES.at(api).c_str(), deviceId); | |||||
| throw Exception("Failed to query RtAudio %s device %d", driver->getName().c_str(), deviceId); | |||||
| openStream(); | openStream(); | ||||
| } | } | ||||
| @@ -78,7 +138,7 @@ struct RtAudioDevice : audio::Device { | |||||
| void openStream() { | void openStream() { | ||||
| // Open new device | // Open new device | ||||
| if (deviceInfo.outputChannels == 0 && deviceInfo.inputChannels == 0) { | if (deviceInfo.outputChannels == 0 && deviceInfo.inputChannels == 0) { | ||||
| throw Exception("RtAudio %s device %d has 0 inputs and 0 outputs", RTAUDIO_API_NAMES.at(api).c_str(), deviceId); | |||||
| throw Exception("RtAudio %s device %d has 0 inputs and 0 outputs", driver->getName().c_str(), deviceId); | |||||
| } | } | ||||
| inputParameters = RtAudio::StreamParameters(); | inputParameters = RtAudio::StreamParameters(); | ||||
| @@ -109,25 +169,25 @@ struct RtAudioDevice : audio::Device { | |||||
| if (blockSize <= 0) { | if (blockSize <= 0) { | ||||
| // DirectSound should use a higher default block size | // DirectSound should use a higher default block size | ||||
| if (api == RtAudio::WINDOWS_DS) | |||||
| if (driver->api == RtAudio::WINDOWS_DS) | |||||
| blockSize = 1024; | blockSize = 1024; | ||||
| else | else | ||||
| blockSize = 256; | blockSize = 256; | ||||
| } | } | ||||
| INFO("Opening RtAudio %s device %d: %s (%d in, %d out, %d sample rate, %d block size)", RTAUDIO_API_NAMES.at(api).c_str(), deviceId, deviceInfo.name.c_str(), inputParameters.nChannels, outputParameters.nChannels, closestSampleRate, blockSize); | |||||
| INFO("Opening RtAudio %s device %d: %s (%d in, %d out, %d sample rate, %d block size)", driver->getName().c_str(), deviceId, deviceInfo.name.c_str(), inputParameters.nChannels, outputParameters.nChannels, closestSampleRate, blockSize); | |||||
| if (rtAudio->openStream( | if (rtAudio->openStream( | ||||
| outputParameters.nChannels > 0 ? &outputParameters : NULL, | outputParameters.nChannels > 0 ? &outputParameters : NULL, | ||||
| inputParameters.nChannels > 0 ? &inputParameters : NULL, | inputParameters.nChannels > 0 ? &inputParameters : NULL, | ||||
| RTAUDIO_FLOAT32, closestSampleRate, (unsigned int*) &blockSize, | RTAUDIO_FLOAT32, closestSampleRate, (unsigned int*) &blockSize, | ||||
| &rtAudioCallback, this, &options)) { | &rtAudioCallback, this, &options)) { | ||||
| throw Exception("Failed to open RtAudio %s device %d", RTAUDIO_API_NAMES.at(api).c_str(), deviceId); | |||||
| throw Exception("Failed to open RtAudio %s device %d", driver->getName().c_str(), deviceId); | |||||
| } | } | ||||
| try { | try { | ||||
| INFO("Starting RtAudio %s device %d", RTAUDIO_API_NAMES.at(api).c_str(), deviceId); | |||||
| INFO("Starting RtAudio %s device %d", driver->getName().c_str(), deviceId); | |||||
| if (rtAudio->startStream()) { | if (rtAudio->startStream()) { | ||||
| throw Exception("Failed to start RtAudio %s device %d", RTAUDIO_API_NAMES.at(api).c_str(), deviceId); | |||||
| throw Exception("Failed to start RtAudio %s device %d", driver->getName().c_str(), deviceId); | |||||
| } | } | ||||
| // Update sample rate to actual value | // Update sample rate to actual value | ||||
| @@ -143,11 +203,11 @@ struct RtAudioDevice : audio::Device { | |||||
| void closeStream() { | void closeStream() { | ||||
| if (rtAudio->isStreamRunning()) { | if (rtAudio->isStreamRunning()) { | ||||
| INFO("Stopping RtAudio %s device %d", RTAUDIO_API_NAMES.at(api).c_str(), deviceId); | |||||
| INFO("Stopping RtAudio %s device %d", driver->getName().c_str(), deviceId); | |||||
| rtAudio->stopStream(); | rtAudio->stopStream(); | ||||
| } | } | ||||
| if (rtAudio->isStreamOpen()) { | if (rtAudio->isStreamOpen()) { | ||||
| INFO("Closing RtAudio %s device %d", RTAUDIO_API_NAMES.at(api).c_str(), deviceId); | |||||
| INFO("Closing RtAudio %s device %d", driver->getName().c_str(), deviceId); | |||||
| rtAudio->closeStream(); | rtAudio->closeStream(); | ||||
| } | } | ||||
| @@ -220,126 +280,72 @@ struct RtAudioDevice : audio::Device { | |||||
| }; | }; | ||||
| struct RtAudioDriver : audio::Driver { | |||||
| RtAudio::Api api; | |||||
| // deviceId -> Device | |||||
| std::map<int, RtAudioDevice*> devices; | |||||
| RtAudio* rtAudio = NULL; | |||||
| RtAudioDriver(RtAudio::Api api) { | |||||
| this->api = api; | |||||
| audio::Device* RtAudioDriver::subscribe(int deviceId, audio::Port* port) { | |||||
| RtAudioDevice* device; | |||||
| auto it = devices.find(deviceId); | |||||
| if (it == devices.end()) { | |||||
| // ASIO only allows one device to be used simultaneously | |||||
| if (api == RtAudio::WINDOWS_ASIO && devices.size() >= 1) | |||||
| throw Exception("ASIO driver only allows one audio device to be used simultaneously"); | |||||
| INFO("Creating RtAudio %s driver", RTAUDIO_API_NAMES.at(api).c_str()); | |||||
| rtAudio = new RtAudio(api, [](RtAudioErrorType type, const std::string& errorText) { | |||||
| WARN("RtAudio error %d: %s", type, errorText.c_str()); | |||||
| }); | |||||
| rtAudio->showWarnings(false); | |||||
| // Can throw Exception | |||||
| device = new RtAudioDevice(this, deviceId); | |||||
| devices[deviceId] = device; | |||||
| } | } | ||||
| ~RtAudioDriver() { | |||||
| assert(devices.empty()); | |||||
| if (rtAudio) | |||||
| delete rtAudio; | |||||
| } | |||||
| std::string getName() override { | |||||
| return RTAUDIO_API_NAMES.at(api); | |||||
| } | |||||
| std::vector<int> getDeviceIds() override { | |||||
| std::vector<int> deviceIds; | |||||
| if (rtAudio) { | |||||
| for (unsigned int id : rtAudio->getDeviceIds()) { | |||||
| deviceIds.push_back(id); | |||||
| } | |||||
| } | |||||
| return deviceIds; | |||||
| else { | |||||
| device = it->second; | |||||
| } | } | ||||
| std::string getDeviceName(int deviceId) override { | |||||
| if (rtAudio) { | |||||
| RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(deviceId); | |||||
| if (deviceInfo.ID > 0) | |||||
| return deviceInfo.name; | |||||
| } | |||||
| return ""; | |||||
| } | |||||
| int getDeviceNumInputs(int deviceId) override { | |||||
| if (rtAudio) { | |||||
| RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(deviceId); | |||||
| if (deviceInfo.ID > 0) | |||||
| return deviceInfo.inputChannels; | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| device->subscribe(port); | |||||
| return device; | |||||
| } | |||||
| int getDeviceNumOutputs(int deviceId) override { | |||||
| if (rtAudio) { | |||||
| RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(deviceId); | |||||
| if (deviceInfo.ID > 0) | |||||
| return deviceInfo.outputChannels; | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| audio::Device* subscribe(int deviceId, audio::Port* port) override { | |||||
| RtAudioDevice* device; | |||||
| auto it = devices.find(deviceId); | |||||
| if (it == devices.end()) { | |||||
| // ASIO only allows one device to be used simultaneously | |||||
| if (api == RtAudio::WINDOWS_ASIO && devices.size() >= 1) | |||||
| throw Exception("ASIO driver only allows one audio device to be used simultaneously"); | |||||
| // Can throw Exception | |||||
| device = new RtAudioDevice(api, deviceId); | |||||
| devices[deviceId] = device; | |||||
| } | |||||
| else { | |||||
| device = it->second; | |||||
| } | |||||
| void RtAudioDriver::unsubscribe(int deviceId, audio::Port* port) { | |||||
| auto it = devices.find(deviceId); | |||||
| if (it == devices.end()) | |||||
| return; | |||||
| RtAudioDevice* device = it->second; | |||||
| device->unsubscribe(port); | |||||
| device->subscribe(port); | |||||
| return device; | |||||
| if (device->subscribed.empty()) { | |||||
| devices.erase(it); | |||||
| delete 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; | |||||
| } | |||||
| } | |||||
| struct ApiInfo { | |||||
| // Should match indices in https://github.com/VCVRack/rtaudio/blob/ece277bd839603648c80c8a5f145678e13bc23f3/RtAudio.cpp#L107-L118 | |||||
| int driverId; | |||||
| RtAudio::Api rtApi; | |||||
| // Used instead of RtAudio::getApiName() | |||||
| std::string name; | |||||
| }; | |||||
| // The vector order here defines the order in the audio driver menu | |||||
| static const std::vector<ApiInfo> API_INFOS = { | |||||
| {1, RtAudio::LINUX_ALSA, "ALSA"}, | |||||
| {2, RtAudio::LINUX_PULSE, "PulseAudio"}, | |||||
| {4, RtAudio::UNIX_JACK, "JACK"}, | |||||
| {5, RtAudio::MACOSX_CORE, "Core Audio"}, | |||||
| {6, RtAudio::WINDOWS_WASAPI, "WASAPI"}, | |||||
| {7, RtAudio::WINDOWS_ASIO, "ASIO"}, | |||||
| {8, RtAudio::WINDOWS_DS, "DirectSound"}, | |||||
| }; | }; | ||||
| void rtaudioInit() { | void rtaudioInit() { | ||||
| // Get RtAudio's driver list | |||||
| std::vector<RtAudio::Api> apis; | std::vector<RtAudio::Api> apis; | ||||
| RtAudio::getCompiledApi(apis); | RtAudio::getCompiledApi(apis); | ||||
| // I don't like the order returned by getCompiledApi(), so reorder it here. | |||||
| std::vector<RtAudio::Api> orderedApis = { | |||||
| RtAudio::LINUX_ALSA, | |||||
| RtAudio::LINUX_PULSE, | |||||
| RtAudio::UNIX_JACK, | |||||
| RtAudio::LINUX_OSS, | |||||
| RtAudio::WINDOWS_WASAPI, | |||||
| RtAudio::WINDOWS_ASIO, | |||||
| RtAudio::WINDOWS_DS, | |||||
| RtAudio::MACOSX_CORE, | |||||
| }; | |||||
| for (RtAudio::Api api : orderedApis) { | |||||
| auto it = std::find(apis.begin(), apis.end(), api); | |||||
| if (it != apis.end()) { | |||||
| RtAudioDriver* driver = new RtAudioDriver(api); | |||||
| audio::addDriver((int) api, driver); | |||||
| } | |||||
| for (const ApiInfo& apiInfo : API_INFOS) { | |||||
| auto it = std::find(apis.begin(), apis.end(), apiInfo.rtApi); | |||||
| if (it == apis.end()) | |||||
| continue; | |||||
| // Create and add driver | |||||
| RtAudioDriver* driver = new RtAudioDriver(apiInfo.rtApi, apiInfo.name); | |||||
| audio::addDriver(apiInfo.driverId, driver); | |||||
| } | } | ||||
| } | } | ||||