From b08801ed88b0bac9f99f2bfab39cc51b75265325 Mon Sep 17 00:00:00 2001 From: falkTX Date: Sun, 21 Apr 2024 20:05:44 +0200 Subject: [PATCH] Start basic AU loader code Signed-off-by: falkTX --- source/backend/engine/CarlaEngine.cpp | 12 +- source/backend/plugin/CarlaPluginAU.cpp | 403 +++++++++++++++++++++++- source/backend/plugin/Makefile | 17 +- source/bridges-plugin/Makefile | 2 + 4 files changed, 414 insertions(+), 20 deletions(-) diff --git a/source/backend/engine/CarlaEngine.cpp b/source/backend/engine/CarlaEngine.cpp index 08f13decf..2dce7e9dd 100644 --- a/source/backend/engine/CarlaEngine.cpp +++ b/source/backend/engine/CarlaEngine.cpp @@ -704,14 +704,14 @@ bool CarlaEngine::addPlugin(const BinaryType btype, plugin = CarlaPlugin::newVST3(initializer); break; - case PLUGIN_AU: - plugin = CarlaPlugin::newAU(initializer); - break; - case PLUGIN_CLAP: plugin = CarlaPlugin::newCLAP(initializer); break; + case PLUGIN_AU: + plugin = CarlaPlugin::newAU(initializer); + break; + #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH case PLUGIN_INTERNAL: plugin = CarlaPlugin::newNative(initializer); @@ -1364,6 +1364,9 @@ bool CarlaEngine::loadFile(const char* const filename) // Direct plugin binaries #ifdef CARLA_OS_MAC + if (extension == "component") + return addPlugin(PLUGIN_AU, filename, nullptr, nullptr, 0, nullptr); + if (extension == "vst") return addPlugin(PLUGIN_VST2, filename, nullptr, nullptr, 0, nullptr); #else @@ -3096,6 +3099,7 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc, const bool alw case PLUGIN_VST2: case PLUGIN_VST3: case PLUGIN_CLAP: + case PLUGIN_AU: btype = getBinaryTypeFromFile(stateSave.binary); break; default: diff --git a/source/backend/plugin/CarlaPluginAU.cpp b/source/backend/plugin/CarlaPluginAU.cpp index 8a0b97c82..e926004d9 100644 --- a/source/backend/plugin/CarlaPluginAU.cpp +++ b/source/backend/plugin/CarlaPluginAU.cpp @@ -1,21 +1,416 @@ // SPDX-FileCopyrightText: 2011-2024 Filipe Coelho // SPDX-License-Identifier: GPL-2.0-or-later -#include "CarlaPlugin.hpp" +#include "CarlaPluginInternal.hpp" #include "CarlaEngine.hpp" -#include "CarlaUtils.hpp" + +#ifdef CARLA_OS_MAC +# include "CarlaBackendUtils.hpp" +# include "CarlaPluginUI.hpp" +# include "CarlaMacUtils.hpp" +# include +# include +#endif CARLA_BACKEND_START_NAMESPACE +// -------------------------------------------------------------------------------------------------------------------- + +#ifdef CARLA_OS_MAC +typedef AudioComponentPlugInInterface* (*FactoryFn)(const AudioComponentDescription*); +typedef OSStatus (*InitializeFn)(void*); +typedef OSStatus (*UninitializeFn)(void*); +typedef OSStatus (*GetPropertyInfoFn)(void*, AudioUnitPropertyID, AudioUnitScope, AudioUnitElement, UInt32*, Boolean*); +typedef OSStatus (*GetPropertyFn)(void*, AudioUnitPropertyID, AudioUnitScope, AudioUnitElement, void*, UInt32*); +typedef OSStatus (*MIDIEventFn)(void*, UInt32, UInt32, UInt32, UInt32); + +static constexpr FourCharCode getFourCharCodeFromString(const char str[4]) +{ + return (str[0] << 24) + (str[1] << 16) + (str[2] << 8) + str[3]; +} + +class CarlaPluginAU : public CarlaPlugin, + private CarlaPluginUI::Callback +{ +public: + CarlaPluginAU(CarlaEngine* const engine, const uint id) + : CarlaPlugin(engine, id), + fInterface(nullptr) + { + carla_stdout("CarlaPluginAU::CarlaPluginAU(%p, %i)", engine, id); + } + + ~CarlaPluginAU() override + { + carla_stdout("CarlaPluginAU::~CarlaPluginAU()"); + + // close UI + showCustomUI(false); + + pData->singleMutex.lock(); + pData->masterMutex.lock(); + + if (pData->client != nullptr && pData->client->isActive()) + pData->client->deactivate(true); + + if (pData->active) + { + deactivate(); + pData->active = false; + } + + if (fInterface != nullptr) + { + fInterface->Close(fInterface); + fInterface = nullptr; + } + + // if (fLastChunk != nullptr) + // { + // std::free(fLastChunk); + // fLastChunk = nullptr; + // } + + clearBuffers(); + } + + // ------------------------------------------------------------------- + // Information (base) + + PluginType getType() const noexcept override + { + return PLUGIN_AU; + } + + PluginCategory getCategory() const noexcept override + { + // TODO + return PLUGIN_CATEGORY_NONE; + } + + uint32_t getLatencyInFrames() const noexcept override + { + // TODO + return 0; + } + + // ------------------------------------------------------------------- + // Information (count) + + // ------------------------------------------------------------------- + // Information (current data) + + // ------------------------------------------------------------------- + // Information (per-plugin data) + + bool getLabel(char* const strBuf) const noexcept override + { + std::strncpy(strBuf, fLabel.buffer(), STR_MAX); + return true; + } + + bool getMaker(char* const strBuf) const noexcept override + { + std::strncpy(strBuf, fMaker.buffer(), STR_MAX); + return true; + } + + bool getRealName(char* const strBuf) const noexcept override + { + std::strncpy(strBuf, fName.buffer(), STR_MAX); + return true; + } + + // ------------------------------------------------------------------- + // Plugin state + + void reload() override + { + CARLA_SAFE_ASSERT_RETURN(pData->engine != nullptr,); + CARLA_SAFE_ASSERT_RETURN(fInterface != nullptr,); + carla_debug("CarlaPluginAU::reload() - start"); + + // Safely disable plugin for reload + const ScopedDisabler sd(this); + + if (pData->active) + deactivate(); + + clearBuffers(); + + bufferSizeChanged(pData->engine->getBufferSize()); + reloadPrograms(true); + + if (pData->active) + activate(); + + carla_debug("CarlaPluginAU::reload() - end"); + } + + // ------------------------------------------------------------------- + // Plugin processing + + void activate() noexcept override + { + CARLA_SAFE_ASSERT_RETURN(fInterface != nullptr,); + + // TODO + } + + void deactivate() noexcept override + { + CARLA_SAFE_ASSERT_RETURN(fInterface != nullptr,); + + // TODO + } + + void process(const float* const* const audioIn, + float** const audioOut, + const float* const* const cvIn, + float** const, + const uint32_t frames) override + { + // TODO + } + + // ------------------------------------------------------------------- + +protected: + void handlePluginUIClosed() override + { + carla_stdout("CarlaPluginCLAP::handlePluginUIClosed()"); + + // TODO + } + + void handlePluginUIResized(const uint width, const uint height) override + { + // TODO + } + + // ------------------------------------------------------------------- + +public: + bool init(const CarlaPluginPtr plugin, + const char* const filename, + const char* const label, + const char* const name, + const uint options) + { + CARLA_SAFE_ASSERT_RETURN(pData->engine != nullptr, false); + + // --------------------------------------------------------------- + // first checks + + if (pData->client != nullptr) + { + pData->engine->setLastError("Plugin client is already registered"); + return false; + } + + if (filename == nullptr || filename[0] == '\0') + { + pData->engine->setLastError("null filename"); + return false; + } + + if (label == nullptr) + { + pData->engine->setLastError("null label"); + return false; + } + + // --------------------------------------------------------------- + // load bundle information + + if (! fBundleLoader.load(filename)) + { + pData->engine->setLastError("Failed to load AU bundle executable"); + return false; + } + + const CFTypeRef componentsRef = CFBundleGetValueForInfoDictionaryKey(fBundleLoader.getRef(), CFSTR("AudioComponents")); + + if (componentsRef == nullptr || CFGetTypeID(componentsRef) != CFArrayGetTypeID()) + { + pData->engine->setLastError("Not an AU component"); + return false; + } + + // --------------------------------------------------------------- + // find binary matching requested label + + CFStringRef componentName; + AudioComponentDescription desc = {}; + FactoryFn factoryFn; + + const CFArrayRef components = static_cast(componentsRef); + + for (uint32_t c = 0, count = CFArrayGetCount(components); c < count; ++c) + { + // reset + desc.componentType = 0; + + const CFTypeRef componentRef = CFArrayGetValueAtIndex(components, c); + CARLA_SAFE_ASSERT_CONTINUE(componentRef != nullptr); + CARLA_SAFE_ASSERT_CONTINUE(CFGetTypeID(componentRef) == CFDictionaryGetTypeID()); + + const CFDictionaryRef component = static_cast(componentRef); + + componentName = nullptr; + CARLA_SAFE_ASSERT_CONTINUE(CFDictionaryGetValueIfPresent(component, CFSTR("name"), (const void **)&componentName)); + + CFStringRef componentFactoryFunction = nullptr; + CARLA_SAFE_ASSERT_CONTINUE(CFDictionaryGetValueIfPresent(component, CFSTR("factoryFunction"), (const void **)&componentFactoryFunction)); + + CFStringRef componentType = nullptr; + CARLA_SAFE_ASSERT_CONTINUE(CFDictionaryGetValueIfPresent(component, CFSTR("type"), (const void **)&componentType)); + CARLA_SAFE_ASSERT_CONTINUE(CFStringGetLength(componentType) == 4); + + CFStringRef componentSubType = nullptr; + CARLA_SAFE_ASSERT_CONTINUE(CFDictionaryGetValueIfPresent(component, CFSTR("subtype"), (const void **)&componentSubType)); + CARLA_SAFE_ASSERT_CONTINUE(CFStringGetLength(componentSubType) == 4); + + CFStringRef componentManufacturer = nullptr; + CARLA_SAFE_ASSERT_CONTINUE(CFDictionaryGetValueIfPresent(component, CFSTR("manufacturer"), (const void **)&componentManufacturer)); + CARLA_SAFE_ASSERT_CONTINUE(CFStringGetLength(componentManufacturer) == 4); + + factoryFn = fBundleLoader.getSymbol(componentFactoryFunction); + CARLA_SAFE_ASSERT_CONTINUE(factoryFn != nullptr); + + char clabel[15] = {}; + CFStringGetCString(componentType, clabel, 5, kCFStringEncodingASCII); + CFStringGetCString(componentSubType, clabel + 5, 5, kCFStringEncodingASCII); + CFStringGetCString(componentManufacturer, clabel + 10, 5, kCFStringEncodingASCII); + + desc.componentType = getFourCharCodeFromString(clabel); + desc.componentSubType = getFourCharCodeFromString(clabel + 5); + desc.componentManufacturer = getFourCharCodeFromString(clabel + 10); + + CARLA_SAFE_ASSERT_CONTINUE(desc.componentType != 0); + CARLA_SAFE_ASSERT_CONTINUE(desc.componentSubType != 0); + CARLA_SAFE_ASSERT_CONTINUE(desc.componentManufacturer != 0); + + clabel[4] = clabel[9] = ','; + + if (label[0] == '\0' || std::strcmp(label, clabel) == 0) + break; + } + + if (desc.componentType == 0) + { + pData->engine->setLastError("Failed to find request plugin in Component bundle"); + return false; + } + + // --------------------------------------------------------------- + // load binary + + fInterface = factoryFn(&desc); + + if (fInterface == nullptr) + { + pData->engine->setLastError("Component failed to create new interface"); + return false; + } + + const InitializeFn auInitialize = (InitializeFn)fInterface->Lookup(kAudioUnitInitializeSelect); + const UninitializeFn auUninitialize = (UninitializeFn)fInterface->Lookup(kAudioUnitUninitializeSelect); + const GetPropertyInfoFn auGetPropertyInfo = (GetPropertyInfoFn)fInterface->Lookup(kAudioUnitGetPropertyInfoSelect); + const GetPropertyFn auGetProperty = (GetPropertyFn)fInterface->Lookup(kAudioUnitGetPropertySelect); + const MIDIEventFn auMIDIEvent = (MIDIEventFn)fInterface->Lookup(kMusicDeviceMIDIEventSelect); + + if (auInitialize == nullptr || + auUninitialize == nullptr || + auGetPropertyInfo == nullptr || + auGetProperty == nullptr) + { + pData->engine->setLastError("Component does not provide all necessary functions"); + fInterface = nullptr; + return false; + } + + if (fInterface->Open(fInterface, (AudioUnit)(void*)0x1) != noErr) + { + pData->engine->setLastError("Component failed to open"); + fInterface->Close(fInterface); + fInterface = nullptr; + return false; + } + + // --------------------------------------------------------------- + // get info + + const CFIndex componentNameLen = CFStringGetLength(componentName); + char* const nameBuffer = new char[componentNameLen + 1]; + + if (CFStringGetCString(componentName, nameBuffer, componentNameLen + 1, kCFStringEncodingUTF8)) + { + if (char* const sep = std::strstr(nameBuffer, ": ")) + { + sep[0] = sep[1] = '\0'; + fName = sep + 2; + fMaker = nameBuffer; + } + else + { + fName = nameBuffer; + fMaker = nameBuffer + componentNameLen; + } + } + + fLabel = label; + pData->name = pData->engine->getUniquePluginName(name != nullptr && name[0] != '\0' ? name : fName.buffer()); + pData->filename = carla_strdup(filename); + + delete[] nameBuffer; + + // --------------------------------------------------------------- + // register client + + pData->client = pData->engine->addClient(plugin); + + if (pData->client == nullptr || ! pData->client->isOk()) + { + pData->engine->setLastError("Failed to register plugin client"); + return false; + } + + // --------------------------------------------------------------- + // set default options + + pData->options = PLUGIN_OPTION_FIXED_BUFFERS; + + return true; + } + +private: + BundleLoader fBundleLoader; + AudioComponentPlugInInterface* fInterface; + CarlaString fName; + CarlaString fLabel; + CarlaString fMaker; +}; +#endif + // ------------------------------------------------------------------------------------------------------------------- CarlaPluginPtr CarlaPlugin::newAU(const Initializer& init) { - carla_debug("CarlaPlugin::newAU({%p, \"%s\", \"%s\", \"%s\", " P_INT64 "})", - init.engine, init.filename, init.name, init.label, init.uniqueId); + carla_stdout("CarlaPlugin::newAU({%p, \"%s\", \"%s\", \"%s\", " P_INT64 "})", + init.engine, init.filename, init.label, init.name, init.uniqueId); + + #ifdef CARLA_OS_MAC + std::shared_ptr plugin(new CarlaPluginAU(init.engine, init.id)); + + if (! plugin->init(plugin, init.filename, init.label, init.name, init.options)) + return nullptr; + return plugin; + #else init.engine->setLastError("AU support not available"); return nullptr; + #endif } // ------------------------------------------------------------------------------------------------------------------- diff --git a/source/backend/plugin/Makefile b/source/backend/plugin/Makefile index df7437be7..a36f458b6 100644 --- a/source/backend/plugin/Makefile +++ b/source/backend/plugin/Makefile @@ -75,20 +75,13 @@ $(OBJDIR)/CarlaPluginJSFX.cpp.o: CarlaPluginJSFX.cpp $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(YSFX_FLAGS) -c -o $@ ifeq ($(MACOS),true) -$(OBJDIR)/CarlaPluginVST2.cpp.o: CarlaPluginVST2.cpp - -@mkdir -p $(OBJDIR) - @echo "Compiling $<" - $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -ObjC++ -c -o $@ +$(OBJDIR)/CarlaPluginAU.cpp.o: BUILD_CXX_FLAGS += -ObjC++ -$(OBJDIR)/CarlaPluginVST3.cpp.o: CarlaPluginVST3.cpp - -@mkdir -p $(OBJDIR) - @echo "Compiling $<" - $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -ObjC++ -c -o $@ +$(OBJDIR)/CarlaPluginCLAP.cpp.o: BUILD_CXX_FLAGS += -ObjC++ -$(OBJDIR)/CarlaPluginCLAP.cpp.o: CarlaPluginCLAP.cpp - -@mkdir -p $(OBJDIR) - @echo "Compiling $<" - $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -ObjC++ -c -o $@ +$(OBJDIR)/CarlaPluginVST2.cpp.o: BUILD_CXX_FLAGS += -ObjC++ + +$(OBJDIR)/CarlaPluginVST3.cpp.o: BUILD_CXX_FLAGS += -ObjC++ endif $(OBJDIR)/%.cpp.o: %.cpp diff --git a/source/bridges-plugin/Makefile b/source/bridges-plugin/Makefile index c272047a2..45afdf1eb 100644 --- a/source/bridges-plugin/Makefile +++ b/source/bridges-plugin/Makefile @@ -276,6 +276,8 @@ $(OBJDIR)/CarlaEng%.cpp.o: $(CWD)/backend/engine/CarlaEng%.cpp $(OBJDIR)/CarlaPluginJSFX.cpp.o: BUILD_CXX_FLAGS += $(YSFX_FLAGS) ifeq ($(MACOS),true) +$(OBJDIR)/CarlaPluginAU.cpp.o: BUILD_CXX_FLAGS += -ObjC++ + $(OBJDIR)/CarlaPluginCLAP.cpp.o: BUILD_CXX_FLAGS += -ObjC++ $(OBJDIR)/CarlaPluginVST2.cpp.o: BUILD_CXX_FLAGS += -ObjC++