//------------------------------------------------------------------------ // 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 #if !SMTG_PLATFORM_64 #include #endif #include #include #include #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 // 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 ; MIDIPacketList* PacketList () { return (MIDIPacketList*)mBuffersAllocated.data (); } std::array 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 (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 (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 (audioProcessor)->initialize ((HostApplication*)&gHostApp) != kResultTrue) return; auto component = U::cast (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 (editController); auto processorConnectionPoint = U::cast (audioProcessor); if (controllerConnectionPoint && processorConnectionPoint) { controllerConnectionPoint->connect (processorConnectionPoint); processorConnectionPoint->connect (controllerConnectionPoint); } NSMutableData* processorData = [[[NSMutableData alloc] init] autorelease]; NSMutableDataIBStream stream (processorData); if (FUnknownPtr (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 (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 (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 (); transferParamChanges.setMaxParameters (500); outputParamTransfer.setMaxParameters (500); timer = Timer::create (this, 20); auto editController2 = U::cast (editController); if (editController2) editController2->setKnobMode (kLinearMode); midiLearn = U::cast (editController); if (midiLearn) midiLearnRingBuffer.resize (8); } } } //------------------------------------------------------------------------ AUWrapper::~AUWrapper () { AutoreleasePool ap; if (timer) timer->release (); if (paramListenerRef) AUListenerDispose (paramListenerRef); if (audioProcessor) { auto combined = U::cast (audioProcessor); if (!combined) { auto controllerConnectionPoint = U::cast (editController); auto processorConnectionPoint = U::cast (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 (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 (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 ( ¶mListenerRef, 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 (audioProcessor); component->setActive (false); } } //------------------------------------------------------------------------ void AUWrapper::updateProgramChangesCache () { // assign programChanges auto pci = std::make_unique (); for (size_t midiChannel = 0u; midiChannel < pci->size (); ++midiChannel) { auto& programChangeInfo = pci->at (midiChannel); programChangeInfo = {}; UnitID unitId; ProgramListID programListId; if (getProgramListAndUnit (static_cast (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 (editController); if (!midiMapping) return; auto comp = U::cast (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 (std::move (cache))); } } //------------------------------------------------------------------------ // MARK: AUBase //-------------------------------------------------------------------------------------------- #ifdef SMTG_AUWRAPPER_USES_AUSDK std::unique_ptr AUWrapper::CreateElement (AudioUnitScope scope, AudioUnitElement element) { switch (scope) { //--- ----------------------- case kAudioUnitScope_Output: return std::make_unique(this); //--- ----------------------- case kAudioUnitScope_Input: return std::make_unique(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 AudioUnit SupportedNumChannels that contains the supported AUChannelInfo informations like: Outputs 2 Inputs 2 Outputs 0 Inputs 1 Outputs 1 Inputs 1 */ 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 (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 (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 (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 (inData); if (CFDictionaryContainsKey ( dict, [NSString stringWithCString:kAUPresetPartKey encoding:NSASCIIStringEncoding])) return kAudioUnitErr_InvalidPropertyValue; #define kCurrentSavedStateVersion 0 CFNumberRef cfnum = reinterpret_cast (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 (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 ( 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 (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 (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 (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(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 (data1) / static_cast (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 (velocity) / static_cast (std::numeric_limits::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 (velocity) / static_cast (std::numeric_limits::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 (std::numeric_limits::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 (program) / static_cast (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 (std::numeric_limits::max ())); } } void onPitchBend (Group group, Channel channel, Data32 data) const override { if (!w.midiMappingCache.empty ()) { addControllerChange (group, channel, ControllerNumbers::kPitchBend, data / static_cast (std::numeric_limits::max ())); } } void onChannelPressure (Group group, Channel channel, Data32 data) const override { if (!w.midiMappingCache.empty ()) { addControllerChange (group, channel, ControllerNumbers::kAfterTouch, data / static_cast (std::numeric_limits::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 (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 (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, ¤tMeasureDownBeat) == 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, ¤tSampleInTimeLine, &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 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*)¶ms->params[_index + 1]; #else // parameters in reverse order, then comp instance #define PARAM(_typ, _name, _index, _nparams) \ _typ _name = *(_typ*)¶ms->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 (self); auto auwrapper = reinterpret_cast (&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 AUFactory : public APFactory { }; //------------------------------------------------------------------------ // new entry method extern "C" { void* AUWrapperFactory (const AudioComponentDescription* inDesc); __attribute__ ((visibility ("default"))) void* AUWrapperFactory ( const AudioComponentDescription* inDesc) { return AUFactory::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::Dispatch (params, obj); } } #endif //------------------------------------------------------------------------ } // namespace Vst } // namespace Steinberg /// \endcond