Files
hot-step-cpp-ROCm/engine/vendor/vst3sdk/public.sdk/source/vst/auwrapper/auwrapper.mm
T
2026-08-16 18:24:52 +07:00

3114 lines
94 KiB
Plaintext

//------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/auwrapper/auwrapper.mm
// Created by : Steinberg, 12/2007
// Description : VST 3 -> AU Wrapper
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
/// \cond ignore
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
/*
Things to do :
- Parameter Mapping could be better (indexed stuff), but needs work if we want to have this
- Speaker Arrangement -> Channel Layout (partially done, just doesn't work always in Logic 8)
- dynamic Bus management
*/
#include "auwrapper.h"
#include "NSDataIBStream.h"
#include "aucocoaview.h"
#include "public.sdk/source/vst/hosting/eventlist.h"
#include "public.sdk/source/vst/hosting/hostclasses.h"
#include "public.sdk/source/vst/hosting/parameterchanges.h"
#include "public.sdk/source/vst/hosting/processdata.h"
#include "public.sdk/source/vst/utility/ump.h"
#include "public.sdk/source/vst/vsteditcontroller.h"
#include "base/source/fdynlib.h"
#include "base/source/fstring.h"
#include "pluginterfaces/base/funknownimpl.h"
#include "pluginterfaces/base/ustring.h"
#include "pluginterfaces/gui/iplugview.h"
#include "pluginterfaces/vst/ivstmidicontrollers.h"
#include "pluginterfaces/vst/vstpresetkeys.h"
#include "pluginterfaces/vst/vsttypes.h"
#include <AudioToolbox/AudioToolbox.h>
#if !SMTG_PLATFORM_64
#include <AudioUnit/AudioUnitCarbonView.h>
#endif
#include <dlfcn.h>
#include <algorithm>
#include <objc/runtime.h>
#if !CA_USE_AUDIO_PLUGIN_ONLY && !defined(SMTG_AUWRAPPER_USES_AUSDK)
#include "CAXException.h"
#endif
#define SMTG_MAKE_STRING_PRIVATE_DONT_USE(x) #x
#define SMTG_MAKE_STRING(x) SMTG_MAKE_STRING_PRIVATE_DONT_USE (x)
typedef bool (*bundleEntryPtr) (CFBundleRef);
typedef bool (*bundleExitPtr) (void);
#include <dlfcn.h>
// we ignore for the moment that the NSAddImage functions are deprecated
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
static bool CopyProcessPath (Steinberg::String& name)
{
Dl_info info;
if (dladdr ((const void*)CopyProcessPath, &info))
{
if (info.dli_fname)
{
name.assign (info.dli_fname);
#ifdef UNICODE
name.toWideString ();
#endif
return true;
}
}
return false;
}
namespace Steinberg {
//------------------------------------------------------------------------
class VST3DynLibrary : public FDynLibrary
{
public:
VST3DynLibrary () : bundleEntryCalled (false) { gInstance = this; }
~VST3DynLibrary ()
{
if (isLoaded ())
{
if (bundleEntryCalled)
{
if (bundleExitPtr bundleExit = (bundleExitPtr)getProcAddress ("bundleExit"))
bundleExit ();
}
#if defined(MAC_OS_X_VERSION_10_11)
// workaround, because CFBundleCreate returns refcount == 2.
if (CFBundleIsExecutableLoaded ((CFBundleRef)instance))
{
CFBundleUnloadExecutable ((CFBundleRef)instance);
CFRelease ((CFBundleRef)instance);
}
#else
CFRelease ((CFBundleRef)instance);
#endif
instance = nullptr;
isloaded = false;
}
gInstance = nullptr;
}
bool init (const tchar* path)
{
if (isLoaded ())
return true;
isBundle = false;
Steinberg::String name (path);
if (name.getChar16 (0) != STR ('/')) // no absoltue path
{
Steinberg::String p;
if (CopyProcessPath (p))
{
Steinberg::int32 index = p.findLast (STR ('/'));
p.remove (index + 1);
name = p + name;
}
}
CFStringRef fsString = (CFStringRef)name.toCFStringRef ();
CFURLRef url = CFURLCreateWithFileSystemPath (NULL, fsString, kCFURLPOSIXPathStyle, true);
if (url)
{
CFBundleRef bundle = CFBundleCreate (NULL, url);
if (bundle)
{
bundleEntryPtr bundleEntry = (bundleEntryPtr)CFBundleGetFunctionPointerForName (
bundle, CFSTR ("bundleEntry"));
if (bundleEntry)
bundleEntryCalled = bundleEntry (bundle);
if (bundleEntryCalled)
{
isBundle = true;
isloaded = true;
instance = (void*)bundle;
}
else
CFRelease (bundle);
}
CFRelease (url);
}
CFRelease (fsString);
return isLoaded ();
}
static VST3DynLibrary* gInstance;
protected:
bool bundleEntryCalled;
};
VST3DynLibrary* VST3DynLibrary::gInstance = nullptr;
namespace Vst {
//------------------------------------------------------------------------
struct AUWrapper::MIDIOutputCallbackHelper
{
MIDIOutputCallbackHelper ();
~MIDIOutputCallbackHelper ();
void setCallbackInfo (AUMIDIOutputCallback callback, void* userData);
void addEvent (UInt8 status, UInt8 channel, UInt8 data1, UInt8 data2, UInt32 inStartFrame);
void fireAtTimeStamp (const AudioTimeStamp& inTimeStamp);
private:
struct MIDIMessageInfoStruct
{
UInt8 status;
UInt8 channel;
UInt8 data1;
UInt8 data2;
UInt32 startFrame;
};
using MIDIMessageList = std::vector<MIDIMessageInfoStruct> ;
MIDIPacketList* PacketList () { return (MIDIPacketList*)mBuffersAllocated.data (); }
std::array<Byte, 1024> mBuffersAllocated;
AUMIDIOutputCallbackStruct mMIDICallbackStruct;
MIDIMessageList mMIDIMessageList;
};
#ifdef SMTG_AUWRAPPER_USES_AUSDK
using namespace ausdk;
#endif
//--------------------------------------------------------------------------------------------
class SpeakerArrangementBase
{
public:
SpeakerArrangementBase ()
{
channelLayout.mChannelBitmap = 0;
channelLayout.mChannelLayoutTag = 0;
channelLayout.mNumberChannelDescriptions = 0;
}
OSStatus setNumChannels (int32 numChannels)
{
switch (numChannels)
{
//--- -----------------------
case 1: channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono; break;
//--- -----------------------
case 2: channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo; break;
//--- -----------------------
case 6: channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_AudioUnit_5_1; break;
//--- -----------------------
default: return kAudioUnitErr_InvalidProperty;
}
return noErr;
}
protected:
AudioChannelLayout channelLayout;
};
//--------------------------------------------------------------------------------------------
class MultiChannelOutputElement : public AUOutputElement, public SpeakerArrangementBase
{
public:
MultiChannelOutputElement (AUBase* audioUnit) : AUOutputElement (audioUnit) {}
UInt32 GetChannelLayoutTags (AudioChannelLayoutTag* outLayoutTagsPtr)
{
if (AudioChannelLayoutTag_GetNumberOfChannels (channelLayout.mChannelLayoutTag) <= 2)
return 0;
if (outLayoutTagsPtr)
*outLayoutTagsPtr = channelLayout.mChannelLayoutTag;
return 1;
}
UInt32 GetAudioChannelLayout (AudioChannelLayout* outMapPtr, Boolean& outWritable)
{
if (AudioChannelLayoutTag_GetNumberOfChannels (channelLayout.mChannelLayoutTag) <= 2)
return 0;
if (outMapPtr)
*outMapPtr = channelLayout;
outWritable = false;
return sizeof (AudioChannelLayout);
}
OSStatus SetAudioChannelLayout (const AudioChannelLayout& layout)
{
return kAudioUnitErr_InvalidProperty;
}
OSStatus SetStreamFormat (const AudioStreamBasicDescription& desc)
{
OSStatus err = setNumChannels (desc.mChannelsPerFrame);
if (err != noErr)
return err;
return AUOutputElement::SetStreamFormat (desc);
}
};
//--------------------------------------------------------------------------------------------
class MultiChannelInputElement : public AUInputElement, public SpeakerArrangementBase
{
public:
MultiChannelInputElement (AUBase* audioUnit) : AUInputElement (audioUnit) {}
UInt32 GetChannelLayoutTags (AudioChannelLayoutTag* outLayoutTagsPtr)
{
if (AudioChannelLayoutTag_GetNumberOfChannels (channelLayout.mChannelLayoutTag) <= 2)
return 0;
if (outLayoutTagsPtr)
*outLayoutTagsPtr = channelLayout.mChannelLayoutTag;
return 1;
}
UInt32 GetAudioChannelLayout (AudioChannelLayout* outMapPtr, Boolean& outWritable)
{
if (AudioChannelLayoutTag_GetNumberOfChannels (channelLayout.mChannelLayoutTag) <= 2)
return 0;
if (outMapPtr)
memcpy (outMapPtr, &channelLayout, sizeof (AudioChannelLayout));
outWritable = false;
return sizeof (AudioChannelLayout);
}
OSStatus SetAudioChannelLayout (const AudioChannelLayout& layout)
{
return kAudioUnitErr_InvalidProperty;
}
OSStatus SetStreamFormat (const AudioStreamBasicDescription& desc)
{
OSStatus err = setNumChannels (desc.mChannelsPerFrame);
if (err != noErr)
return err;
return AUInputElement::SetStreamFormat (desc);
}
};
//------------------------------------------------------------------------
static CFStringRef createCFStringFromString128 (const String128& string)
{
UString128 str (string);
return CFStringCreateWithCharacters (nullptr, (const UniChar*)string, str.getLength ());
}
//------------------------------------------------------------------------
static void createString128FromCFString (CFStringRef inString, String128& outString)
{
CFStringGetCharacters (inString,
CFRangeMake (0, std::max<CFIndex> (128, CFStringGetLength (inString))),
(UniChar*)outString);
}
//------------------------------------------------------------------------
static CFBundleRef GetBundleFromExecutable (const char* filepath)
{
AutoreleasePool ap;
NSString* execStr = [NSString stringWithCString:filepath encoding:NSUTF8StringEncoding];
NSString* macOSStr = [execStr stringByDeletingLastPathComponent];
NSString* contentsStr = [macOSStr stringByDeletingLastPathComponent];
NSString* bundleStr = [contentsStr stringByDeletingLastPathComponent];
return CFBundleCreate (nullptr, (CFURLRef)[NSURL fileURLWithPath:bundleStr isDirectory:YES]);
}
//------------------------------------------------------------------------
static CFBundleRef GetCurrentBundle ()
{
Dl_info info;
if (dladdr ((const void*)GetCurrentBundle, &info))
{
if (info.dli_fname)
{
return GetBundleFromExecutable (info.dli_fname);
}
}
return nullptr;
}
//------------------------------------------------------------------------
CFBundleRef AUWrapper::gBundleRef = nullptr;
static CFIndex gBundleRefCount = 0;
static AUChannelInfo* channelInfos = nullptr;
//------------------------------------------------------------------------
static void initBundleRef ()
{
if (AUWrapper::gBundleRef == nullptr)
{
AUWrapper::gBundleRef = GetCurrentBundle ();
gBundleRefCount = CFGetRetainCount (AUWrapper::gBundleRef);
}
else
CFRetain (AUWrapper::gBundleRef);
}
//------------------------------------------------------------------------
static void releaseBundleRef ()
{
CFIndex currentCount = CFGetRetainCount (AUWrapper::gBundleRef);
CFRelease (AUWrapper::gBundleRef);
if (currentCount == gBundleRefCount)
AUWrapper::gBundleRef = nullptr;
}
//------------------------------------------------------------------------
class AUHostApplication : public HostApplication, public IVst3ToAUWrapper
{
public:
virtual tresult PLUGIN_API getName (String128 name) SMTG_OVERRIDE
{
String str ("VST3-AU Wrapper");
str.copyTo (name, 0, 127);
return kResultTrue;
}
DEFINE_INTERFACES
DEF_INTERFACE (Vst::IVst3ToAUWrapper)
END_DEFINE_INTERFACES (HostApplication)
REFCOUNT_METHODS (HostApplication)
};
static AUHostApplication gHostApp;
//------------------------------------------------------------------------
IMPLEMENT_FUNKNOWN_METHODS (AUWrapper, IComponentHandler, IComponentHandler::iid)
//------------------------------------------------------------------------
AUWrapper::AUWrapper (ComponentInstanceRecord* ci)
#ifdef SMTG_AUWRAPPER_USES_AUSDK
: AUWRAPPER_BASE_CLASS (ci, 128, 128)
#else
: AUWRAPPER_BASE_CLASS (ci, 0, 0)
#endif
, audioProcessor (nullptr)
, editController (nullptr)
, timer (nullptr)
, noteCounter (0)
, sampleRate (44100)
, bypassParamID (-1)
, presets (nullptr)
, numPresets (0)
, factoryProgramChangedID (-1)
, isInstrument (false)
, isBypassed (false)
, paramListenerRef (nullptr)
, midiOutCount (0)
, isOfflineRender (false)
{
FUNKNOWN_CTOR
AutoreleasePool ap;
initBundleRef ();
for (int32 i = 0; i < kMaxProgramChangeParameters; i++)
programChangeInfos[i] = {};
processData.processContext = &processContext;
processData.inputParameterChanges = &processParamChanges;
processData.outputParameterChanges = &outputParamChanges;
eventList.setMaxSize (128);
outputEvents.setMaxSize (128);
processData.inputEvents = &eventList;
processData.outputEvents = &outputEvents;
loadVST3Module ();
auto factory = U::cast<IPluginFactory2> (owned (getFactory ()));
if (factory)
{
// find first audio processor class
for (int32 i = 0; i < factory->countClasses (); i++)
{
PClassInfo2 ci;
if (factory->getClassInfo2 (i, &ci) == kResultTrue)
{
if (strcmp (ci.category, kVstAudioEffectClass) == 0)
{
if (factory->createInstance (ci.cid, IAudioProcessor::iid,
(void**)&audioProcessor) == kResultTrue)
{
ConstString plugCategory (ci.subCategories);
if (plugCategory.findFirst ("Instrument", -1,
ConstString::kCaseInsensitive) >= 0)
isInstrument = true;
break;
}
}
}
}
}
if (audioProcessor)
{
if (FUnknownPtr<IPluginBase> (audioProcessor)->initialize ((HostApplication*)&gHostApp) !=
kResultTrue)
return;
auto component = U::cast<IComponent> (audioProcessor);
if (audioProcessor->queryInterface (IEditController::iid, (void**)&editController) !=
kResultTrue)
{
if (component)
{
TUID ccid;
if (component->getControllerClassId (ccid) == kResultTrue)
{
if (factory->createInstance (ccid, IEditController::iid,
(void**)&editController) == kResultTrue)
{
if (editController->initialize ((HostApplication*)&gHostApp) != kResultTrue)
{
editController->release ();
editController = nullptr;
return;
}
auto controllerConnectionPoint = U::cast<IConnectionPoint> (editController);
auto processorConnectionPoint = U::cast<IConnectionPoint> (audioProcessor);
if (controllerConnectionPoint && processorConnectionPoint)
{
controllerConnectionPoint->connect (processorConnectionPoint);
processorConnectionPoint->connect (controllerConnectionPoint);
}
NSMutableData* processorData = [[[NSMutableData alloc] init] autorelease];
NSMutableDataIBStream stream (processorData);
if (FUnknownPtr<IComponent> (audioProcessor)->getState (&stream) ==
kResultTrue)
{
stream.seek (0, IBStream::kIBSeekSet);
editController->setComponentState (&stream);
}
}
}
}
}
if (editController && component)
{
editController->setComponentHandler (this);
// initialize busses
int32 inputBusCount = component->getBusCount (kAudio, kInput);
int32 outputBusCount = component->getBusCount (kAudio, kOutput);
CreateElements ();
Inputs ().SetNumberOfElements (inputBusCount);
Outputs ().SetNumberOfElements (outputBusCount);
SpeakerArrangement sa;
for (int32 inputNo = 0; inputNo < inputBusCount; inputNo++)
{
MultiChannelInputElement* element =
dynamic_cast<MultiChannelInputElement*> (Inputs ().GetIOElement (inputNo));
if (element == nullptr)
continue;
if (audioProcessor->getBusArrangement (kInput, inputNo, sa) == kResultTrue)
{
AudioStreamBasicDescription streamDesc (element->GetStreamFormat ());
streamDesc.mChannelsPerFrame = SpeakerArr::getChannelCount (sa);
element->SetStreamFormat (streamDesc);
}
BusInfo info = {};
if (component->getBusInfo (kAudio, kInput, inputNo, info) == kResultTrue)
{
String busName (info.name);
CFStringRef busNameString = (CFStringRef)busName.toCFStringRef ();
element->SetName (busNameString);
CFRelease (busNameString);
}
#ifdef SMTG_AUWRAPPER_ACTIVATE_ONLY_DEFAULT_ACTIVE_BUSES
if (info.flags & BusInfo::BusFlags::kDefaultActive)
#endif
{
component->activateBus (kAudio, kInput, inputNo, true);
}
}
for (int32 outputNo = 0; outputNo < outputBusCount; outputNo++)
{
MultiChannelOutputElement* element =
dynamic_cast<MultiChannelOutputElement*> (Outputs ().GetIOElement (outputNo));
if (element == nullptr)
continue;
if (audioProcessor->getBusArrangement (kOutput, outputNo, sa) == kResultTrue)
{
AudioStreamBasicDescription streamDesc (element->GetStreamFormat ());
streamDesc.mChannelsPerFrame = SpeakerArr::getChannelCount (sa);
element->SetStreamFormat (streamDesc);
}
BusInfo info = {};
if (component->getBusInfo (kAudio, kOutput, outputNo, info) == kResultTrue)
{
String busName (info.name);
CFStringRef busNameString = (CFStringRef)busName.toCFStringRef ();
element->SetName (busNameString);
CFRelease (busNameString);
}
#ifdef SMTG_AUWRAPPER_ACTIVATE_ONLY_DEFAULT_ACTIVE_BUSES
if (info.flags & BusInfo::BusFlags::kDefaultActive)
#endif
{
component->activateBus (kAudio, kOutput, outputNo, true);
}
}
processData.prepare (*component, 0, kSample32);
// initialize parameters
syncParameterValues ();
cacheParameterValues ();
midiOutCount = component->getBusCount (kEvent, kOutput);
if (midiOutCount > 0)
mCallbackHelper = std::make_unique<MIDIOutputCallbackHelper> ();
transferParamChanges.setMaxParameters (500);
outputParamTransfer.setMaxParameters (500);
timer = Timer::create (this, 20);
auto editController2 = U::cast<IEditController2> (editController);
if (editController2)
editController2->setKnobMode (kLinearMode);
midiLearn = U::cast<IMidiLearn> (editController);
if (midiLearn)
midiLearnRingBuffer.resize (8);
}
}
}
//------------------------------------------------------------------------
AUWrapper::~AUWrapper ()
{
AutoreleasePool ap;
if (timer)
timer->release ();
if (paramListenerRef)
AUListenerDispose (paramListenerRef);
if (audioProcessor)
{
auto combined = U::cast<IEditController> (audioProcessor);
if (!combined)
{
auto controllerConnectionPoint = U::cast<IConnectionPoint> (editController);
auto processorConnectionPoint = U::cast<IConnectionPoint> (audioProcessor);
if (controllerConnectionPoint && processorConnectionPoint)
{
controllerConnectionPoint->disconnect (processorConnectionPoint);
processorConnectionPoint->disconnect (controllerConnectionPoint);
}
}
}
midiLearn.reset ();
if (editController)
{
editController->setComponentHandler (nullptr);
uint32 refCount = editController->addRef ();
if (refCount == 2)
editController->terminate ();
editController->release ();
editController->release ();
editController = nullptr;
}
clearParameterValueCache ();
if (audioProcessor)
{
FUnknownPtr<IPluginBase> (audioProcessor)->terminate ();
audioProcessor->release ();
audioProcessor = nullptr;
}
unloadVST3Module ();
releaseBundleRef ();
if (presets)
delete presets;
FUNKNOWN_DTOR
}
//------------------------------------------------------------------------
void AUWrapper::loadVST3Module ()
{
if (VST3DynLibrary::gInstance == nullptr)
{
if (gBundleRef)
{
String pluginPath;
CFStringRef pluginNameStr = (CFStringRef)CFBundleGetValueForInfoDictionaryKey (
gBundleRef, CFSTR ("VST3 Plug-in"));
if (!pluginNameStr)
{
NSURL* pluginUrl = (NSURL*)CFBundleCopyResourceURL (gBundleRef, CFSTR ("plugin"),
CFSTR ("vst3"), NULL);
if (!pluginUrl)
{
NSLog (
@"VST3 Plug-in not defined in Info Dictionary and not included in bundle");
return;
}
pluginPath.fromCFStringRef ((CFStringRef)[pluginUrl path]);
[pluginUrl release];
}
else
{
pluginPath.fromCFStringRef (pluginNameStr);
if (!pluginPath.getChar (0) != STR ('/'))
{
FSRef fsRef;
if (FSFindFolder (kLocalDomain, kAudioPlugInsFolderType, false, &fsRef) ==
noErr)
{
NSURL* url = (NSURL*)CFURLCreateFromFSRef (nullptr, &fsRef);
if (url)
{
String basePath ([[url path] UTF8String]);
basePath.toWideString (kCP_Utf8);
pluginPath.insertAt (0, STR ("/VST3/"));
pluginPath.insertAt (0, basePath);
[url release];
}
}
}
}
dynLib = owned (new VST3DynLibrary ());
if (pluginPath.isEmpty () || !dynLib->init (pluginPath))
{
dynLib.reset ();
return;
}
}
}
else
{
dynLib = VST3DynLibrary::gInstance;
}
}
//------------------------------------------------------------------------
void AUWrapper::unloadVST3Module ()
{
dynLib.reset ();
}
//------------------------------------------------------------------------
IPluginFactory* AUWrapper::getFactory ()
{
if (VST3DynLibrary::gInstance)
{
GetFactoryProc getPlugFactory =
(GetFactoryProc)VST3DynLibrary::gInstance->getProcAddress ("GetPluginFactory");
if (getPlugFactory)
return getPlugFactory ();
}
return nullptr;
}
//------------------------------------------------------------------------
// MARK: ComponentBase
#if !CA_USE_AUDIO_PLUGIN_ONLY && !defined(SMTG_AUWRAPPER_USES_AUSDK)
ComponentResult AUWrapper::Version ()
{
AutoreleasePool ap;
NSString* versionString =
(NSString*)CFBundleGetValueForInfoDictionaryKey (gBundleRef, CFSTR ("AudioUnit Version"));
if (versionString)
{
int version = 0;
if (sscanf ([versionString UTF8String], "%x", &version) == 1)
return version;
}
return 0xFFFFFFFF;
}
#endif
//------------------------------------------------------------------------
void AUWrapper::PostConstructor ()
{
AUWRAPPER_BASE_CLASS::PostConstructor ();
}
//------------------------------------------------------------------------
static SpeakerArrangement numChannelsToSpeakerArrangement (UInt32 numChannels)
{
switch (numChannels)
{
//--- -----------------------
case 1: return SpeakerArr::kMono;
//--- -----------------------
case 2: return SpeakerArr::kStereo;
//--- -----------------------
case 6: return SpeakerArr::k51;
}
return 0;
}
//------------------------------------------------------------------------
ComponentResult AUWrapper::Initialize ()
{
if (audioProcessor && editController)
{
// match speaker arrangement with AU stream format
auto component = U::cast<IComponent> (audioProcessor);
int32 inputBusCount = component->getBusCount (kAudio, kInput);
int32 outputBusCount = component->getBusCount (kAudio, kOutput);
SupportedNumChannels (0); // initialize channelInfos
if (channelInfos)
{
int32 maxBusCount = std::max (inputBusCount, outputBusCount);
for (int32 i = 0; i < maxBusCount; i++)
{
int32 inChannelCount = 0;
int32 outChannelCount = 0;
if (inputBusCount > i)
inChannelCount =
Inputs ().GetIOElement (i)->GetStreamFormat ().mChannelsPerFrame;
if (outputBusCount > i)
outChannelCount =
Outputs ().GetIOElement (i)->GetStreamFormat ().mChannelsPerFrame;
if (!validateChannelPair (inChannelCount, outChannelCount, channelInfos,
SupportedNumChannels (0)))
return kAudioUnitErr_FailedInitialization;
}
}
SpeakerArrangement inputs[inputBusCount];
SpeakerArrangement outputs[outputBusCount];
for (int32 element = 0; element < inputBusCount; element++)
{
const AudioStreamBasicDescription desc =
Inputs ().GetIOElement (element)->GetStreamFormat ();
inputs[element] = numChannelsToSpeakerArrangement (desc.mChannelsPerFrame);
}
for (int32 element = 0; element < outputBusCount; element++)
{
const AudioStreamBasicDescription desc =
Outputs ().GetIOElement (element)->GetStreamFormat ();
outputs[element] = numChannelsToSpeakerArrangement (desc.mChannelsPerFrame);
}
if (audioProcessor->setBusArrangements (inputs, inputBusCount, outputs, outputBusCount) !=
kResultTrue)
{
return kAudioUnitErr_FailedInitialization;
}
// After set Bus Arrangement, the channelbuffers may need to be reallocated -> hence the
// second prepare!
processData.prepare (*component, 0, kSample32);
ProcessSetup ps;
ps.sampleRate = getSampleRate ();
ps.maxSamplesPerBlock = GetMaxFramesPerSlice ();
ps.symbolicSampleSize = kSample32;
ps.processMode = kRealtime;
audioProcessor->setupProcessing (ps);
updateMidiMappingCache ();
component->setActive (true);
audioProcessor->setProcessing (true);
if (paramListenerRef == nullptr)
{
static constexpr auto interval = 1. / 60.; // 60 times a second
OSStatus status = AUListenerCreateWithDispatchQueue (
&paramListenerRef, interval, dispatch_get_main_queue (),
^(void* _Nullable inObject, const AudioUnitParameter* _Nonnull inParameter,
AudioUnitParameterValue inValue) {
setControllerParameter (inParameter->mParameterID, inValue);
});
SMTG_ASSERT (status == noErr)
if (paramListenerRef)
{
AudioUnitParameter sPar;
sPar.mAudioUnit = GetComponentInstance ();
sPar.mParameterID = 0;
sPar.mElement = 0;
sPar.mScope = kAudioUnitScope_Global;
programParameters.clear ();
// for each VST3 parameter
for (int32 i = 0; i < editController->getParameterCount (); i++)
{
ParameterInfo pi = {};
editController->getParameterInfo (i, pi);
// do not register bypass
if ((pi.flags & ParameterInfo::kIsBypass) != 0)
{
continue;
}
sPar.mParameterID = pi.id;
status = AUListenerAddParameter (paramListenerRef, nullptr, &sPar);
SMTG_ASSERT (status == noErr)
if ((pi.flags & ParameterInfo::kIsProgramChange) != 0)
{
programParameters.push_back (pi);
}
}
updateProgramChangesCache ();
IUnitInfo* unitInfoController = NULL;
// let's see if there's a preset list (take the first one)
if (editController->queryInterface (IUnitInfo::iid, (void**)&unitInfoController) ==
kResultTrue &&
unitInfoController)
{
ProgramListInfo programListInfo;
if (unitInfoController->getProgramListInfo (0, programListInfo) == kResultTrue)
{
factoryProgramChangedID = -1;
numPresets = programListInfo.programCount;
if (programListInfo.programCount > 0)
{
UnitID unitId = -1;
// find the unit supporting this ProgramList
IUnitInfo* unitInfo = NULL;
if (editController->queryInterface (IUnitInfo::iid,
(void**)&unitInfo) == kResultTrue &&
unitInfo)
{
int32 unitCount = unitInfo->getUnitCount ();
for (int32 i = 0; i < unitCount; i++)
{
UnitInfo unitInfoStruct = {};
if (unitInfo->getUnitInfo (i, unitInfoStruct) == kResultTrue)
{
if (programListInfo.id == unitInfoStruct.programListId)
{
unitId = unitInfoStruct.id;
break;
}
}
}
unitInfo->release ();
}
if (unitId != -1)
{
// find the associated ProgramChange parameter ID
for (int32 i = 0; i < (int32)programParameters.size (); i++)
{
const ParameterInfo& paramInfo = programParameters.at (i);
if (paramInfo.unitId == unitId)
{
factoryProgramChangedID = paramInfo.id;
break;
}
}
if (factoryProgramChangedID != -1)
{
presets = new AUPreset[programListInfo.programCount];
for (int32 i = 0; i < programListInfo.programCount; i++)
{
String128 name;
unitInfoController->getProgramName (programListInfo.id, i,
name);
presets[i].presetNumber = i;
presets[i].presetName = createCFStringFromString128 (name);
}
}
}
}
}
unitInfoController->release ();
}
}
}
return AUWRAPPER_BASE_CLASS::Initialize ();
}
return -1;
}
//------------------------------------------------------------------------
void AUWrapper::Cleanup ()
{
if (audioProcessor)
{
audioProcessor->setProcessing (false);
auto component = U::cast<IComponent> (audioProcessor);
component->setActive (false);
}
}
//------------------------------------------------------------------------
void AUWrapper::updateProgramChangesCache ()
{
// assign programChanges
auto pci = std::make_unique<ProgramChangeInfoList> ();
for (size_t midiChannel = 0u; midiChannel < pci->size (); ++midiChannel)
{
auto& programChangeInfo = pci->at (midiChannel);
programChangeInfo = {};
UnitID unitId;
ProgramListID programListId;
if (getProgramListAndUnit (static_cast<int32> (midiChannel), unitId, programListId))
{
for (int32 i = 0; i < (int32)programParameters.size (); i++)
{
const ParameterInfo& paramInfo = programParameters.at (i);
if (paramInfo.unitId == unitId)
{
programChangeInfo.pid = paramInfo.id;
ParameterInfo paramInfo = {};
if (editController->getParameterInfo (paramInfo.id, paramInfo) == kResultTrue)
{
programChangeInfo.numPrograms = paramInfo.stepCount + 1;
}
break;
}
}
}
}
programChangeInfoTransfer.transferObject_ui (std::move (pci));
}
//------------------------------------------------------------------------
void AUWrapper::updateMidiMappingCache ()
{
if (!editController || !audioProcessor)
return;
auto midiMapping = U::cast<IMidiMapping> (editController);
if (!midiMapping)
return;
auto comp = U::cast<IComponent> (audioProcessor);
if (!comp)
return;
auto numInputs = comp->getBusCount (MediaTypes::kEvent, BusDirections::kInput);
if (numInputs <= 0)
return;
MidiMapping cache;
cache.busList.resize (numInputs);
for (auto busIndex = 0; busIndex < numInputs; ++busIndex)
{
BusInfo busInfo {};
if (comp->getBusInfo (MediaTypes::kEvent, BusDirections::kInput, 0, busInfo) != kResultTrue)
continue;
if (busInfo.channelCount <= 0)
continue;
cache.busList[busIndex].resize (busInfo.channelCount);
for (auto channelIndex = 0; channelIndex < busInfo.channelCount; ++channelIndex)
{
for (auto cc = 0; cc < ControllerNumbers::kCountCtrlNumber; ++cc)
{
ParamID pid;
if (midiMapping->getMidiControllerAssignment (busIndex, channelIndex, cc, pid) ==
kResultTrue)
{
cache.busList[busIndex][channelIndex][cc] = pid;
}
}
}
}
if (!cache.empty ())
{
midiMappingTransfer.transferObject_ui (std::make_unique<MidiMapping> (std::move (cache)));
}
}
//------------------------------------------------------------------------
// MARK: AUBase
//--------------------------------------------------------------------------------------------
#ifdef SMTG_AUWRAPPER_USES_AUSDK
std::unique_ptr<AUElement> AUWrapper::CreateElement (AudioUnitScope scope, AudioUnitElement element)
{
switch (scope)
{
//--- -----------------------
case kAudioUnitScope_Output: return std::make_unique<MultiChannelOutputElement>(this);
//--- -----------------------
case kAudioUnitScope_Input: return std::make_unique<MultiChannelInputElement>(this);
}
return AUWRAPPER_BASE_CLASS::CreateElement (scope, element);
}
#else
AUElement* AUWrapper::CreateElement (AudioUnitScope scope, AudioUnitElement element)
{
switch (scope)
{
//--- -----------------------
case kAudioUnitScope_Output: return new MultiChannelOutputElement (this);
//--- -----------------------
case kAudioUnitScope_Input: return new MultiChannelInputElement (this);
}
return AUWRAPPER_BASE_CLASS::CreateElement (scope, element);
}
#endif // SMTG_AUWRAPPER_USES_AUSDK
//------------------------------------------------------------------------
UInt32 AUWrapper::SupportedNumChannels (const AUChannelInfo** outInfo)
{
/*
This code expects that the Info.plist contains an array with key
<key>AudioUnit SupportedNumChannels</key>
that contains the supported AUChannelInfo informations like:
<array>
<dict>
<key>Outputs</key>
<string>2</string>
<key>Inputs</key>
<string>2</string>
</dict>
<dict>
<key>Outputs</key>
<string>0</string>
<key>Inputs</key>
<string>1</string>
</dict>
<dict>
<key>Outputs</key>
<string>1</string>
<key>Inputs</key>
<string>1</string>
</dict>
</array>
*/
static unsigned long numChannelInfos = 0;
static bool once = true;
if (once && gBundleRef)
{
once = false;
char buffer[128];
CFStringRef processName = nullptr;
ProcessSerialNumber psn;
SMTG_VERIFY_IS (GetCurrentProcess (&psn), noErr);
SMTG_VERIFY_IS (CopyProcessName (&psn, &processName), noErr);
CFStringGetCString (processName, buffer, sizeof (buffer), kCFStringEncodingUTF8);
CFRelease (processName);
strncat (buffer, " SupportedNumChannels", sizeof (buffer) - strlen (buffer) - 1);
CFStringRef dictName =
CFStringCreateWithCString (kCFAllocatorDefault, buffer, kCFStringEncodingUTF8);
NSArray* supportedNumChannelsArray =
(NSArray*)CFBundleGetValueForInfoDictionaryKey (gBundleRef, dictName);
if (!supportedNumChannelsArray)
supportedNumChannelsArray = (NSArray*)CFBundleGetValueForInfoDictionaryKey (
gBundleRef, CFSTR ("AudioUnit SupportedNumChannels"));
if (supportedNumChannelsArray)
{
numChannelInfos = [supportedNumChannelsArray count];
if (numChannelInfos > 0)
{
channelInfos = new AUChannelInfo[numChannelInfos];
int i = 0;
for (NSDictionary* dict in supportedNumChannelsArray)
{
NSInteger inputs = [[dict objectForKey:@"Inputs"] integerValue];
NSInteger outputs = [[dict objectForKey:@"Outputs"] integerValue];
channelInfos[i].inChannels = inputs;
channelInfos[i++].outChannels = outputs;
}
}
}
CFRelease (dictName);
}
if (outInfo)
*outInfo = channelInfos;
return static_cast<UInt32> (numChannelInfos);
}
//------------------------------------------------------------------------
bool AUWrapper::StreamFormatWritable (AudioUnitScope scope, AudioUnitElement element)
{
return IsInitialized () ? false : true;
}
//------------------------------------------------------------------------
ComponentResult AUWrapper::ChangeStreamFormat (AudioUnitScope inScope, AudioUnitElement inElement,
const AudioStreamBasicDescription& inPrevFormat,
const AudioStreamBasicDescription& inNewFormat)
{
if (inPrevFormat.mChannelsPerFrame == inNewFormat.mChannelsPerFrame)
{
// sample rate change
ComponentResult res = AUWRAPPER_BASE_CLASS::ChangeStreamFormat (inScope, inElement,
inPrevFormat, inNewFormat);
if (res == noErr)
sampleRate = inNewFormat.mSampleRate;
return res;
}
else if (inPrevFormat.mSampleRate != inNewFormat.mSampleRate)
sampleRate = inNewFormat.mSampleRate;
else if (IsInitialized () || SupportedNumChannels (0) == 0)
return kAudioUnitErr_Initialized;
AUIOElement* element;
switch (inScope)
{
//--- -----------------------
case kAudioUnitScope_Input: element = Inputs ().GetIOElement (inElement); break;
//--- -----------------------
case kAudioUnitScope_Output: element = Outputs ().GetIOElement (inElement); break;
//--- -----------------------
case kAudioUnitScope_Global: element = Outputs ().GetIOElement (0); break;
//--- -----------------------
default: Throw (kAudioUnitErr_InvalidScope);
}
OSStatus err = element->SetStreamFormat (inNewFormat);
if (err == noErr)
PropertyChanged (kAudioUnitProperty_StreamFormat, inScope, inElement);
return err;
}
//------------------------------------------------------------------------
ComponentResult AUWrapper::SetConnection (const AudioUnitConnection& inConnection)
{
ComponentResult result = AUWRAPPER_BASE_CLASS::SetConnection (inConnection);
if (result == noErr)
{
int32 busIndex = inConnection.destInputNumber;
bool active = GetInput (busIndex)->IsActive ();
auto component = U::cast<IComponent> (audioProcessor);
component->activateBus (kAudio, kInput, busIndex, active);
}
return result;
}
//------------------------------------------------------------------------
void AUWrapper::buildUnitInfos (IUnitInfo* unitInfoController, UnitInfoMap& units) const
{
units.clear ();
if (unitInfoController)
{
int32 numUnits = unitInfoController->getUnitCount ();
for (int32 i = 0; i < numUnits; i++)
{
UnitInfo ui;
if (unitInfoController->getUnitInfo (i, ui) == kResultTrue)
units[ui.id] = ui;
}
}
}
//------------------------------------------------------------------------
ComponentResult AUWrapper::GetParameterInfo (AudioUnitScope inScope,
AudioUnitParameterID inParameterID,
AudioUnitParameterInfo& outParameterInfo)
{
if (inScope != kAudioUnitScope_Global)
return kAudioUnitErr_InvalidScope;
Steinberg::Base::Thread::FGuard guard (parameterCacheChanging);
CachedParameterInfoMap::const_iterator it = cachedParameterInfos.find (inParameterID);
if (it == cachedParameterInfos.end ())
return kAudioUnitErr_InvalidParameter;
memcpy (&outParameterInfo, &it->second, sizeof (AudioUnitParameterInfo));
if (outParameterInfo.cfNameString)
CFRetain (outParameterInfo.cfNameString);
if (outParameterInfo.unitName)
CFRetain (outParameterInfo.unitName);
return noErr;
}
//------------------------------------------------------------------------
ComponentResult AUWrapper::SetParameter (AudioUnitParameterID inID, AudioUnitScope inScope,
AudioUnitElement inElement,
AudioUnitParameterValue inValue,
UInt32 inBufferOffsetInFrames)
{
if (inScope == kAudioUnitScope_Global)
{
if (inID == factoryProgramChangedID)
{
int presetIdx = (int)((float)inValue * (float)numPresets);
if (presetIdx >= 0 && presetIdx < numPresets)
{
SetAFactoryPresetAsCurrent (presets[presetIdx]);
PropertyChanged (kAudioUnitProperty_PresentPreset, kAudioUnitScope_Global, 0);
PropertyChanged (kAudioUnitProperty_CurrentPreset, kAudioUnitScope_Global, 0);
}
}
transferParamChanges.addChange (inID, inValue, inBufferOffsetInFrames);
}
return AUWRAPPER_BASE_CLASS::SetParameter (inID, inScope, inElement, inValue,
inBufferOffsetInFrames);
}
//------------------------------------------------------------------------
void AUWrapper::setControllerParameter (ParamID pid, ParamValue value)
{
if (editController && pid != factoryProgramChangedID)
editController->setParamNormalized (pid, value);
}
//------------------------------------------------------------------------
ComponentResult AUWrapper::SaveState (CFPropertyListRef* outData)
{
ComponentResult result = AUWRAPPER_BASE_CLASS::SaveState (outData);
if (result != noErr)
return result;
NSMutableDictionary* dict = (NSMutableDictionary*)*outData;
AutoreleasePool ap;
NSMutableData* processorData = [[[NSMutableData alloc] init] autorelease];
NSMutableData* controllerData = [[[NSMutableData alloc] init] autorelease];
if (audioProcessor)
{
NSMutableDataIBStream stream (processorData);
if (FUnknownPtr<IComponent> (audioProcessor)->getState (&stream) != kResultTrue)
{
[processorData setLength:0];
}
}
if (editController)
{
NSMutableDataIBStream stream (controllerData);
if (editController->getState (&stream) != kResultTrue)
{
[controllerData setLength:0];
}
}
[dict setValue:processorData forKey:@"Processor State"];
[dict setValue:controllerData forKey:@"Controller State"];
*outData = dict;
return result;
}
//------------------------------------------------------------------------
ComponentResult AUWrapper::RestoreState (CFPropertyListRef inData)
{
return restoreState (inData, false);
}
//------------------------------------------------------------------------
ComponentResult AUWrapper::restoreState (CFPropertyListRef inData, bool fromProject)
{
AutoreleasePool ap;
if (CFGetTypeID (inData) != CFDictionaryGetTypeID ())
return kAudioUnitErr_InvalidPropertyValue;
AudioComponentDescription desc = GetComponentDescription ();
CFDictionaryRef dict = static_cast<CFDictionaryRef> (inData);
if (CFDictionaryContainsKey (
dict, [NSString stringWithCString:kAUPresetPartKey encoding:NSASCIIStringEncoding]))
return kAudioUnitErr_InvalidPropertyValue;
#define kCurrentSavedStateVersion 0
CFNumberRef cfnum = reinterpret_cast<CFNumberRef> (CFDictionaryGetValue (
dict, [NSString stringWithCString:kAUPresetVersionKey encoding:NSASCIIStringEncoding]));
if (cfnum == NULL)
return kAudioUnitErr_InvalidPropertyValue;
SInt32 value;
CFNumberGetValue (cfnum, kCFNumberSInt32Type, &value);
if (value != kCurrentSavedStateVersion)
return kAudioUnitErr_InvalidPropertyValue;
cfnum = reinterpret_cast<CFNumberRef> (CFDictionaryGetValue (
dict, [NSString stringWithCString:kAUPresetSubtypeKey encoding:NSASCIIStringEncoding]));
if (cfnum == NULL)
return kAudioUnitErr_InvalidPropertyValue;
CFNumberGetValue (cfnum, kCFNumberSInt32Type, &value);
if (UInt32 (value) != desc.componentSubType)
return kAudioUnitErr_InvalidPropertyValue;
cfnum = reinterpret_cast<CFNumberRef> (
CFDictionaryGetValue (dict, [NSString stringWithCString:kAUPresetManufacturerKey
encoding:NSASCIIStringEncoding]));
if (cfnum == NULL)
return kAudioUnitErr_InvalidPropertyValue;
CFNumberGetValue (cfnum, kCFNumberSInt32Type, &value);
if (UInt32 (value) != desc.componentManufacturer)
return kAudioUnitErr_InvalidPropertyValue;
ComponentResult result = noErr;
if (result == noErr && audioProcessor && editController &&
CFGetTypeID (inData) == CFDictionaryGetTypeID ())
{
// Note: the bypass state is not part of the AU state
bool wasBypassed = false;
if (bypassParamID != -1)
{
wasBypassed = editController->getParamNormalized (bypassParamID) >= 0.5 ? true : false;
}
NSDictionary* dict = (NSDictionary*)inData;
NSData* processorData = [dict valueForKey:@"Processor State"];
auto processLen = [processorData length];
if (processLen == 0)
return kAudioUnitErr_InvalidPropertyValue;
if (processorData)
{
NSDataIBStream stream (processorData);
if (fromProject)
stream.getAttributes ()->setString (Vst::PresetAttributes::kStateType,
String (Vst::StateType::kProject));
FUnknownPtr<IComponent> (audioProcessor)->setState (&stream);
}
NSData* controllerData = [dict valueForKey:@"Controller State"];
if (controllerData)
{
NSDataIBStream processorStream (processorData);
editController->setComponentState (&processorStream);
NSDataIBStream stream (controllerData);
if (fromProject)
stream.getAttributes ()->setString (Vst::PresetAttributes::kStateType,
String (Vst::StateType::kProject));
editController->setState (&stream);
syncParameterValues ();
cacheParameterValues ();
}
if (bypassParamID != -1)
{
transferParamChanges.addChange (bypassParamID, wasBypassed ? 1 : 0, 0);
editController->setParamNormalized (bypassParamID, wasBypassed ? 1 : 0);
}
}
return result;
}
//------------------------------------------------------------------------
OSStatus AUWrapper::GetPresets (CFArrayRef* outData) const
{
if (presets && numPresets > 0)
{
if (outData != nullptr)
{
CFMutableArrayRef presetsArray = CFArrayCreateMutable (NULL, numPresets, NULL);
for (int32 i = 0; i < numPresets; i++)
{
CFArrayAppendValue (presetsArray, &presets[i]);
}
*outData = (CFArrayRef)presetsArray;
}
return noErr;
}
return kAudioUnitErr_InvalidProperty;
}
//------------------------------------------------------------------------
OSStatus AUWrapper::NewFactoryPresetSet (const AUPreset& inNewFactoryPreset)
{
if (numPresets > 0)
{
float normalizedValue = (float)inNewFactoryPreset.presetNumber / (float)numPresets;
transferParamChanges.addChange (factoryProgramChangedID, normalizedValue, 0);
editController->setParamNormalized (factoryProgramChangedID, normalizedValue);
if (inNewFactoryPreset.presetNumber < numPresets)
SetAFactoryPresetAsCurrent (presets[inNewFactoryPreset.presetNumber]);
return noErr;
}
return kAudioUnitErr_InvalidProperty;
}
//------------------------------------------------------------------------
ComponentResult AUWrapper::Render (AudioUnitRenderActionFlags& ioActionFlags,
const AudioTimeStamp& inTimeStamp, UInt32 inNumberFrames)
{
#if !AUSDK_MIDI2_AVAILABLE
midiMappingTransfer.accessTransferObject_rt (
[&] (auto& obj) { midiMappingCache = std::move (obj); });
#endif
updateProcessContext ();
processContext.systemTime = inTimeStamp.mHostTime;
processParamChanges.clearQueue ();
transferParamChanges.transferChangesTo (processParamChanges);
processData.numSamples = inNumberFrames;
processData.processMode = isOfflineRender? kOffline : kRealtime;
for (int32 i = 0; i < Inputs ().GetNumberOfElements (); i++)
{
AUInputElement* input = GetInput (i);
if (input->IsActive ())
input->PullInput (ioActionFlags, inTimeStamp, i, inNumberFrames);
processData.inputs[i].numChannels = input->GetStreamFormat ().mChannelsPerFrame;
for (int32 channel = 0; channel < input->GetStreamFormat ().mChannelsPerFrame; channel++)
{
processData.inputs[i].channelBuffers32[channel] =
input->IsActive () ? (Sample32*)input->GetBufferList ().mBuffers[channel].mData :
nullptr;
}
}
for (int32 i = 0; i < Outputs ().GetNumberOfElements (); i++)
{
AUOutputElement* output = GetOutput (i);
output->PrepareBuffer (inNumberFrames);
processData.outputs[i].numChannels = output->GetStreamFormat ().mChannelsPerFrame;
for (int32 channel = 0; channel < output->GetStreamFormat ().mChannelsPerFrame; channel++)
{
processData.outputs[i].channelBuffers32[channel] =
(Sample32*)output->GetBufferList ().mBuffers[channel].mData;
}
}
audioProcessor->process (processData);
outputParamTransfer.transferChangesFrom (outputParamChanges);
outputParamChanges.clearQueue ();
eventList.clear ();
ioActionFlags &= ~kAudioUnitRenderAction_OutputIsSilence;
processOutputEvents (inTimeStamp);
return noErr;
}
static constexpr uint8 kNoteOff = 0x80; ///< note, off velocity
static constexpr uint8 kNoteOn = 0x90; ///< note, on velocity
static constexpr uint8 kPolyPressure = 0xA0; ///< note, pressure
static constexpr uint8 kController = 0xB0; ///< controller, value
static constexpr uint8 kProgramChangeStatus = 0xC0; ///< program change
static constexpr uint8 kAfterTouchStatus = 0xD0; ///< channel pressure
static constexpr uint8 kPitchBendStatus = 0xE0; ///< lsb, msb
//const float kMidiScaler = 1.f / 127.f;
static constexpr uint8 kChannelMask = 0x0F;
//static const uint8 kStatusMask = 0xF0;
static constexpr uint32 kDataMask = 0x7F;
//------------------------------------------------------------------------
inline void AUWrapper::processOutputEvents (const AudioTimeStamp& inTimeStamp)
{
if (midiOutCount > 0 && outputEvents.getEventCount () > 0)
{
int32 total = outputEvents.getEventCount ();
Event e = {};
for (int32 i = 0; i < total; i++)
{
if (outputEvents.getEvent (i, e) != kResultOk)
break;
//--- SYSEX ----------------
if (e.type == Event::kDataEvent && e.data.type == DataEvent::kMidiSysEx)
{
}
else
{
switch (e.type)
{
//--- -----------------------
case Event::kNoteOnEvent:
{
UInt8 status = (UInt8) (kNoteOn | (e.noteOn.channel & kChannelMask));
UInt8 data1 = (UInt8) (e.noteOn.pitch & kDataMask);
UInt8 data2 =
(UInt8) ((int32) (e.noteOn.velocity * 127.f + 0.4999999f) & kDataMask);
UInt8 channel = e.noteOn.channel;
mCallbackHelper->addEvent (status, channel, data1, data2, e.sampleOffset);
}
break;
//--- -----------------------
case Event::kNoteOffEvent:
{
UInt8 status = (UInt8) (kNoteOff | (e.noteOff.channel & kChannelMask));
UInt8 data1 = e.noteOff.pitch;
UInt8 data2 =
(UInt8) ((int32) (e.noteOff.velocity * 127.f + 0.4999999f) & kDataMask);
UInt8 channel = e.noteOff.channel;
mCallbackHelper->addEvent (status, channel, data1, data2, e.sampleOffset);
}
break;
}
}
}
outputEvents.clear ();
mCallbackHelper->fireAtTimeStamp (inTimeStamp);
}
}
//--------------------------------------------------------------------------------------------
#ifdef SMTG_AUWRAPPER_USES_AUSDK
OSStatus AUWrapper::GetPropertyInfo (AudioUnitPropertyID inID, AudioUnitScope inScope,
AudioUnitElement inElement, UInt32& outDataSize,
bool& outWritable)
#else
OSStatus AUWrapper::GetPropertyInfo (AudioUnitPropertyID inID, AudioUnitScope inScope,
AudioUnitElement inElement, UInt32& outDataSize,
Boolean& outWritable)
#endif
{
switch (inID)
{
//--- -----------------------
case kAudioUnitProperty_BypassEffect:
{
if (inScope == kAudioUnitScope_Global && bypassParamID != -1)
{
outWritable = true;
outDataSize = sizeof (UInt32);
return noErr;
}
return kAudioUnitErr_InvalidProperty;
}
//--- -----------------------
case kAudioUnitProperty_ParameterClumpName:
{
if (bypassParamID != -1)
{
outWritable = false;
outDataSize = sizeof (AudioUnitParameterNameInfo);
return noErr;
}
break;
}
//--- -----------------------
case kAudioUnitProperty_ParameterStringFromValue:
{
if (inScope == kAudioUnitScope_Global)
{
outDataSize = sizeof (AudioUnitParameterStringFromValue);
outWritable = false;
return noErr;
}
return kAudioUnitErr_InvalidProperty;
}
//--- -----------------------
case kAudioUnitProperty_ParameterValueFromString:
{
if (inScope == kAudioUnitScope_Global)
{
outDataSize = sizeof (AudioUnitParameterValueFromString);
outWritable = false;
return noErr;
}
return kAudioUnitErr_InvalidProperty;
}
//--- -----------------------
case kAudioUnitProperty_ElementName:
{
if (inScope == kAudioUnitScope_Input || inScope == kAudioUnitScope_Output)
{
outDataSize = sizeof (CFStringRef);
return noErr;
}
break;
}
//--- -----------------------
case kAudioUnitProperty_CocoaUI:
{
outWritable = false;
outDataSize = sizeof (AudioUnitCocoaViewInfo);
return noErr;
}
//--- -----------------------
case kAudioUnitProperty_MIDIOutputCallbackInfo:
{
if (inScope == kAudioUnitScope_Global && midiOutCount > 0)
{
outDataSize = sizeof (CFArrayRef);
outWritable = false;
return noErr;
}
break;
}
//--- -----------------------
case kAudioUnitProperty_MIDIOutputCallback:
{
if (inScope == kAudioUnitScope_Global && midiOutCount > 0)
{
outDataSize = sizeof (AUMIDIOutputCallbackStruct);
outWritable = true;
return noErr;
}
break;
}
#if AUSDK_MIDI2_AVAILABLE
//--- -----------------------
case kAudioUnitProperty_AudioUnitMIDIProtocol:
{
if (inScope == kAudioUnitScope_Global)
{
outDataSize = sizeof (SInt32);
outWritable = false;
return noErr;
}
return kAudioUnitErr_InvalidProperty;
}
#endif
//--- -----------------------
case 64000:
{
if (editController)
{
outDataSize = sizeof (TPtrInt);
outWritable = false;
return noErr;
}
return kAudioUnitErr_InvalidProperty;
}
//--- -----------------------
case 64001:
{
if (VST3DynLibrary::gInstance)
{
outDataSize = sizeof (TPtrInt);
outWritable = false;
return noErr;
}
return kAudioUnitErr_InvalidProperty;
}
//--- -----------------------
case kAudioUnitProperty_OfflineRender:
{
if (inScope == kAudioUnitScope_Global)
{
outWritable = true;
outDataSize = sizeof (UInt32);
return noErr;
}
return kAudioUnitErr_InvalidProperty;
}
}
return AUWRAPPER_BASE_CLASS::GetPropertyInfo (inID, inScope, inElement, outDataSize,
outWritable);
}
//--------------------------------------------------------------------------------------------
ComponentResult AUWrapper::SetProperty (AudioUnitPropertyID inID, AudioUnitScope inScope,
AudioUnitElement inElement, const void* inData,
UInt32 inDataSize)
{
switch (inID)
{
//--- -----------------------
case kAudioUnitProperty_BypassEffect:
{
if (inScope == kAudioUnitScope_Global && bypassParamID != -1)
{
bool tempNewSetting = *((UInt32*)inData) != 0;
transferParamChanges.addChange (bypassParamID, tempNewSetting ? 1 : 0, 0);
editController->setParamNormalized (bypassParamID, tempNewSetting ? 1 : 0);
isBypassed = tempNewSetting;
return noErr;
}
break;
}
//--- -----------------------
case kAudioUnitProperty_MIDIOutputCallback:
{
if (inScope == kAudioUnitScope_Global && midiOutCount > 0)
{
if (inDataSize < sizeof (AUMIDIOutputCallbackStruct))
return kAudioUnitErr_InvalidPropertyValue;
AUMIDIOutputCallbackStruct* callbackStruct = (AUMIDIOutputCallbackStruct*)inData;
mCallbackHelper->setCallbackInfo (callbackStruct->midiOutputCallback,
callbackStruct->userData);
return noErr;
}
break;
}
//--- -----------------------
case kAudioUnitProperty_OfflineRender:
{
if (inScope == kAudioUnitScope_Global)
{
bool offline = *((UInt32*)inData) != 0;
if(offline != isOfflineRender && audioProcessor != nullptr)
{
isOfflineRender = offline;
auto component = U::cast<IComponent> (audioProcessor);
if(IsInitialized())
{
audioProcessor->setProcessing (false);
component->setActive (false);
}
ProcessSetup ps;
ps.sampleRate = getSampleRate ();
ps.maxSamplesPerBlock = GetMaxFramesPerSlice ();
ps.symbolicSampleSize = kSample32;
ps.processMode = isOfflineRender? kOffline : kRealtime;
audioProcessor->setupProcessing (ps);
if(IsInitialized())
{
component->setActive (true);
audioProcessor->setProcessing (true);
}
}
return noErr;
}
break;
}
}
return AUWRAPPER_BASE_CLASS::SetProperty (inID, inScope, inElement, inData, inDataSize);
}
//--------------------------------------------------------------------------------------------
bool AUWrapper::CanScheduleParameters () const
{
return false;
}
//--------------------------------------------------------------------------------------------
ComponentResult AUWrapper::GetProperty (AudioUnitPropertyID inID, AudioUnitScope inScope,
AudioUnitElement inElement, void* outData)
{
switch (inID)
{
//--- -----------------------
case kAudioUnitProperty_BypassEffect:
{
if (inScope == kAudioUnitScope_Global && bypassParamID != -1)
{
UInt32 bypass = editController->getParamNormalized (bypassParamID) >= 0.5 ? 1 : 0;
*((UInt32*)outData) = bypass;
return noErr;
}
break;
}
//--- -----------------------
case kAudioUnitProperty_ParameterClumpName:
{
AudioUnitParameterNameInfo* parameterNameInfo = (AudioUnitParameterNameInfo*)outData;
int32 clumpId = parameterNameInfo->inID;
if (clumpId < 1 || clumpId > clumpGroups.size ())
return kAudioUnitErr_PropertyNotInUse;
const String& clumpGroup = clumpGroups.at (clumpId - 1);
parameterNameInfo->outName = (CFStringRef)clumpGroup.toCFStringRef ();
return noErr;
}
//--- -----------------------
case kAudioUnitProperty_ParameterStringFromValue:
{
if (inScope == kAudioUnitScope_Global)
{
AudioUnitParameterStringFromValue* ps = (AudioUnitParameterStringFromValue*)outData;
String128 paramString;
if (editController->getParamStringByValue (
ps->inParamID,
ps->inValue ? *ps->inValue :
editController->getParamNormalized (ps->inParamID),
paramString) == kResultTrue)
ps->outString = createCFStringFromString128 (paramString);
else
ps->outString = CFStringCreateWithCString (nullptr, "", kCFStringEncodingUTF8);
return noErr;
}
return kAudioUnitErr_InvalidProperty;
}
//--- -----------------------
case kAudioUnitProperty_ParameterValueFromString:
{
if (inScope == kAudioUnitScope_Global)
{
AudioUnitParameterValueFromString* ps = (AudioUnitParameterValueFromString*)outData;
String128 paramString;
createString128FromCFString (ps->inString, paramString);
ParamValue value;
if (editController->getParamValueByString (ps->inParamID, paramString, value) ==
kResultTrue)
{
ps->outValue = value;
return noErr;
}
return -1;
}
return kAudioUnitErr_InvalidProperty;
}
//--- -----------------------
case kAudioUnitProperty_ElementName:
{
if (inScope == kAudioUnitScope_Input || inScope == kAudioUnitScope_Output)
{
auto component = U::cast<IComponent> (audioProcessor);
BusInfo busInfo;
if (component->getBusInfo (kAudio,
inScope == kAudioUnitScope_Input ? kInput : kOutput,
inElement, busInfo) == kResultTrue)
{
outData = (void*)createCFStringFromString128 (busInfo.name);
return noErr;
}
}
return kAudioUnitErr_InvalidProperty;
}
//--- -----------------------
case kAudioUnitProperty_CocoaUI:
{
AutoreleasePool ap;
CFStringRef className = (CFStringRef)[[NSString alloc]
initWithCString:SMTG_MAKE_STRING (SMTG_AUCocoaUIBase_CLASS_NAME)
encoding:NSUTF8StringEncoding];
const char* image = class_getImageName (
objc_getClass (SMTG_MAKE_STRING (SMTG_AUCocoaUIBase_CLASS_NAME)));
CFBundleRef bundle = GetBundleFromExecutable (image);
CFURLRef url = CFBundleCopyBundleURL (bundle);
CFRelease (bundle);
AudioUnitCocoaViewInfo cocoaInfo = {url, {className}};
*((AudioUnitCocoaViewInfo*)outData) = cocoaInfo;
return noErr;
}
//--- -----------------------
case kAudioUnitProperty_MIDIOutputCallbackInfo:
{
if (inScope == kAudioUnitScope_Global && midiOutCount > 0)
{
CFStringRef strs[1];
strs[0] = CFSTR ("MIDI Callback");
CFArrayRef callbackArray =
CFArrayCreate (NULL, (const void**)strs, 1, &kCFTypeArrayCallBacks);
*(CFArrayRef*)outData = callbackArray;
return noErr;
}
return kAudioUnitErr_InvalidProperty;
}
#if AUSDK_MIDI2_AVAILABLE
//--- -----------------------
case kAudioUnitProperty_HostMIDIProtocol:
{
if (inScope == kAudioUnitScope_Global)
return noErr;
return kAudioUnitErr_InvalidProperty;
}
//--- -----------------------
case kAudioUnitProperty_AudioUnitMIDIProtocol:
{
if (inScope == kAudioUnitScope_Global)
{
*reinterpret_cast<SInt32*>(outData) = MIDIProtocolID::kMIDIProtocol_2_0;
return noErr;
}
return kAudioUnitErr_InvalidProperty;
}
#endif // AUSDK_MIDI2_AVAILABLE
//--- -----------------------
case 64000:
{
if (editController)
{
TPtrInt ptr = (TPtrInt)editController;
*((TPtrInt*)outData) = ptr;
return noErr;
}
else
*((TPtrInt*)outData) = 0;
return kAudioUnitErr_InvalidProperty;
}
//--- -----------------------
case 64001:
{
if (VST3DynLibrary::gInstance)
{
TPtrInt ptr = (TPtrInt)VST3DynLibrary::gInstance;
*((TPtrInt*)outData) = ptr;
return noErr;
}
else
*((TPtrInt*)outData) = 0;
return kAudioUnitErr_InvalidProperty;
}
}
return AUWRAPPER_BASE_CLASS::GetProperty (inID, inScope, inElement, outData);
}
#if !CA_USE_AUDIO_PLUGIN_ONLY && !defined(SMTG_AUWRAPPER_USES_AUSDK)
//------------------------------------------------------------------------
int AUWrapper::GetNumCustomUIComponents ()
{
#if !__LP64__
if (editController)
{
static int numUIComponents = 0;
static bool once = true;
if (once)
{
AutoreleasePool ap;
once = false;
IPlugView* plugView = editController->createView (ViewType::kEditor);
if (plugView)
{
if (plugView->isPlatformTypeSupported (kPlatformTypeHIView) == kResultTrue)
numUIComponents = 1;
plugView->release ();
}
}
return numUIComponents;
}
#endif // !__LP64__
return 0;
}
//------------------------------------------------------------------------
void AUWrapper::GetUIComponentDescs (ComponentDescription* inDescArray)
{
#if !__LP64__
if (editController && GetNumCustomUIComponents () > 0)
{
static OSType subType = 0;
static OSType manuf = 0;
static bool once = true;
if (once)
{
once = false;
short prevResFile = CurResFile ();
CFBundleRef bundle = GetCurrentBundle ();
if (!bundle)
return;
short resourceFileID = CFBundleOpenBundleResourceMap (bundle);
UseResFile (resourceFileID);
Handle h = Get1Resource ('thng', 9000);
if (h)
{
HLock (h);
OSType* hPtr = (OSType*)*h;
subType = hPtr[1];
manuf = hPtr[2];
HUnlock (h);
}
UseResFile (prevResFile);
CFRelease (bundle);
}
inDescArray[0].componentType = kAudioUnitCarbonViewComponentType;
inDescArray[0].componentSubType = subType;
inDescArray[0].componentManufacturer = manuf;
inDescArray[0].componentFlags = 0;
inDescArray[0].componentFlagsMask = 0;
}
#endif // !__LP64__
}
#endif // !CA_USE_AUDIO_PLUGIN_ONLY && !defined(SMTG_AUWRAPPER_USES_AUSDK)
//------------------------------------------------------------------------
Float64 AUWrapper::GetLatency ()
{
Float64 latencyInSeconds = 0.0;
if (audioProcessor)
latencyInSeconds = audioProcessor->getLatencySamples () / getSampleRate ();
return latencyInSeconds;
}
//------------------------------------------------------------------------
Float64 AUWrapper::GetTailTime ()
{
Float64 tailTimeInSeconds = 0.0;
if (audioProcessor)
tailTimeInSeconds = audioProcessor->getTailSamples () / getSampleRate ();
return tailTimeInSeconds;
}
#if !CA_USE_AUDIO_PLUGIN_ONLY
//------------------------------------------------------------------------
// MARK: MusicDeviceBase
//------------------------------------------------------------------------
OSStatus AUWrapper::HandleNoteOn (UInt8 inChannel, UInt8 inNoteNumber, UInt8 inVelocity,
UInt32 inStartFrame)
{
Event e = {};
e.type = Event::kNoteOnEvent;
e.noteOn.channel = inChannel;
e.noteOn.pitch = inNoteNumber;
e.noteOn.velocity = inVelocity / 127.;
e.noteOn.noteId = inNoteNumber;
e.sampleOffset = inStartFrame;
eventList.addEvent (e);
return noErr;
}
//------------------------------------------------------------------------
OSStatus AUWrapper::HandleNoteOff (UInt8 inChannel, UInt8 inNoteNumber, UInt8 inVelocity,
UInt32 inStartFrame)
{
Event e = {};
e.type = Event::kNoteOffEvent;
e.noteOff.channel = inChannel;
e.noteOff.pitch = inNoteNumber;
e.noteOff.velocity = inVelocity / 127.;
e.noteOff.noteId = inNoteNumber;
e.sampleOffset = inStartFrame;
eventList.addEvent (e);
return noErr;
}
//------------------------------------------------------------------------
ComponentResult AUWrapper::StartNote (MusicDeviceInstrumentID inInstrument,
MusicDeviceGroupID inGroupID,
NoteInstanceID* outNoteInstanceID, UInt32 inOffsetSampleFrame,
const MusicDeviceNoteParams& inParams)
{
NoteInstanceID noteID = ((UInt8)inParams.mPitch) | ((noteCounter++) << 8);
Event e = {};
e.type = Event::kNoteOnEvent;
e.noteOn.pitch = inParams.mPitch;
e.noteOn.velocity = inParams.mVelocity / 127.;
e.noteOn.noteId = noteID;
e.sampleOffset = inOffsetSampleFrame;
// The Group ID is the channel with a standard midi device
e.noteOn.channel = inGroupID;
eventList.addEvent (e);
if (outNoteInstanceID)
*outNoteInstanceID = noteID;
return noErr;
}
//------------------------------------------------------------------------
ComponentResult AUWrapper::StopNote (MusicDeviceGroupID inGroupID, NoteInstanceID inNoteInstanceID,
UInt32 inOffsetSampleFrame)
{
UInt32 pitch = inNoteInstanceID & 0xFF;
Event e = {};
e.type = Event::kNoteOffEvent;
e.noteOff.pitch = pitch;
e.noteOff.velocity = 0;
e.noteOff.noteId = inNoteInstanceID;
e.sampleOffset = inOffsetSampleFrame;
// The Group ID is the channel with a standard midi device
e.noteOff.channel = inGroupID;
eventList.addEvent (e);
return noErr;
}
//--------------------------------------------------------------------------------------------
OSStatus AUWrapper::GetInstrumentCount (UInt32& outInstCount) const
{
outInstCount = 1;
return noErr;
}
//------------------------------------------------------------------------
// MARK: AUMIDIBase
//------------------------------------------------------------------------
OSStatus AUWrapper::HandleNonNoteEvent (UInt8 status, UInt8 channel, UInt8 data1, UInt8 data2,
UInt32 inStartFrame)
{
OSStatus result = noErr;
if (status == kPolyPressure) // kMidiMessage_PolyPressure
{
Event e = {};
e.type = Event::kPolyPressureEvent;
e.polyPressure.channel = channel;
e.polyPressure.pitch = data1;
e.polyPressure.pressure = data2 / 127.;
e.sampleOffset = inStartFrame;
eventList.addEvent (e);
return result;
}
if (midiMappingCache.empty ())
return result;
ParamID pid = kNoParamId;
ParamValue value = 0;
CtrlNumber cn = -1;
bool prgChange = false;
switch (status)
{
//--- -----------------------
case kPitchBendStatus: // kMidiMessage_PitchWheel
{
cn = kPitchBend;
unsigned short _14bit;
_14bit = (unsigned short)data2;
_14bit <<= 7;
_14bit |= (unsigned short)data1;
value = (double)_14bit / (double)0x3FFF;
break;
}
//--- -----------------------
case kAfterTouchStatus: // kMidiMessage_ChannelPressure
{
cn = kAfterTouch;
value = data1 / 127.f;
break;
}
//--- -----------------------
case kController: // kMidiMessage_ControlChange
{
cn = data1;
value = data2 / 127.f;
break;
}
//--- -----------------------
case kProgramChangeStatus: // kMidiMessage_ProgramChange
{
programChangeInfoTransfer.accessTransferObject_rt ([this] (const auto& pci) {
programChangeInfos = std::move (pci);
});
pid = programChangeInfos[channel].pid;
if (pid != kNoParamId && programChangeInfos[channel].numPrograms > 0 &&
data1 < programChangeInfos[channel].numPrograms)
{
value = static_cast<ParamValue> (data1) /
static_cast<ParamValue> (programChangeInfos[channel].numPrograms - 1);
prgChange = true;
}
}
}
if (prgChange || cn >= 0)
{
if (pid == kNoParamId && channel < midiMappingCache.busList[0].size ())
{
auto it = midiMappingCache.busList[0][channel].find (cn);
if (it != midiMappingCache.busList[0][channel].end ())
pid = it->second;
}
if (pid != kNoParamId)
{
beginEdit (pid);
performEdit (pid, value);
endEdit (pid);
// make sure that our edit controller get the changes
int32 index;
IParamValueQueue* vq = outputParamChanges.addParameterData (pid, index);
if (vq)
vq->addPoint (inStartFrame, value, index);
}
}
if (midiLearn && cn >= 0)
midiLearnRingBuffer.push ({0, channel, cn});
return result;
}
#endif // !CA_USE_AUDIO_PLUGIN_ONLY
#if AUSDK_MIDI2_AVAILABLE
//------------------------------------------------------------------------
bool AUWrapper::handleMIDIEventPacket (UInt32 inOffsetSampleFrame, const MIDIEventPacket* packet)
{
struct UMPHandler final : UMP::UniversalMidiPacketHandlerAdapter
{
AUWrapper& w;
UInt32 sampleOffset;
UMPHandler (AUWrapper& wrapper, UInt32 sampleOffset)
: w (wrapper), sampleOffset (sampleOffset)
{
}
void onNoteOn (Group group, Channel channel, NoteNumber note, Velocity16 velocity,
AttributeType attr, AttributeValue attrValue) const override
{
Event e = {};
e.type = Event::kNoteOnEvent;
e.noteOn.channel = channel;
e.noteOn.pitch = note;
e.noteOn.velocity = static_cast<float> (velocity) /
static_cast<float> (std::numeric_limits<Velocity16>::max ());
e.noteOn.noteId = note;
e.sampleOffset = sampleOffset;
w.eventList.addEvent (e);
}
void onNoteOff (Group group, Channel channel, NoteNumber note, Velocity16 velocity,
AttributeType attr, AttributeValue attrValue) const override
{
Event e = {};
e.type = Event::kNoteOffEvent;
e.noteOff.channel = channel;
e.noteOff.pitch = note;
e.noteOff.velocity = static_cast<float> (velocity) /
static_cast<float> (std::numeric_limits<Velocity16>::max ());
e.noteOff.noteId = note;
e.sampleOffset = sampleOffset;
w.eventList.addEvent (e);
}
void onPolyPressure (Group group, Channel channel, NoteNumber note,
Data32 data) const override
{
Event e = {};
e.type = Event::kPolyPressureEvent;
e.polyPressure.channel = channel;
e.polyPressure.pitch = note;
e.polyPressure.pressure =
data / static_cast<float> (std::numeric_limits<Data32>::max ());
e.sampleOffset = sampleOffset;
w.eventList.addEvent (e);
}
void onProgramChange (Group group, Channel channel, OptionFlags options, Program program,
BankMSB bankMSB, BankLSB bankLSB) const override
{
w.programChangeInfoTransfer.accessTransferObject_rt ([&] (const auto& pci) {
w.programChangeInfos = std::move (pci);
});
auto pid = w.programChangeInfos[channel].pid;
if (pid != kNoParamId && w.programChangeInfos[channel].numPrograms > 0 &&
program < w.programChangeInfos[channel].numPrograms)
{
auto value =
static_cast<ParamValue> (program) /
static_cast<ParamValue> (w.programChangeInfos[channel].numPrograms - 1);
addParameterChange (pid, value);
}
}
void onControlChange (Group group, Channel channel, ControllerNumber controller,
Data32 data) const override
{
if (!w.midiMappingCache.empty () && controller < ControllerNumbers::kCountCtrlNumber)
{
ParamID pid {};
constexpr std::array ignored = {
ControllerNumbers::kCtrlBankSelectMSB, ControllerNumbers::kCtrlDataEntryMSB,
ControllerNumbers::kCtrlBankSelectLSB, ControllerNumbers::kCtrlDataEntryLSB,
ControllerNumbers::kCtrlNRPNSelectLSB, ControllerNumbers::kCtrlNRPNSelectMSB,
ControllerNumbers::kCtrlRPNSelectLSB, ControllerNumbers::kCtrlRPNSelectMSB};
if (std::find (ignored.begin (), ignored.end (), controller) != ignored.end ())
return;
addControllerChange (group, channel, controller,
data /
static_cast<float> (std::numeric_limits<Data32>::max ()));
}
}
void onPitchBend (Group group, Channel channel, Data32 data) const override
{
if (!w.midiMappingCache.empty ())
{
addControllerChange (group, channel, ControllerNumbers::kPitchBend,
data /
static_cast<float> (std::numeric_limits<Data32>::max ()));
}
}
void onChannelPressure (Group group, Channel channel, Data32 data) const override
{
if (!w.midiMappingCache.empty ())
{
addControllerChange (group, channel, ControllerNumbers::kAfterTouch,
data /
static_cast<float> (std::numeric_limits<Data32>::max ()));
}
}
void addControllerChange (Group group, Channel channel, ControllerNumber ctrler,
ParamValue value) const
{
assert (!w.midiMappingCache.empty ());
if (channel < w.midiMappingCache.busList[0].size ())
{
auto it = w.midiMappingCache.busList[0][channel].find (ctrler);
if (it != w.midiMappingCache.busList[0][channel].end ())
addParameterChange (it->second, value);
}
if (w.midiLearn)
w.midiLearnRingBuffer.push ({0, channel, ctrler});
}
void addParameterChange (ParamID pid, ParamValue value) const
{
w.beginEdit (pid);
w.performEdit (pid, value);
w.endEdit (pid);
int32 index;
IParamValueQueue* vq = w.outputParamChanges.addParameterData (pid, index);
if (vq)
vq->addPoint (sampleOffset, value, index);
}
};
UMPHandler handler (*this, inOffsetSampleFrame);
return UMP::parsePackets<UMP::ParseSections::ChannelVoice2> (packet->wordCount, packet->words,
handler) == packet->wordCount;
}
//------------------------------------------------------------------------
OSStatus AUWrapper::MIDIEventList (UInt32 inOffsetSampleFrame,
const struct MIDIEventList* eventList)
{
if (eventList->protocol != MIDIProtocolID::kMIDIProtocol_2_0)
return -1;
if (eventList->numPackets == 0)
return noErr;
midiMappingTransfer.accessTransferObject_rt (
[&] (auto& obj) { midiMappingCache = std::move (obj); });
auto eventPacket = &eventList->packet[0];
if (!handleMIDIEventPacket (inOffsetSampleFrame, eventPacket))
return -1;
for (auto i = 1u; i < eventList->numPackets; ++i)
{
eventPacket = MIDIEventPacketNext (eventPacket);
if (!handleMIDIEventPacket (inOffsetSampleFrame, eventPacket))
return -1;
}
return noErr;
}
#endif // AUSDK_MIDI2_AVAILABLE
//------------------------------------------------------------------------
// MARK: IComponentHandler
//------------------------------------------------------------------------
tresult PLUGIN_API AUWrapper::beginEdit (ParamID tag)
{
AudioUnitEvent auEvent;
auEvent.mArgument.mParameter.mAudioUnit = GetComponentInstance ();
auEvent.mArgument.mParameter.mParameterID = tag;
auEvent.mArgument.mParameter.mScope = kAudioUnitScope_Global;
auEvent.mArgument.mParameter.mElement = 0;
auEvent.mEventType = kAudioUnitEvent_BeginParameterChangeGesture;
AUEventListenerNotify (paramListenerRef, NULL, &auEvent);
return kResultTrue;
}
//------------------------------------------------------------------------
tresult PLUGIN_API AUWrapper::performEdit (ParamID tag, ParamValue valueNormalized)
{
AudioUnitParameter sPar = {GetComponentInstance (), tag, kAudioUnitScope_Global, 0};
AUParameterSet (paramListenerRef, NULL, &sPar, valueNormalized, 0);
AudioUnitEvent auEvent;
auEvent.mArgument.mParameter.mAudioUnit = GetComponentInstance ();
auEvent.mArgument.mParameter.mParameterID = tag;
auEvent.mArgument.mParameter.mScope = kAudioUnitScope_Global;
auEvent.mArgument.mParameter.mElement = 0;
auEvent.mEventType = kAudioUnitEvent_ParameterValueChange;
AUEventListenerNotify (paramListenerRef, NULL, &auEvent);
return kResultTrue;
}
//------------------------------------------------------------------------
tresult PLUGIN_API AUWrapper::endEdit (ParamID tag)
{
AudioUnitEvent auEvent;
auEvent.mArgument.mParameter.mAudioUnit = GetComponentInstance ();
auEvent.mArgument.mParameter.mParameterID = tag;
auEvent.mArgument.mParameter.mScope = kAudioUnitScope_Global;
auEvent.mArgument.mParameter.mElement = 0;
auEvent.mEventType = kAudioUnitEvent_EndParameterChangeGesture;
AUEventListenerNotify (paramListenerRef, NULL, &auEvent);
return kResultTrue;
}
//------------------------------------------------------------------------
tresult PLUGIN_API AUWrapper::restartComponent (int32 flags)
{
tresult result = kNotImplemented;
if (flags & kParamValuesChanged)
{
syncParameterValues ();
updateProgramChangesCache ();
result = kResultTrue;
}
if (flags & kMidiCCAssignmentChanged)
{
updateMidiMappingCache ();
result = kResultTrue;
}
if (flags & kParamTitlesChanged)
{
cacheParameterValues ();
result = kResultTrue;
PropertyChanged (kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0);
}
if (flags & kLatencyChanged)
{
AudioUnitEvent auEvent;
auEvent.mArgument.mProperty.mAudioUnit = GetComponentInstance ();
auEvent.mArgument.mProperty.mPropertyID = kAudioUnitProperty_Latency;
auEvent.mArgument.mProperty.mScope = kAudioUnitScope_Global;
auEvent.mArgument.mProperty.mElement = 0;
auEvent.mEventType = kAudioUnitEvent_PropertyChange;
AUEventListenerNotify (paramListenerRef, NULL, &auEvent);
result = kResultTrue;
}
// TODO: finish restartComponent implementation
return result;
}
//------------------------------------------------------------------------
// MARK: Helpers
//------------------------------------------------------------------------
void AUWrapper::syncParameterValues ()
{
if (editController)
{
for (int32 i = 0; i < editController->getParameterCount (); i++)
{
ParameterInfo pi;
editController->getParameterInfo (i, pi);
if (pi.flags & ParameterInfo::kIsBypass)
{
bypassParamID = pi.id;
}
else
{
ParamValue value = editController->getParamNormalized (pi.id);
GlobalScope ().GetElement (0)->SetParameter (pi.id, value);
}
}
}
}
//------------------------------------------------------------------------
void AUWrapper::cacheParameterValues ()
{
clearParameterValueCache ();
if (editController)
{
Steinberg::Base::Thread::FGuard guard (parameterCacheChanging);
auto unitInfoController = U::cast<IUnitInfo> (editController);
if (unitInfoController)
{
if (unitInfos.empty ())
buildUnitInfos (unitInfoController, unitInfos);
}
// This must be cached!
for (int32 i = 0; i < editController->getParameterCount (); i++)
{
AudioUnitParameterInfo auInfo;
ParameterInfo pi;
editController->getParameterInfo (i, pi);
int32 clumpIdx = 0; // Zero should be reserved for values that are not clumped
// Build clump group if possible
if (unitInfoController)
{
String fullUnitName;
ConstString separator (STR ("."));
bool insertSeparator = false;
UnitInfoMap::const_iterator it = unitInfos.find (pi.unitId);
while (it != unitInfos.end () && it->second.id != kRootUnitId)
{
ConstString unitName (it->second.name);
if (unitName.length () > 0)
{
if (insertSeparator)
fullUnitName.insertAt (0, separator);
insertSeparator = true;
fullUnitName.insertAt (0, unitName);
}
it = unitInfos.find (it->second.parentUnitId);
}
if (!fullUnitName.isEmpty ())
{
bool insertClump = true;
clumpIdx = 1;
for (int32 i = 0; i < clumpGroups.size (); i++)
{
const String& str = clumpGroups.at (i);
if (str.compare (fullUnitName) == 0)
{
insertClump = false;
break;
}
++clumpIdx;
}
if (insertClump)
clumpGroups.push_back (fullUnitName);
}
}
auInfo.name[0] = 0;
auInfo.cfNameString = createCFStringFromString128 (pi.title);
auInfo.defaultValue = pi.defaultNormalizedValue;
auInfo.minValue = 0;
auInfo.maxValue = 1;
auInfo.unit = kAudioUnitParameterUnit_CustomUnit;
auInfo.unitName = createCFStringFromString128 (pi.units);
// We release this ourselved when emptying the cache
auInfo.flags =
/*kAudioUnitParameterFlag_CFNameRelease |*/
kAudioUnitParameterFlag_HasCFNameString | kAudioUnitParameterFlag_IsReadable |
kAudioUnitParameterFlag_HasName;
if (clumpIdx > 0)
AUBase::HasClump (auInfo, clumpIdx);
if (!(pi.flags & ParameterInfo::kIsReadOnly))
auInfo.flags |= kAudioUnitParameterFlag_IsWritable;
if (!(pi.flags & ParameterInfo::kCanAutomate))
auInfo.flags |= kAudioUnitParameterFlag_NonRealTime;
if (pi.stepCount == 1)
auInfo.unit = kAudioUnitParameterUnit_Boolean;
cachedParameterInfos[pi.id] = auInfo;
}
}
}
//------------------------------------------------------------------------
void AUWrapper::clearParameterValueCache ()
{
Steinberg::Base::Thread::FGuard guard (parameterCacheChanging);
for (CachedParameterInfoMap::const_iterator it = cachedParameterInfos.begin (),
end = cachedParameterInfos.end ();
it != end; ++it)
{
CFRelease (it->second.cfNameString);
CFRelease (it->second.unitName);
}
cachedParameterInfos.clear ();
}
//------------------------------------------------------------------------
void AUWrapper::updateProcessContext ()
{
memset (&processContext, 0, sizeof (ProcessContext));
processContext.sampleRate = getSampleRate ();
Float64 beat = 0;
Float64 tempo = 0;
if (CallHostBeatAndTempo (&beat, &tempo) == noErr)
{
processContext.state |=
ProcessContext::kTempoValid | ProcessContext::kProjectTimeMusicValid;
processContext.tempo = tempo;
processContext.projectTimeMusic = beat;
}
UInt32 deltaSampleOffsetToNextBeat = 0;
Float32 timeSigNumerator = 0;
UInt32 timeSigDenominator = 0;
Float64 currentMeasureDownBeat = 0;
if (CallHostMusicalTimeLocation (&deltaSampleOffsetToNextBeat, &timeSigNumerator,
&timeSigDenominator, &currentMeasureDownBeat) == noErr)
{
processContext.state |= ProcessContext::kTimeSigValid | ProcessContext::kBarPositionValid |
ProcessContext::kClockValid;
processContext.timeSigNumerator = timeSigNumerator;
processContext.timeSigDenominator = timeSigDenominator;
processContext.samplesToNextClock = deltaSampleOffsetToNextBeat;
processContext.barPositionMusic = currentMeasureDownBeat;
}
Boolean isPlaying;
Boolean transportStateChanged;
Float64 currentSampleInTimeLine;
Boolean isCycling;
Float64 cycleStartBeat;
Float64 cycleEndBeat;
if (CallHostTransportState (&isPlaying, &transportStateChanged, &currentSampleInTimeLine,
&isCycling, &cycleStartBeat, &cycleEndBeat) == noErr)
{
processContext.state |= ProcessContext::kCycleValid;
processContext.cycleStartMusic = cycleStartBeat;
processContext.cycleEndMusic = cycleEndBeat;
processContext.projectTimeSamples = currentSampleInTimeLine;
if (isPlaying)
processContext.state |= ProcessContext::kPlaying;
if (isCycling)
processContext.state |= ProcessContext::kCycleActive;
}
}
//------------------------------------------------------------------------
void AUWrapper::onTimer (Timer* timer)
{
ParamID pid;
ParamValue value;
int32 sampleOffset;
while (outputParamTransfer.getNextChange (pid, value, sampleOffset))
{
GlobalScope ().GetElement (0)->SetParameter (pid, value);
AudioUnitParameter auParam = {};
auParam.mAudioUnit = GetComponentInstance ();
auParam.mElement = 0;
auParam.mScope = kAudioUnitScope_Global;
auParam.mParameterID = pid;
AUParameterListenerNotify (paramListenerRef, nullptr, &auParam);
editController->setParamNormalized (pid, value);
}
if (isBypassed)
{
ProcessData bypassData = processData;
bypassData.numSamples = 0;
processParamChanges.clearQueue ();
transferParamChanges.transferChangesTo (processParamChanges);
if (processParamChanges.getParameterCount () > 0)
{
audioProcessor->process (bypassData);
outputParamTransfer.transferChangesFrom (outputParamChanges);
outputParamChanges.clearQueue ();
}
}
if (midiLearn)
{
MidiLearnEvent event;
while (midiLearnRingBuffer.pop (event))
midiLearn->onLiveMIDIControllerInput (event.busIndex, event.channel, event.midiCC);
}
}
//------------------------------------------------------------------------
bool AUWrapper::validateChannelPair (int inChannelsIn, int inChannelsOut, const AUChannelInfo* info,
UInt32 numChanInfo) const
{
// we've the following cases (some combinations) to test here:
/*
>0 An explicit number of channels on either side
0 that side (generally input!) has no elements
-1 wild card:
-1,-1 any num channels as long as same channels on in and out
-1,-2 any num channels channels on in and out - special meaning
-2+ indicates total num channs AU can handle
- elements configurable to any num channels,
- element count in scope must be writable
*/
// now chan layout can contain -1 for either scope (ie. doesn't care)
for (unsigned int i = 0; i < numChanInfo; ++i)
{
// less than zero on both sides - check for special attributes
if ((info[i].inChannels < 0) && (info[i].outChannels < 0))
{
// these are our wild card matches
if (info[i].inChannels == -1 && info[i].outChannels == -1)
{
if (inChannelsIn && inChannelsOut)
{
if (inChannelsOut == inChannelsIn)
return true;
}
else
return true; // if one of these is zero, then a -1 means any
}
else if ((info[i].inChannels == -1 && info[i].outChannels == -2) ||
(info[i].inChannels == -2 && info[i].outChannels == -1))
{
return true;
}
// these are our total num channels matches
// element count MUST be writable
else
{
bool outWrite = false;
bool inWrite = false;
// IsElementCountWritable (kAudioUnitScope_Output, outWrite);
// IsElementCountWritable (kAudioUnitScope_Input, inWrite);
if (inWrite && outWrite)
{
if ((inChannelsOut <= abs (info[i].outChannels)) &&
(inChannelsIn <= abs (info[i].inChannels)))
{
return true;
}
}
}
}
// special meaning on input, specific num on output
else if (info[i].inChannels < 0)
{
if (info[i].outChannels == inChannelsOut)
{
// can do any in channels
if (info[i].inChannels == -1)
{
return true;
}
// total chans on input
else
{
bool inWrite = false;
// IsElementCountWritable (kAudioUnitScope_Input, inWrite);
if (inWrite && (inChannelsIn <= abs (info[i].inChannels)))
{
return true;
}
}
}
}
// special meaning on output, specific num on input
else if (info[i].outChannels < 0)
{
if (info[i].inChannels == inChannelsIn)
{
// can do any out channels
if (info[i].outChannels == -1)
{
return true;
}
// total chans on output
else
{
bool outWrite = false;
// IsElementCountWritable (kAudioUnitScope_Output, outWrite);
if (outWrite && (inChannelsOut <= abs (info[i].outChannels)))
{
return true;
}
}
}
}
// both chans in struct >= 0 - thus has to explicitly match
else if ((info[i].inChannels == inChannelsIn) && (info[i].outChannels == inChannelsOut))
{
return true;
}
// now check to see if a wild card on the args (inChannelsIn or inChannelsOut chans is zero)
// is found
// tells us to match just one side of the scopes
else if (inChannelsIn == 0)
{
if (info[i].outChannels == inChannelsOut)
{
return true;
}
}
else if (inChannelsOut == 0)
{
if (info[i].inChannels == inChannelsIn)
{
return true;
}
}
}
return false;
}
//------------------------------------------------------------------------
bool AUWrapper::getProgramListAndUnit (int32 midiChannel, UnitID& unitId,
ProgramListID& programListId)
{
programListId = kNoProgramListId;
unitId = -1;
IUnitInfo* unitInfo = NULL;
if (editController &&
editController->queryInterface (IUnitInfo::iid, (void**)&unitInfo) == kResultTrue &&
unitInfo)
{
if (unitInfo->getUnitByBus (kEvent, kInput, 0, midiChannel, unitId) == kResultTrue)
{
int32 unitCount = unitInfo->getUnitCount ();
for (int32 i = 0; i < unitCount; i++)
{
UnitInfo unitInfoStruct = {};
if (unitInfo->getUnitInfo (i, unitInfoStruct) == kResultTrue)
{
if (unitId == unitInfoStruct.id)
{
programListId = unitInfoStruct.programListId;
unitInfo->release ();
return programListId != kNoProgramListId;
}
}
}
}
unitInfo->release ();
}
return false;
}
//------------------------------------------------------------------------
AUWrapper::MIDIOutputCallbackHelper::MIDIOutputCallbackHelper ()
{
mMIDIMessageList.reserve (16);
mMIDICallbackStruct.midiOutputCallback = NULL;
}
//------------------------------------------------------------------------
AUWrapper::MIDIOutputCallbackHelper::~MIDIOutputCallbackHelper () = default;
//------------------------------------------------------------------------
void AUWrapper::MIDIOutputCallbackHelper::setCallbackInfo (AUMIDIOutputCallback callback,
void* userData)
{
mMIDICallbackStruct.midiOutputCallback = callback;
mMIDICallbackStruct.userData = userData;
}
//------------------------------------------------------------------------
void AUWrapper::MIDIOutputCallbackHelper::addEvent (UInt8 status, UInt8 channel, UInt8 data1,
UInt8 data2, UInt32 inStartFrame)
{
MIDIMessageInfoStruct info = {status, channel, data1, data2, inStartFrame};
mMIDIMessageList.push_back (info);
}
//------------------------------------------------------------------------
void AUWrapper::MIDIOutputCallbackHelper::fireAtTimeStamp (const AudioTimeStamp& inTimeStamp)
{
if (!mMIDIMessageList.empty () && mMIDICallbackStruct.midiOutputCallback != nullptr)
{
auto callMidiOutputCallback = [&] (MIDIPacketList* pktlist) {
auto result = mMIDICallbackStruct.midiOutputCallback (mMIDICallbackStruct.userData,
&inTimeStamp, 0, pktlist);
if (result != noErr)
NSLog (@"error calling midiOutputCallback: %d", result);
};
// synthesize the packet list and call the MIDIOutputCallback
// iterate through the vector and get each item
MIDIPacketList* pktlist = PacketList ();
MIDIPacket* pkt = MIDIPacketListInit (pktlist);
for (auto it = mMIDIMessageList.begin (); it != mMIDIMessageList.end (); it++)
{
auto& item = *it;
static_assert (sizeof (Byte) == 1, "the following code expects this");
std::array<Byte, 3> data = {item.status, item.data1, item.data2};
pkt = MIDIPacketListAdd (pktlist, mBuffersAllocated.size (), pkt, item.startFrame,
data.size (), data.data ());
if (pkt == nullptr)
{
// send what we have and then clear the buffer and send again
// issue the callback with what we got
callMidiOutputCallback (pktlist);
// clear stuff we've already processed, and fire again
mMIDIMessageList.erase (mMIDIMessageList.begin (), it);
fireAtTimeStamp (inTimeStamp);
return;
}
}
callMidiOutputCallback (pktlist);
}
mMIDIMessageList.clear ();
}
#if !CA_USE_AUDIO_PLUGIN_ONLY && !defined(SMTG_AUWRAPPER_USES_AUSDK)
// copied from AUDispatch.cpp
#if __LP64__
// comp instance, parameters in forward order
#define PARAM(_typ, _name, _index, _nparams) _typ _name = *(_typ*)&params->params[_index + 1];
#else
// parameters in reverse order, then comp instance
#define PARAM(_typ, _name, _index, _nparams) \
_typ _name = *(_typ*)&params->params[_nparams - 1 - _index];
#endif
//------------------------------------------------------------------------
ComponentResult AUWrapper::ComponentEntryDispatch (ComponentParameters* params, AUWrapper* This)
{
OSStatus result = noErr;
switch (params->what)
{
//--- -----------------------
case kAudioUnitSetPropertySelect:
{
PARAM (AudioUnitPropertyID, inID, 0, 5);
if (inID == kAudioUnitProperty_ClassInfoFromDocument)
{
PARAM (AudioUnitScope, inScope, 1, 5);
// PARAM (AudioUnitElement, inElement, 2, 5);
PARAM (const void*, inData, 3, 5);
PARAM (UInt32, inDataSize, 4, 5);
ca_require (inDataSize == sizeof (CFPropertyListRef*), InvalidPropertyValue);
ca_require (inScope == kAudioUnitScope_Global, InvalidScope);
result = This->restoreState (*(CFPropertyListRef*)inData, true);
return result;
}
break;
}
}
return AUWRAPPER_BASE_CLASS::ComponentEntryDispatch (params, This);
InvalidScope:
return kAudioUnitErr_InvalidScope;
InvalidPropertyValue:
return kAudioUnitErr_InvalidPropertyValue;
}
#endif
//------------------------------------------------------------------------
//------------------------------------------------------------------------
//------------------------------------------------------------------------
class CleanupMemory
{
public:
CleanupMemory () {}
~CleanupMemory ()
{
if (channelInfos)
{
delete[] channelInfos;
channelInfos = nullptr;
}
}
};
CleanupMemory _gCleanupMemory;
CleanupMemory* gCleanupMemory = &_gCleanupMemory;
static OSStatus AUWrapperMethodSetProperty (void* self, AudioUnitPropertyID inID,
AudioUnitScope inScope, AudioUnitElement inElement,
const void* inData, UInt32 inDataSize)
{
OSStatus result = noErr;
auto plugInstance = reinterpret_cast<AudioComponentPlugInInstance*> (self);
auto auwrapper = reinterpret_cast<AUWrapper*> (&plugInstance->mInstanceStorage);
try
{
if (inData && inDataSize && inID == kAudioUnitProperty_ClassInfoFromDocument)
{
#ifdef SMTG_AUWRAPPER_USES_AUSDK
ThrowExceptionIf (!inDataSize == sizeof (CFPropertyListRef*),
kAudioUnitErr_InvalidPropertyValue);
ThrowExceptionIf (!inScope == kAudioUnitScope_Global, kAudioUnitErr_InvalidScope);
#else
#endif // SMTG_AUWRAPPER_USES_AUSDK
return auwrapper->restoreState (*(CFPropertyListRef*)inData, true);
}
return auwrapper->DispatchSetProperty (inID, inScope, inElement, inData, inDataSize);
}
#ifdef SMTG_AUWRAPPER_USES_AUSDK
AUSDK_Catch (result) return result;
#else
catch (OSStatus err)
{
return err;
}
catch (...)
{
return -1;
}
InvalidScope:
return kAudioUnitErr_InvalidScope;
InvalidPropertyValue:
return kAudioUnitErr_InvalidPropertyValue;
#endif // SMTG_AUWRAPPER_USES_AUSDK
}
struct AUWrapperLookup
{
static AudioComponentMethod Lookup (SInt16 selector)
{
// ProcessBufferLists not supported, so don't try any of the process methods
if (selector == kAudioUnitProcessSelect || selector == kAudioUnitProcessMultipleSelect)
return NULL;
if (selector == kAudioUnitSetPropertySelect)
return (AudioComponentMethod)AUWrapperMethodSetProperty;
AudioComponentMethod method = AUBaseProcessLookup::Lookup (selector);
if (method)
return method;
method = AUMusicLookup::Lookup (selector);
if (method)
return method;
method = AUBaseProcessMultipleLookup::Lookup (selector);
if (method)
return method;
return NULL;
}
};
template <class Implementor>
class AUFactory : public APFactory<AUWrapperLookup, Implementor>
{
};
//------------------------------------------------------------------------
// new entry method
extern "C" {
void* AUWrapperFactory (const AudioComponentDescription* inDesc);
__attribute__ ((visibility ("default"))) void* AUWrapperFactory (
const AudioComponentDescription* inDesc)
{
return AUFactory<AUWrapper>::Factory (inDesc);
}
} // extern "C"
#if !CA_USE_AUDIO_PLUGIN_ONLY && !defined(SMTG_AUWRAPPER_USES_AUSDK)
//------------------------------------------------------------------------
// old entry method
extern "C" {
ComponentResult AUWrapperEntry (ComponentParameters* params, AUWrapper* obj);
__attribute__ ((visibility ("default"))) ComponentResult AUWrapperEntry (
ComponentParameters* params, AUWrapper* obj)
{
return ComponentEntryPoint<AUWrapper>::Dispatch (params, obj);
}
}
#endif
//------------------------------------------------------------------------
} // namespace Vst
} // namespace Steinberg
/// \endcond