From 27cebc7675302971bd36c4443bb352dad381526e Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Fri, 14 Mar 2025 14:28:38 -0400 Subject: [PATCH 1/6] Set BundleHasStrictIdentifier to false so Rack <=2.6.0 Mac app bundles with identifier `com.vcvrack.rack` are overwritten with the new `com.vcvrack.rack2` app bundle. Set BundleIsRelocatable to true so if a user moves the Rack >=2.6.1 app bundle, the installer will find it by its identifier and upgrade (overwrite) it. --- Component.plist | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Component.plist b/Component.plist index cda5ad5d..421a812b 100644 --- a/Component.plist +++ b/Component.plist @@ -3,9 +3,9 @@ - BundleHasStrictIdentifier + BundleHasStrictIdentifier BundleIsVersionChecked - BundleIsRelocatable + BundleIsRelocatable BundleOverwriteActionupgrade RootRelativeBundlePathVCV Rack 2 Free.app From 454033dce133427ca4bc86bbce86d5cadd169efd Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Fri, 14 Mar 2025 17:49:45 -0400 Subject: [PATCH 2/6] Reorder README. --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0389f6ed..795ef08c 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,15 @@ - [Richie Hindle](http://entrian.com/audio/): Rack developer, bug fixes - [Grayscale](https://grayscale.info/): Module design, branding - Christoph Scholtes: [Library reviews](https://github.com/VCVRack/library) and [plugin toolchain](https://github.com/VCVRack/rack-plugin-toolchain) +- Translators + - German: Stephan Müsch, Norbert Denninger + - Spanish: Kevin U. Cano Guerra, Coriander V. Pines + - French: Pyer + - Italian: Alessandro Paglia + - Chinese (Simplified): NoiseTone + - Japanese: [Leo Kuroshita](https://x.com/kurogedelic) - Rack plugin developers: Authorship shown on each plugin's [VCV Library](https://library.vcvrack.com/) page - Rack users like you: [Bug reports and feature requests](https://vcvrack.com/support) -- German translation: Stephan Müsch, Norbert Denninger -- Spanish translation: Kevin U. Cano Guerra, Coriander V. Pines -- French translation: Pyer -- Italian translation: Alessandro Paglia -- Chinese (Simplified) translation: NoiseTone -- Japanese translation: [Leo Kuroshita](https://x.com/kurogedelic) ## Dependency libraries From 16dbaff7716016edd40e6756461b6f1eadfe6bdb Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Fri, 14 Mar 2025 19:01:43 -0400 Subject: [PATCH 3/6] Refactor RtAudioDriver/Device. Hardcode old RtAudio driverIds to be compatible with Rack <2.6.1 patches. --- src/rtaudio.cpp | 262 +++++++++++++++++++++++++----------------------- 1 file changed, 134 insertions(+), 128 deletions(-) diff --git a/src/rtaudio.cpp b/src/rtaudio.cpp index 06dc0d8a..a383d950 100644 --- a/src/rtaudio.cpp +++ b/src/rtaudio.cpp @@ -18,22 +18,82 @@ namespace rack { -static const std::map 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 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 getDeviceIds() override { + std::vector 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 { - RtAudio::Api api; + RtAudioDriver* driver; int deviceId; RtAudio* rtAudio; RtAudio::DeviceInfo deviceInfo; @@ -43,13 +103,13 @@ struct RtAudioDevice : audio::Device { int blockSize = 0; float sampleRate = 0; - RtAudioDevice(RtAudio::Api api, int deviceId) { - this->api = api; + RtAudioDevice(RtAudioDriver* driver, int deviceId) { + this->driver = driver; this->deviceId = deviceId; // 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()); }); @@ -59,7 +119,7 @@ struct RtAudioDevice : audio::Device { // Query device ID deviceInfo = rtAudio->getDeviceInfo(deviceId); 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(); } @@ -78,7 +138,7 @@ struct RtAudioDevice : audio::Device { void openStream() { // Open new device 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(); @@ -109,25 +169,25 @@ struct RtAudioDevice : audio::Device { if (blockSize <= 0) { // DirectSound should use a higher default block size - if (api == RtAudio::WINDOWS_DS) + if (driver->api == RtAudio::WINDOWS_DS) blockSize = 1024; else 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( outputParameters.nChannels > 0 ? &outputParameters : NULL, inputParameters.nChannels > 0 ? &inputParameters : NULL, RTAUDIO_FLOAT32, closestSampleRate, (unsigned int*) &blockSize, &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 { - 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()) { - 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 @@ -143,11 +203,11 @@ struct RtAudioDevice : audio::Device { void closeStream() { 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(); } 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(); } @@ -220,126 +280,72 @@ struct RtAudioDevice : audio::Device { }; -struct RtAudioDriver : audio::Driver { - RtAudio::Api api; - // deviceId -> Device - std::map 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 getDeviceIds() override { - std::vector 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 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() { + // Get RtAudio's driver list std::vector apis; RtAudio::getCompiledApi(apis); - // I don't like the order returned by getCompiledApi(), so reorder it here. - std::vector 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); } } From 4552a7168fed5056dd49ff1d36b0bf7e3641db62 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Sat, 15 Mar 2025 14:27:06 -0400 Subject: [PATCH 4/6] Specify Fundamental (VCV Free) plugin version and download it in dist target. Refactor getFundamentalPackagePath() to not use regex. --- .gitignore | 1 + Makefile | 10 +++++++--- src/plugin.cpp | 4 ++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 2b3b0a1b..43a0f312 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ *.res *.d *.dSYM +*.vcvplugin /build /Rack /dep diff --git a/Makefile b/Makefile index 929ffa69..1d6accc6 100644 --- a/Makefile +++ b/Makefile @@ -175,10 +175,14 @@ DIST_HTML := $(patsubst %.md, build/%.html, $(DIST_MD)) DIST_RES := res cacert.pem Core.json template.vcv LICENSE-GPLv3.txt $(DIST_HTML) translations DIST_SDK_DIR := Rack-SDK DIST_SDK = Rack-SDK-$(VERSION)-$(ARCH_NAME).zip +FUNDAMENTAL_VERSION ?= 2.6.1 +FUNDAMENTAL_FILENAME := Fundamental-$(FUNDAMENTAL_VERSION)-$(ARCH_NAME).vcvplugin dist: $(TARGET) $(STANDALONE_TARGET) $(DIST_HTML) mkdir -p dist + # Download Fundamental package if not already downloaded + [ -f "$(FUNDAMENTAL_FILENAME)" ] || curl -o "$(FUNDAMENTAL_FILENAME)" "https://api.vcvrack.com/download?slug=Fundamental&version=$(FUNDAMENTAL_VERSION)&arch=$(ARCH_NAME)" ifdef ARCH_LIN mkdir -p dist/"$(DIST_DIR)" cp $(TARGET) dist/"$(DIST_DIR)"/ @@ -190,7 +194,7 @@ ifdef ARCH_LIN ldd dist/"$(DIST_DIR)"/$(STANDALONE_TARGET) # Copy resources cp -R $(DIST_RES) dist/"$(DIST_DIR)"/ - cp plugins/Fundamental/dist/Fundamental-*.vcvplugin dist/"$(DIST_DIR)"/ + cp "$(FUNDAMENTAL_FILENAME)" dist/"$(DIST_DIR)"/ endif ifdef ARCH_MAC mkdir -p dist/"$(DIST_BUNDLE)" @@ -210,7 +214,7 @@ ifdef ARCH_MAC $(SED) 's/{VERSION}/$(VERSION)/g' dist/"$(DIST_BUNDLE)"/Contents/Info.plist cp -R icon.icns dist/"$(DIST_BUNDLE)"/Contents/Resources/ cp -R $(DIST_RES) dist/"$(DIST_BUNDLE)"/Contents/Resources/ - cp plugins/Fundamental/dist/Fundamental-*.vcvplugin dist/"$(DIST_BUNDLE)"/Contents/Resources/ + cp "$(FUNDAMENTAL_FILENAME)" dist/"$(DIST_BUNDLE)"/Contents/Resources/ endif ifdef ARCH_WIN mkdir -p dist/"$(DIST_DIR)" @@ -223,7 +227,7 @@ ifdef ARCH_WIN cp /mingw64/bin/libwinpthread-1.dll dist/"$(DIST_DIR)"/ cp /mingw64/bin/libstdc++-6.dll dist/"$(DIST_DIR)"/ cp /mingw64/bin/libgcc_s_seh-1.dll dist/"$(DIST_DIR)"/ - cp plugins/Fundamental/dist/Fundamental-*.vcvplugin dist/"$(DIST_DIR)"/ + cp "$(FUNDAMENTAL_FILENAME)" dist/"$(DIST_DIR)"/ endif diff --git a/src/plugin.cpp b/src/plugin.cpp index 3be598cb..6dd0b512 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -250,9 +250,9 @@ static void extractPackages(std::string path) { } static std::string getFundamentalPackagePath() { - std::regex r("Fundamental-.*-" + APP_OS + "-" + APP_CPU + "\\.vcvplugin"); for (const std::string& path : system::getEntries(asset::systemDir)) { - if (std::regex_match(system::getFilename(path), r)) + std::string filename = system::getFilename(path); + if (string::startsWith(filename, "Fundamental-") && string::endsWith(filename, APP_OS + "-" + APP_CPU + ".vcvplugin")) return path; } return ""; From 4d8030a5359fe0b25842bdb7a9a7603be614d1b8 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Sun, 16 Mar 2025 12:49:41 -0400 Subject: [PATCH 5/6] Clear library global state in library::destroy() in case init() is called again. --- src/library.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/library.cpp b/src/library.cpp index d8849460..7f4ef565 100644 --- a/src/library.cpp +++ b/src/library.cpp @@ -57,6 +57,10 @@ void destroy() { std::lock_guard timeoutLock(timeoutMutex); std::lock_guard appUpdateLock(appUpdateMutex); std::lock_guard updateLock(updateMutex); + + // Clear globals in case init() is called again + loginStatus = ""; + updateInfos.clear(); } From 2bcff47a7cfe90459180dfa8546f628ff801487c Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Mon, 17 Mar 2025 01:43:23 -0400 Subject: [PATCH 6/6] Update changelog. --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6bc95ee..9cffcf34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ In this document, Ctrl means Command (⌘) on Mac. +### 2.6.2 (2025-03-17) +- Fix incorrect audio driver being loaded from patches made in Rack ≤2.6.0. +- Fix Mac installer creating duplicate app bundle. + ### 2.6.1 (2025-03-14) - Add UI translations for: - German