|
|
@@ -1,21 +1,416 @@ |
|
|
|
// SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com> |
|
|
|
// 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 <AudioToolbox/AudioUnit.h> |
|
|
|
# include <Foundation/Foundation.h> |
|
|
|
#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<CFArrayRef>(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<CFDictionaryRef>(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<FactoryFn>(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<CarlaPluginAU> 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 |
|
|
|
} |
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------------------------- |
|
|
|