// SPDX-FileCopyrightText: 2011-2024 Filipe Coelho // SPDX-License-Identifier: GPL-2.0-or-later #include "CarlaPluginInternal.hpp" #include "CarlaEngine.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 (*SetPropertyFn)(void*, AudioUnitPropertyID, AudioUnitScope, AudioUnitElement, const void*, UInt32); typedef OSStatus (*GetParameterFn)(void*, AudioUnitParameterID, AudioUnitScope, AudioUnitElement, AudioUnitParameterValue*); typedef OSStatus (*SetParameterFn)(void*, AudioUnitParameterID, AudioUnitScope, AudioUnitElement, AudioUnitParameterValue, UInt32); typedef OSStatus (*ScheduleParametersFn)(void*, const AudioUnitParameterEvent*, UInt32); typedef OSStatus (*ResetFn)(void*, AudioUnitScope, AudioUnitElement); typedef OSStatus (*RenderFn)(void*, AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32, UInt32, AudioBufferList*); 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), fAudioBufferData(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 (fAudioBufferData != nullptr) { std::free(fAudioBufferData); fAudioBufferData = 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) uint getOptionsAvailable() const noexcept override { // TODO return 0x0; } float getParameterValue(const uint32_t parameterId) const noexcept override { CARLA_SAFE_ASSERT_RETURN(fInterface != nullptr, 0.f); CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count, 0.f); const AudioUnitParameterID paramId = pData->param.data[parameterId].rindex; AudioUnitParameterValue value = 0.f; if (fFunctions.getParameter(fInterface, paramId, kAudioUnitScope_Global, 0, &value) == noErr) return value; return 0.f; } 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; } bool getParameterName(const uint32_t parameterId, char* const strBuf) const noexcept override { CARLA_SAFE_ASSERT_RETURN(fInterface != nullptr, false); CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count, false); const AudioUnitParameterID paramId = pData->param.data[parameterId].rindex; AudioUnitParameterInfo info = {}; UInt32 outDataSize = sizeof(AudioUnitParameterInfo); if (fFunctions.getProperty(fInterface, kAudioUnitProperty_ParameterInfo, kAudioUnitScope_Global, paramId, &info, &outDataSize) == noErr) { if (info.flags & kAudioUnitParameterFlag_HasCFNameString) { *strBuf = '\0'; if (! CFStringGetCString(info.cfNameString, strBuf, std::min(STR_MAX, CFStringGetLength(info.cfNameString) + 1), kCFStringEncodingUTF8)) { carla_stdout("CFStringGetCString fail '%s'", info.name); std::strncpy(strBuf, info.name, STR_MAX); } else { carla_stdout("CFStringGetCString ok '%s' '%s'", info.name, strBuf); std::strncpy(strBuf, info.name, STR_MAX); } if (info.flags & kAudioUnitParameterFlag_CFNameRelease) CFRelease(info.cfNameString); return true; } carla_stdout("CFStringGetCString not used '%s'", info.name); std::strncpy(strBuf, info.name, STR_MAX); return true; } carla_safe_assert("fFunctions.getProperty(...)", __FILE__, __LINE__); return CarlaPlugin::getParameterName(parameterId, strBuf); } // ------------------------------------------------------------------- // Set data (plugin-specific stuff) void setParameterValue(const uint32_t parameterId, const float value, const bool sendGui, const bool sendOsc, const bool sendCallback) noexcept override { CARLA_SAFE_ASSERT_RETURN(fInterface != nullptr,); CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count,); const AudioUnitParameterID paramId = pData->param.data[parameterId].rindex; const float fixedValue = pData->param.getFixedValue(parameterId, value); fFunctions.setParameter(fInterface, paramId, kAudioUnitScope_Global, 0, value, 0); CarlaPlugin::setParameterValue(parameterId, fixedValue, sendGui, sendOsc, sendCallback); } void setParameterValueRT(const uint32_t parameterId, const float value, const uint32_t frameOffset, const bool sendCallbackLater) noexcept override { CARLA_SAFE_ASSERT_RETURN(fInterface != nullptr,); CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count,); const AudioUnitParameterID paramId = pData->param.data[parameterId].rindex; const float fixedValue = pData->param.getFixedValue(parameterId, value); // TODO use scheduled events fFunctions.setParameter(fInterface, paramId, kAudioUnitScope_Global, 0, value, frameOffset); CarlaPlugin::setParameterValueRT(parameterId, fixedValue, frameOffset, sendCallbackLater); } // ---------------------------------------------------------------------------------------------------------------- // Plugin state void reload() override { CARLA_SAFE_ASSERT_RETURN(pData->engine != nullptr,); CARLA_SAFE_ASSERT_RETURN(fInterface != nullptr,); carla_debug("CarlaPluginAU::reload() - start"); const EngineProcessMode processMode = pData->engine->getProccessMode(); // Safely disable plugin for reload const ScopedDisabler sd(this); if (pData->active) deactivate(); clearBuffers(); uint32_t audioIns, audioOuts, parametersIns, parametersOuts; audioIns = audioOuts = parametersIns = parametersOuts = 0; bool needsCtrlIn, needsCtrlOut, hasMidiIn, hasMidiOut; needsCtrlIn = needsCtrlOut = hasMidiIn = hasMidiOut = false; CarlaString portName; const uint portNameSize = pData->engine->getMaxPortNameSize(); UInt32 outDataSize; Boolean outWritable = false; // audio ports outDataSize = 0; if (fFunctions.getPropertyInfo(fInterface, kAudioUnitProperty_SupportedNumChannels, kAudioUnitScope_Global, 0, &outDataSize, &outWritable) == noErr && outDataSize != 0 && outDataSize % sizeof(AUChannelInfo) == 0) { const uint32_t numChannels = outDataSize / sizeof(AUChannelInfo); AUChannelInfo* const channelInfo = new AUChannelInfo[numChannels]; carla_stdout("kAudioUnitProperty_SupportedNumChannels returns %u configs", numChannels); if (fFunctions.getProperty(fInterface, kAudioUnitProperty_SupportedNumChannels, kAudioUnitScope_Global, 0, channelInfo, &outDataSize) == noErr && outDataSize == numChannels * sizeof(AUChannelInfo)) { AUChannelInfo* highestInfo = &channelInfo[0]; carla_stdout("kAudioUnitProperty_SupportedNumChannels returns {%d,%d}... config", channelInfo[0].inChannels, channelInfo[0].outChannels); for (uint32_t i=0; i highestInfo->inChannels && channelInfo[i].outChannels > highestInfo->outChannels) { highestInfo = &channelInfo[i]; } } audioIns = std::min(64, highestInfo->inChannels); audioOuts = std::min(64, highestInfo->outChannels); } else { carla_stdout("kAudioUnitProperty_SupportedNumChannels failed"); } delete[] channelInfo; } else { outDataSize = 0; if (fFunctions.getPropertyInfo(fInterface, kAudioUnitProperty_ElementCount, kAudioUnitScope_Input, 0, &outDataSize, &outWritable) == noErr && outDataSize == sizeof(UInt32)) { UInt32 count = 0; if (fFunctions.getProperty(fInterface, kAudioUnitProperty_ElementCount, kAudioUnitScope_Input, 0, &count, &outDataSize) == noErr && outDataSize == sizeof(UInt32) && count != 0) { AudioStreamBasicDescription desc; std::memset(&desc, 0, sizeof(desc)); outDataSize = sizeof(AudioStreamBasicDescription); if (fFunctions.getProperty(fInterface, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &desc, &outDataSize) == noErr) audioIns = std::min(64, desc.mChannelsPerFrame); } } outDataSize = 0; if (fFunctions.getPropertyInfo(fInterface, kAudioUnitProperty_ElementCount, kAudioUnitScope_Output, 0, &outDataSize, &outWritable) == noErr && outDataSize == sizeof(UInt32)) { UInt32 count = 0; if (fFunctions.getProperty(fInterface, kAudioUnitProperty_ElementCount, kAudioUnitScope_Output, 0, &count, &outDataSize) == noErr && outDataSize == sizeof(UInt32) && count != 0) { AudioStreamBasicDescription desc; std::memset(&desc, 0, sizeof(desc)); outDataSize = sizeof(AudioStreamBasicDescription); if (fFunctions.getProperty(fInterface, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &desc, &outDataSize) == noErr) audioOuts = std::min(64, desc.mChannelsPerFrame); } } } if (audioIns > 0) { pData->audioIn.createNew(audioIns); } if (audioOuts > 0) { pData->audioOut.createNew(audioOuts); needsCtrlIn = true; } std::free(fAudioBufferData); if (const uint32_t numBuffers = std::max(audioIns, audioOuts)) { fAudioBufferData = static_cast(std::malloc(sizeof(uint32_t) + sizeof(AudioBuffer) * numBuffers)); fAudioBufferData->mNumberBuffers = numBuffers; for (uint32_t i = 0; i < numBuffers; ++i) fAudioBufferData->mBuffers[i].mNumberChannels = 1; } else { fAudioBufferData = static_cast(std::malloc(sizeof(uint32_t))); fAudioBufferData->mNumberBuffers = 0; } // Audio Ins for (uint32_t i=0; i < audioIns; ++i) { portName.clear(); if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) { portName = pData->name; portName += ":"; } if (audioIns > 1) { portName += "input_"; portName += CarlaString(i + 1); } else portName += "input"; portName.truncate(portNameSize); pData->audioIn.ports[i].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, true, i); pData->audioIn.ports[i].rindex = i; } // Audio Outs for (uint32_t i=0; i < audioOuts; ++i) { portName.clear(); if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) { portName = pData->name; portName += ":"; } if (audioOuts > 1) { portName += "output_"; portName += CarlaString(i + 1); } else portName += "output"; portName.truncate(portNameSize); pData->audioOut.ports[i].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, false, i); pData->audioOut.ports[i].rindex = i; } // parameters outDataSize = 0; if (fFunctions.getPropertyInfo(fInterface, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0, &outDataSize, &outWritable) == noErr && outDataSize != 0 && outDataSize % sizeof(AudioUnitParameterID) == 0) { const uint32_t numParams = outDataSize / sizeof(AudioUnitParameterID); AudioUnitParameterID* const paramIds = new AudioUnitParameterID[numParams]; if (fFunctions.getProperty(fInterface, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0, paramIds, &outDataSize) == noErr && outDataSize == numParams * sizeof(AudioUnitParameterID)) { pData->param.createNew(numParams, false); AudioUnitParameterInfo info; float min, max, def, step, stepSmall, stepLarge; for (uint32_t i=0; iparam.data[i].index = static_cast(i); pData->param.data[i].rindex = static_cast(paramIds[i]); if (info.flags & kAudioUnitParameterFlag_IsWritable) { pData->param.data[i].type = PARAMETER_INPUT; needsCtrlIn = true; } else if (info.flags & (kAudioUnitParameterFlag_IsReadable|kAudioUnitParameterFlag_MeterReadOnly)) { pData->param.data[i].type = PARAMETER_OUTPUT; needsCtrlOut = true; } else { pData->param.data[i].type = PARAMETER_UNKNOWN; continue; } min = info.minValue; max = info.maxValue; def = info.defaultValue; if (min > max) max = min; if (carla_isEqual(min, max)) { carla_stderr2("WARNING - Broken plugin parameter '%s': max == min", info.name); max = min + 0.1f; } if (def < min) def = min; else if (def > max) def = max; pData->param.data[i].hints |= PARAMETER_IS_ENABLED; if ((info.flags & kAudioUnitParameterFlag_NonRealTime) == 0) { pData->param.data[i].hints |= PARAMETER_IS_AUTOMATABLE; pData->param.data[i].hints |= PARAMETER_CAN_BE_CV_CONTROLLED; } if (info.unit == kAudioUnitParameterUnit_Boolean) { step = max - min; stepSmall = step; stepLarge = step; pData->param.data[i].hints |= PARAMETER_IS_BOOLEAN; } else if (info.unit == kAudioUnitParameterUnit_Indexed) { step = 1.0f; stepSmall = 1.0f; stepLarge = 10.0f; pData->param.data[i].hints |= PARAMETER_IS_INTEGER; } else { float range = max - min; step = range/100.0f; stepSmall = range/1000.0f; stepLarge = range/10.0f; } pData->param.ranges[i].min = min; pData->param.ranges[i].max = max; pData->param.ranges[i].def = def; pData->param.ranges[i].step = step; pData->param.ranges[i].stepSmall = stepSmall; pData->param.ranges[i].stepLarge = stepLarge; } } delete[] paramIds; } if (needsCtrlIn || hasMidiIn) { portName.clear(); if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) { portName = pData->name; portName += ":"; } portName += "events-in"; portName.truncate(portNameSize); pData->event.portIn = (CarlaEngineEventPort*)pData->client->addPort(kEnginePortTypeEvent, portName, true, 0); #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH pData->event.cvSourcePorts = pData->client->createCVSourcePorts(); #endif } if (needsCtrlOut || hasMidiOut) { portName.clear(); if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT) { portName = pData->name; portName += ":"; } portName += "events-out"; portName.truncate(portNameSize); pData->event.portOut = (CarlaEngineEventPort*)pData->client->addPort(kEnginePortTypeEvent, portName, false, 0); } // plugin hints pData->hints = 0x0; if (audioOuts > 0 && (audioIns == audioOuts || audioIns == 1)) pData->hints |= PLUGIN_CAN_DRYWET; if (audioOuts > 0) pData->hints |= PLUGIN_CAN_VOLUME; if (audioOuts >= 2 && audioOuts % 2 == 0) pData->hints |= PLUGIN_CAN_BALANCE; // extra plugin hints pData->extraHints = 0x0; 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,); AudioStreamBasicDescription streamFormat = { .mFormatID = kAudioFormatLinearPCM, .mBitsPerChannel = 32, .mBytesPerFrame = sizeof(float), .mBytesPerPacket = sizeof(float), .mFramesPerPacket = 1, .mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved, .mChannelsPerFrame = 0, .mSampleRate = pData->engine->getSampleRate(), }; if (pData->audioIn.count != 0) { streamFormat.mChannelsPerFrame = pData->audioIn.count; CARLA_SAFE_ASSERT_RETURN(fFunctions.setProperty(fInterface, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &streamFormat, sizeof(streamFormat)) == noErr,); } if (pData->audioOut.count != 0) { streamFormat.mChannelsPerFrame = pData->audioOut.count; CARLA_SAFE_ASSERT_RETURN(fFunctions.setProperty(fInterface, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &streamFormat, sizeof(streamFormat)) == noErr,); } fFunctions.initialize(fInterface); } void deactivate() noexcept override { CARLA_SAFE_ASSERT_RETURN(fInterface != nullptr,); fFunctions.uninitialize(fInterface); } void process(const float* const* const audioIn, float** const audioOut, const float* const* const, float** const, const uint32_t frames) override { // ------------------------------------------------------------------------------------------------------------ // Check if active if (! pData->active) { // disable any output sound for (uint32_t i=0; i < pData->audioOut.count; ++i) carla_zeroFloats(audioOut[i], frames); return; } // ------------------------------------------------------------------------------------------------------------ // Check buffers CARLA_SAFE_ASSERT_RETURN(frames > 0,); if (pData->audioIn.count > 0) { CARLA_SAFE_ASSERT_RETURN(audioIn != nullptr,); } if (pData->audioOut.count > 0) { CARLA_SAFE_ASSERT_RETURN(audioOut != nullptr,); } // ------------------------------------------------------------------------------------------------------------ // Try lock, silence otherwise if (pData->engine->isOffline()) { pData->singleMutex.lock(); } else if (! pData->singleMutex.tryLock()) { for (uint32_t i=0; i < pData->audioOut.count; ++i) carla_zeroFloats(audioOut[i], frames); return; } // ------------------------------------------------------------------------------------------------------------ // Check if needs reset if (pData->needsReset) { // TODO } // ------------------------------------------------------------------------------------------------------------ // Event Input (main port) if (pData->event.portIn != nullptr) { // TODO } // ------------------------------------------------------------------------------------------------------------ // Plugin processing const EngineTimeInfo timeInfo(pData->engine->getTimeInfo()); AudioUnitRenderActionFlags actionFlags = kAudioUnitRenderAction_DoNotCheckRenderArgs; AudioTimeStamp timeStamp = {}; timeStamp.mFlags = kAudioTimeStampSampleTimeValid; timeStamp.mSampleTime = timeInfo.frame; const UInt32 inBusNumber = 0; { uint32_t i = 0; for (; i < pData->audioOut.count; ++i) { fAudioBufferData->mBuffers[i].mData = audioOut[i]; fAudioBufferData->mBuffers[i].mDataByteSize = sizeof(float) * frames; if (audioOut[i] != audioIn[i]) std::memcpy(audioOut[i], audioIn[i], sizeof(float) * frames); } for (; i < pData->audioIn.count; ++i) { fAudioBufferData->mBuffers[i].mData = audioOut[i]; fAudioBufferData->mBuffers[i].mDataByteSize = sizeof(float) * frames; } } fFunctions.render(fInterface, &actionFlags, &timeStamp, inBusNumber, frames, fAudioBufferData); // ------------------------------------------------------------------------------------------------------------ pData->singleMutex.unlock(); #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH // ------------------------------------------------------------------------------------------------------------ // Control Output // TODO #endif // ------------------------------------------------------------------------------------------------------------ // Events/MIDI Output // TODO } // ---------------------------------------------------------------------------------------------------------------- protected: void handlePluginUIClosed() override { carla_stdout("CarlaPluginAU::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; } // --------------------------------------------------------------- // 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 == nullptr || 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; } if (! fFunctions.init(fInterface)) { 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; } // ------------------------------------------------------------------------------------------------------------ // init component { const UInt32 bufferSize = pData->engine->getBufferSize(); if (fFunctions.setProperty(fInterface, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &bufferSize, sizeof(bufferSize)) != noErr) { pData->engine->setLastError("Failed to set Component maximum frames per slice"); return false; } } { const Float64 sampleRate = pData->engine->getSampleRate(); // input scope UInt32 outDataSize = 0; Boolean outWritable = false; if (fFunctions.getPropertyInfo(fInterface, kAudioUnitProperty_ElementCount, kAudioUnitScope_Input, 0, &outDataSize, &outWritable) == noErr && outDataSize == sizeof(UInt32)) { UInt32 outData = 0; if (fFunctions.getProperty(fInterface, kAudioUnitProperty_ElementCount, kAudioUnitScope_Input, 0, &outData, &outDataSize) == noErr && outData != 0) { if (fFunctions.setProperty(fInterface, kAudioUnitProperty_SampleRate, kAudioUnitScope_Input, 0, &sampleRate, sizeof(sampleRate)) != noErr) { pData->engine->setLastError("Failed to set Component input sample rate"); return false; } } } // output scope outDataSize = 0; outWritable = false; if (fFunctions.getPropertyInfo(fInterface, kAudioUnitProperty_ElementCount, kAudioUnitScope_Output, 0, &outDataSize, &outWritable) == noErr && outDataSize == sizeof(UInt32)) { UInt32 outData = 0; if (fFunctions.getProperty(fInterface, kAudioUnitProperty_ElementCount, kAudioUnitScope_Output, 0, &outData, &outDataSize) == noErr && outData != 0) { if (fFunctions.setProperty(fInterface, kAudioUnitProperty_SampleRate, kAudioUnitScope_Output, 0, &sampleRate, sizeof(sampleRate)) != noErr) { pData->engine->setLastError("Failed to set Component output sample rate"); return false; } } } } // ------------------------------------------------------------------------------------------------------------ // set default options pData->options = PLUGIN_OPTION_FIXED_BUFFERS; return true; } private: BundleLoader fBundleLoader; AudioComponentPlugInInterface* fInterface; AudioBufferList* fAudioBufferData; CarlaString fName; CarlaString fLabel; CarlaString fMaker; struct Functions { InitializeFn initialize; UninitializeFn uninitialize; GetPropertyInfoFn getPropertyInfo; GetPropertyFn getProperty; SetPropertyFn setProperty; GetParameterFn getParameter; SetParameterFn setParameter; ScheduleParametersFn scheduleParameters; ResetFn reset; RenderFn render; MIDIEventFn midiEvent; Functions() : initialize(nullptr), uninitialize(nullptr), getPropertyInfo(nullptr), getProperty(nullptr), setProperty(nullptr), getParameter(nullptr), setParameter(nullptr), scheduleParameters(nullptr), reset(nullptr), render(nullptr), midiEvent(nullptr) {} bool init(AudioComponentPlugInInterface* const interface) { initialize = (InitializeFn)interface->Lookup(kAudioUnitInitializeSelect); uninitialize = (UninitializeFn)interface->Lookup(kAudioUnitUninitializeSelect); getPropertyInfo = (GetPropertyInfoFn)interface->Lookup(kAudioUnitGetPropertyInfoSelect); getProperty = (GetPropertyFn)interface->Lookup(kAudioUnitGetPropertySelect); setProperty = (SetPropertyFn)interface->Lookup(kAudioUnitSetPropertySelect); getParameter = (GetParameterFn)interface->Lookup(kAudioUnitGetParameterSelect); setParameter = (SetParameterFn)interface->Lookup(kAudioUnitSetParameterSelect); scheduleParameters = (ScheduleParametersFn)interface->Lookup(kAudioUnitScheduleParametersSelect); reset = (ResetFn)interface->Lookup(kAudioUnitResetSelect); render = (RenderFn)interface->Lookup(kAudioUnitRenderSelect); midiEvent = (MIDIEventFn)interface->Lookup(kMusicDeviceMIDIEventSelect); return initialize != nullptr && uninitialize != nullptr && getPropertyInfo != nullptr && getProperty != nullptr && setProperty != nullptr && getParameter != nullptr && setParameter != nullptr && scheduleParameters != nullptr && render != nullptr; } } fFunctions; }; #endif // -------------------------------------------------------------------------------------------------------------------- CarlaPluginPtr CarlaPlugin::newAU(const Initializer& init) { 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 } // -------------------------------------------------------------------------------------------------------------------- CARLA_BACKEND_END_NAMESPACE