/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2017 - ROLI Ltd. JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 5 End-User License Agreement and JUCE 5 Privacy Policy (both updated and effective as of the 27th April 2017). End User License Agreement: www.juce.com/juce-5-licence Privacy Policy: www.juce.com/juce-5-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ #include "../../juce_core/system/juce_TargetPlatform.h" #include "../utility/juce_CheckSettingMacros.h" #if JucePlugin_Build_AUv3 #import #import #import #if JUCE_MAC #if (! defined MAC_OS_X_VERSION_MIN_REQUIRED) || (! defined MAC_OS_X_VERSION_10_11) || (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_11) #error AUv3 needs Deployment Target OS X 10.11 or higher to compile #endif #if (defined MAC_OS_X_VERSION_10_13) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_13) #define JUCE_AUV3_MIDI_OUTPUT_SUPPORTED 1 #define JUCE_AUV3_VIEW_CONFIG_SUPPORTED 1 #endif #endif #if JUCE_IOS #if (! defined __IPHONE_OS_VERSION_MIN_REQUIRED) || (! defined __IPHONE_9_0) || (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0) #error AUv3 needs Deployment Target iOS 9.0 or higher to compile #endif #if (defined __IPHONE_11_0) && (__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_11_0) #define JUCE_AUV3_MIDI_OUTPUT_SUPPORTED 1 #define JUCE_AUV3_VIEW_CONFIG_SUPPORTED 1 #endif #endif #ifndef __OBJC2__ #error AUv3 needs Objective-C 2 support (compile with 64-bit) #endif #define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 #include "../utility/juce_IncludeSystemHeaders.h" #include "../utility/juce_IncludeModuleHeaders.h" #include "../../juce_graphics/native/juce_mac_CoreGraphicsHelpers.h" #include "../../juce_audio_basics/native/juce_mac_CoreAudioLayouts.h" #include "../../juce_audio_processors/format_types/juce_LegacyAudioParameter.cpp" #include "../../juce_audio_processors/format_types/juce_AU_Shared.h" #define JUCE_VIEWCONTROLLER_OBJC_NAME(x) JUCE_JOIN_MACRO (x, FactoryAUv3) #if JUCE_IOS #define JUCE_IOS_MAC_VIEW UIView #else #define JUCE_IOS_MAC_VIEW NSView #endif #define JUCE_AUDIOUNIT_OBJC_NAME(x) JUCE_JOIN_MACRO (x, AUv3) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnullability-completeness" using namespace juce; struct AudioProcessorHolder : public ReferenceCountedObject { AudioProcessorHolder() {} AudioProcessorHolder (AudioProcessor* p) : processor (p) {} AudioProcessor& operator*() noexcept { return *processor; } AudioProcessor* operator->() noexcept { return processor.get(); } AudioProcessor* get() noexcept { return processor.get(); } struct ViewConfig { double width; double height; bool hostHasMIDIController; }; std::unique_ptr viewConfiguration; using Ptr = ReferenceCountedObjectPtr; private: std::unique_ptr processor; AudioProcessorHolder& operator= (AudioProcessor*) = delete; AudioProcessorHolder (AudioProcessorHolder&) = delete; AudioProcessorHolder& operator= (AudioProcessorHolder&) = delete; }; //============================================================================== class JuceAudioUnitv3Base { public: JuceAudioUnitv3Base (const AudioComponentDescription& descr, AudioComponentInstantiationOptions options, NSError** error) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-method-access" : au ([audioUnitObjCClass.createInstance() initWithComponentDescription: descr options: options error: error juceClass: this]) #pragma clang diagnostic pop {} JuceAudioUnitv3Base (AUAudioUnit* audioUnit) : au (audioUnit) { jassert (MessageManager::getInstance()->isThisTheMessageThread()); initialiseJuce_GUI(); } virtual ~JuceAudioUnitv3Base() {} //============================================================================== AUAudioUnit* getAudioUnit() noexcept { return au; } //============================================================================== virtual void reset() = 0; //============================================================================== virtual AUAudioUnitPreset* getCurrentPreset() = 0; virtual void setCurrentPreset(AUAudioUnitPreset*) = 0; virtual NSArray* getFactoryPresets() = 0; virtual NSDictionary* getFullState() { objc_super s = { getAudioUnit(), [AUAudioUnit class] }; return ObjCMsgSendSuper*> (&s, @selector (fullState)); } virtual void setFullState (NSDictionary* state) { objc_super s = { getAudioUnit(), [AUAudioUnit class] }; ObjCMsgSendSuper*> (&s, @selector (setFullState:), state); } virtual AUParameterTree* getParameterTree() = 0; virtual NSArray* parametersForOverviewWithCount (int) = 0; //============================================================================== virtual NSTimeInterval getLatency() = 0; virtual NSTimeInterval getTailTime() = 0; //============================================================================== virtual AUAudioUnitBusArray* getInputBusses() = 0; virtual AUAudioUnitBusArray* getOutputBusses() = 0; virtual NSArray* getChannelCapabilities() = 0; virtual bool shouldChangeToFormat (AVAudioFormat*, AUAudioUnitBus*) = 0; //============================================================================== virtual int getVirtualMIDICableCount() = 0; virtual bool getSupportsMPE() = 0; virtual NSArray* getMIDIOutputNames() = 0; //============================================================================== virtual AUInternalRenderBlock getInternalRenderBlock() = 0; virtual bool getCanProcessInPlace() { return false; } virtual bool getRenderingOffline() = 0; virtual void setRenderingOffline (bool offline) = 0; virtual bool getShouldBypassEffect() { objc_super s = { getAudioUnit(), [AUAudioUnit class] }; return (ObjCMsgSendSuper (&s, @selector (shouldBypassEffect)) == YES); } virtual void setShouldBypassEffect (bool shouldBypass) { objc_super s = { getAudioUnit(), [AUAudioUnit class] }; ObjCMsgSendSuper (&s, @selector (setShouldBypassEffect:), shouldBypass ? YES : NO); } //============================================================================== virtual NSString* getContextName() const = 0; virtual void setContextName (NSString*) = 0; virtual bool allocateRenderResourcesAndReturnError (NSError **outError) { objc_super s = { getAudioUnit(), [AUAudioUnit class] }; return (ObjCMsgSendSuper (&s, @selector (allocateRenderResourcesAndReturnError:), outError) == YES); } virtual void deallocateRenderResources() { objc_super s = { getAudioUnit(), [AUAudioUnit class] }; ObjCMsgSendSuper (&s, @selector (deallocateRenderResources)); } //============================================================================== #if JUCE_AUV3_VIEW_CONFIG_SUPPORTED virtual NSIndexSet* getSupportedViewConfigurations (NSArray*) = 0; virtual void selectViewConfiguration (AUAudioUnitViewConfiguration*) = 0; #endif private: struct Class : public ObjCClass { Class() : ObjCClass ("AUAudioUnit_") { addIvar ("cppObject"); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wundeclared-selector" addMethod (@selector (initWithComponentDescription:options:error:juceClass:), initWithComponentDescriptionAndJuceClass, "@@:", @encode (AudioComponentDescription), @encode (AudioComponentInstantiationOptions), "^@@"); #pragma clang diagnostic pop addMethod (@selector (initWithComponentDescription:options:error:), initWithComponentDescription, "@@:", @encode (AudioComponentDescription), @encode (AudioComponentInstantiationOptions), "^@"); addMethod (@selector (dealloc), dealloc, "v@:"); //============================================================================== addMethod (@selector (reset), reset, "v@:"); //============================================================================== addMethod (@selector (currentPreset), getCurrentPreset, "@@:"); addMethod (@selector (setCurrentPreset:), setCurrentPreset, "v@:@"); addMethod (@selector (factoryPresets), getFactoryPresets, "@@:"); addMethod (@selector (fullState), getFullState, "@@:"); addMethod (@selector (setFullState:), setFullState, "v@:@"); addMethod (@selector (parameterTree), getParameterTree, "@@:"); addMethod (@selector (parametersForOverviewWithCount:), parametersForOverviewWithCount, "@@:", @encode (NSInteger)); //============================================================================== addMethod (@selector (latency), getLatency, @encode (NSTimeInterval), "@:"); addMethod (@selector (tailTime), getTailTime, @encode (NSTimeInterval), "@:"); //============================================================================== addMethod (@selector (inputBusses), getInputBusses, "@@:"); addMethod (@selector (outputBusses), getOutputBusses, "@@:"); addMethod (@selector (channelCapabilities), getChannelCapabilities, "@@:"); addMethod (@selector (shouldChangeToFormat:forBus:), shouldChangeToFormat, "B@:@@"); //============================================================================== addMethod (@selector (virtualMIDICableCount), getVirtualMIDICableCount, @encode (NSInteger), "@:"); addMethod (@selector (supportsMPE), getSupportsMPE, @encode (BOOL), "@:"); #if JUCE_AUV3_MIDI_OUTPUT_SUPPORTED addMethod (@selector (MIDIOutputNames), getMIDIOutputNames, "@@:"); #endif //============================================================================== addMethod (@selector (internalRenderBlock), getInternalRenderBlock, @encode (AUInternalRenderBlock), "@:"); addMethod (@selector (canProcessInPlace), getCanProcessInPlace, @encode (BOOL), "@:"); addMethod (@selector (isRenderingOffline), getRenderingOffline, @encode (BOOL), "@:"); addMethod (@selector (setRenderingOffline:), setRenderingOffline, "v@:", @encode (BOOL)); addMethod (@selector (shouldBypassEffect), getShouldBypassEffect, @encode (BOOL), "@:"); addMethod (@selector (setShouldBypassEffect:), setShouldBypassEffect, "v@:", @encode (BOOL)); addMethod (@selector (allocateRenderResourcesAndReturnError:), allocateRenderResourcesAndReturnError, "B@:^@"); addMethod (@selector (deallocateRenderResources), deallocateRenderResources, "v@:"); //============================================================================== addMethod (@selector (contextName), getContextName, "@@:"); addMethod (@selector (setContextName:), setContextName, "v@:@"); //============================================================================== #if JUCE_AUV3_VIEW_CONFIG_SUPPORTED addMethod (@selector (supportedViewConfigurations:), getSupportedViewConfigurations, "@@:@"); addMethod (@selector (selectViewConfiguration:), selectViewConfiguration, "v@:@"); #endif registerClass(); } //============================================================================== static JuceAudioUnitv3Base* _this (id self) { return getIvar (self, "cppObject"); } static void setThis (id self, JuceAudioUnitv3Base* cpp) { object_setInstanceVariable (self, "cppObject", cpp); } //============================================================================== static id initWithComponentDescription (id _self, SEL, AudioComponentDescription descr, AudioComponentInstantiationOptions options, NSError** error) { AUAudioUnit* self = _self; objc_super s = { self, [AUAudioUnit class] }; self = ObjCMsgSendSuper (&s, @selector(initWithComponentDescription:options:error:), descr, options, error); JuceAudioUnitv3Base* juceAU = JuceAudioUnitv3Base::create (self, descr, options, error); setThis (self, juceAU); return self; } static id initWithComponentDescriptionAndJuceClass (id _self, SEL, AudioComponentDescription descr, AudioComponentInstantiationOptions options, NSError** error, JuceAudioUnitv3Base* juceAU) { AUAudioUnit* self = _self; objc_super s = { self, [AUAudioUnit class] }; self = ObjCMsgSendSuper (&s, @selector(initWithComponentDescription:options:error:), descr, options, error); setThis (self, juceAU); return self; } static void dealloc (id self, SEL) { if (! MessageManager::getInstance()->isThisTheMessageThread()) { WaitableEvent deletionEvent; struct AUDeleter : public CallbackMessage { AUDeleter (id selfToDelete, WaitableEvent& event) : parentSelf (selfToDelete), parentDeletionEvent (event) { } void messageCallback() override { delete _this (parentSelf); parentDeletionEvent.signal(); } id parentSelf; WaitableEvent& parentDeletionEvent; }; (new AUDeleter (self, deletionEvent))->post(); deletionEvent.wait (-1); } else { delete _this (self); } } //============================================================================== static void reset (id self, SEL) { _this (self)->reset(); } //============================================================================== static AUAudioUnitPreset* getCurrentPreset (id self, SEL) { return _this (self)->getCurrentPreset(); } static void setCurrentPreset (id self, SEL, AUAudioUnitPreset* preset) { return _this (self)->setCurrentPreset (preset); } static NSArray* getFactoryPresets (id self, SEL) { return _this (self)->getFactoryPresets(); } static NSDictionary* getFullState (id self, SEL) { return _this (self)->getFullState(); } static void setFullState (id self, SEL, NSDictionary* state) { return _this (self)->setFullState (state); } static AUParameterTree* getParameterTree (id self, SEL) { return _this (self)->getParameterTree(); } static NSArray* parametersForOverviewWithCount (id self, SEL, NSInteger count) { return _this (self)->parametersForOverviewWithCount (static_cast (count)); } //============================================================================== static NSTimeInterval getLatency (id self, SEL) { return _this (self)->getLatency(); } static NSTimeInterval getTailTime (id self, SEL) { return _this (self)->getTailTime(); } //============================================================================== static AUAudioUnitBusArray* getInputBusses (id self, SEL) { return _this (self)->getInputBusses(); } static AUAudioUnitBusArray* getOutputBusses (id self, SEL) { return _this (self)->getOutputBusses(); } static NSArray* getChannelCapabilities (id self, SEL) { return _this (self)->getChannelCapabilities(); } static BOOL shouldChangeToFormat (id self, SEL, AVAudioFormat* format, AUAudioUnitBus* bus) { return _this (self)->shouldChangeToFormat (format, bus) ? YES : NO; } //============================================================================== static NSInteger getVirtualMIDICableCount (id self, SEL) { return _this (self)->getVirtualMIDICableCount(); } static BOOL getSupportsMPE (id self, SEL) { return _this (self)->getSupportsMPE() ? YES : NO; } static NSArray* getMIDIOutputNames (id self, SEL) { return _this (self)->getMIDIOutputNames(); } //============================================================================== static AUInternalRenderBlock getInternalRenderBlock (id self, SEL) { return _this (self)->getInternalRenderBlock(); } static BOOL getCanProcessInPlace (id self, SEL) { return _this (self)->getCanProcessInPlace() ? YES : NO; } static BOOL getRenderingOffline (id self, SEL) { return _this (self)->getRenderingOffline() ? YES : NO; } static void setRenderingOffline (id self, SEL, BOOL renderingOffline) { _this (self)->setRenderingOffline (renderingOffline); } static BOOL allocateRenderResourcesAndReturnError (id self, SEL, NSError** error) { return _this (self)->allocateRenderResourcesAndReturnError (error) ? YES : NO; } static void deallocateRenderResources (id self, SEL) { _this (self)->deallocateRenderResources(); } static BOOL getShouldBypassEffect (id self, SEL) { return _this (self)->getShouldBypassEffect() ? YES : NO; } static void setShouldBypassEffect (id self, SEL, BOOL shouldBypass) { _this (self)->setShouldBypassEffect (shouldBypass); } //============================================================================== static NSString* getContextName (id self, SEL) { return _this (self)->getContextName(); } static void setContextName (id self, SEL, NSString* str) { return _this (self)->setContextName (str); } //============================================================================== #if JUCE_AUV3_VIEW_CONFIG_SUPPORTED static NSIndexSet* getSupportedViewConfigurations (id self, SEL, NSArray* configs) { return _this (self)->getSupportedViewConfigurations (configs); } static void selectViewConfiguration (id self, SEL, AUAudioUnitViewConfiguration* config) { _this (self)->selectViewConfiguration (config); } #endif }; static JuceAudioUnitv3Base* create (AUAudioUnit*, AudioComponentDescription, AudioComponentInstantiationOptions, NSError**); //============================================================================== static Class audioUnitObjCClass; protected: AUAudioUnit* au; }; //============================================================================== JuceAudioUnitv3Base::Class JuceAudioUnitv3Base::audioUnitObjCClass; //============================================================================== //=========================== The actual AudioUnit ============================= //============================================================================== class JuceAudioUnitv3 : public JuceAudioUnitv3Base, public AudioProcessorListener, public AudioPlayHead, private AudioProcessorParameter::Listener { public: JuceAudioUnitv3 (const AudioProcessorHolder::Ptr& processor, const AudioComponentDescription& descr, AudioComponentInstantiationOptions options, NSError** error) : JuceAudioUnitv3Base (descr, options, error), processorHolder (processor), mapper (*processorHolder->get()) { init(); } JuceAudioUnitv3 (AUAudioUnit* audioUnit, AudioComponentDescription, AudioComponentInstantiationOptions, NSError**) : JuceAudioUnitv3Base (audioUnit), processorHolder (new AudioProcessorHolder (createPluginFilterOfType (AudioProcessor::wrapperType_AudioUnitv3))), mapper (*processorHolder->get()) { init(); } ~JuceAudioUnitv3() { auto& processor = getAudioProcessor(); processor.removeListener (this); if (bypassParam != nullptr) bypassParam->removeListener (this); removeEditor (processor); if (editorObserverToken != nullptr) { [paramTree.get() removeParameterObserver: editorObserverToken]; editorObserverToken = nullptr; } } //============================================================================== void init() { inParameterChangedCallback = false; AudioProcessor& processor = getAudioProcessor(); const AUAudioFrameCount maxFrames = [getAudioUnit() maximumFramesToRender]; #ifdef JucePlugin_PreferredChannelConfigurations short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; const int numConfigs = sizeof (configs) / sizeof (short[2]); jassert (numConfigs > 0 && (configs[0][0] > 0 || configs[0][1] > 0)); processor.setPlayConfigDetails (configs[0][0], configs[0][1], kDefaultSampleRate, static_cast (maxFrames)); Array channelInfos; for (int i = 0; i < numConfigs; ++i) { AUChannelInfo channelInfo; channelInfo.inChannels = configs[i][0]; channelInfo.outChannels = configs[i][1]; channelInfos.add (channelInfo); } #else Array channelInfos = AudioUnitHelpers::getAUChannelInfo (processor); #endif processor.setPlayHead (this); totalInChannels = processor.getTotalNumInputChannels(); totalOutChannels = processor.getTotalNumOutputChannels(); { channelCapabilities.reset ([[NSMutableArray alloc] init]); for (int i = 0; i < channelInfos.size(); ++i) { AUChannelInfo& info = channelInfos.getReference (i); [channelCapabilities.get() addObject: [NSNumber numberWithInteger: info.inChannels]]; [channelCapabilities.get() addObject: [NSNumber numberWithInteger: info.outChannels]]; } } editorObserverToken = nullptr; internalRenderBlock = CreateObjCBlock (this, &JuceAudioUnitv3::renderCallback); processor.setRateAndBufferSizeDetails (kDefaultSampleRate, static_cast (maxFrames)); processor.prepareToPlay (kDefaultSampleRate, static_cast (maxFrames)); processor.addListener (this); addParameters(); addPresets(); addAudioUnitBusses (true); addAudioUnitBusses (false); } AudioProcessor& getAudioProcessor() const noexcept { return **processorHolder; } //============================================================================== void reset() override { midiMessages.clear(); lastTimeStamp.mSampleTime = std::numeric_limits::max(); } //============================================================================== AUAudioUnitPreset* getCurrentPreset() override { const int n = static_cast ([factoryPresets.get() count]); const int idx = static_cast (getAudioProcessor().getCurrentProgram()); if (idx < n) return [factoryPresets.get() objectAtIndex:static_cast (idx)]; return nullptr; } void setCurrentPreset(AUAudioUnitPreset* preset) override { const int n = static_cast ([factoryPresets.get() count]); const int idx = static_cast ([preset number]); if (isPositiveAndBelow (idx, n)) getAudioProcessor().setCurrentProgram (idx); } NSArray* getFactoryPresets() override { return factoryPresets.get(); } NSDictionary* getFullState() override { NSMutableDictionary* retval = [[NSMutableDictionary alloc] init]; { NSDictionary* superRetval = JuceAudioUnitv3Base::getFullState(); if (superRetval != nullptr) [retval addEntriesFromDictionary:superRetval]; } juce::MemoryBlock state; getAudioProcessor().getCurrentProgramStateInformation (state); if (state.getSize() > 0) { NSData* ourState = [[NSData alloc] initWithBytes: state.getData() length: state.getSize()]; NSString* nsKey = [[NSString alloc] initWithUTF8String: JUCE_STATE_DICTIONARY_KEY]; [retval setObject: ourState forKey: nsKey]; [nsKey release]; [ourState release]; } return [retval autorelease]; } void setFullState (NSDictionary* state) override { if (state == nullptr) return; NSMutableDictionary* modifiedState = [[NSMutableDictionary alloc] init]; [modifiedState addEntriesFromDictionary: state]; NSString* nsPresetKey = [[NSString alloc] initWithUTF8String: kAUPresetDataKey]; [modifiedState removeObjectForKey: nsPresetKey]; [nsPresetKey release]; JuceAudioUnitv3Base::setFullState (modifiedState); NSString* nsKey = [[NSString alloc] initWithUTF8String: JUCE_STATE_DICTIONARY_KEY]; NSObject* obj = [modifiedState objectForKey: nsKey]; [nsKey release]; if (obj != nullptr) { if ([obj isKindOfClass:[NSData class]]) { NSData* data = reinterpret_cast (obj); const int numBytes = static_cast ([data length]); const juce::uint8* const rawBytes = reinterpret_cast< const juce::uint8* const> ([data bytes]); if (numBytes > 0) getAudioProcessor().setCurrentProgramStateInformation (rawBytes, numBytes); } } [modifiedState release]; } AUParameterTree* getParameterTree() override { return paramTree.get(); } NSArray* parametersForOverviewWithCount (int count) override { const int n = static_cast ([overviewParams.get() count]); if (count >= n) return overviewParams.get(); NSMutableArray* retval = [[NSMutableArrayalloc] initWithArray: overviewParams.get()]; [retval removeObjectsInRange: NSMakeRange (static_cast (count), static_cast (n - count))]; return [retval autorelease]; } //============================================================================== NSTimeInterval getLatency() override { auto& p = getAudioProcessor(); return p.getLatencySamples() / p.getSampleRate(); } NSTimeInterval getTailTime() override { return getAudioProcessor().getTailLengthSeconds(); } //============================================================================== AUAudioUnitBusArray* getInputBusses() override { return inputBusses.get(); } AUAudioUnitBusArray* getOutputBusses() override { return outputBusses.get(); } NSArray* getChannelCapabilities() override { return channelCapabilities.get(); } bool shouldChangeToFormat (AVAudioFormat* format, AUAudioUnitBus* auBus) override { const bool isInput = ([auBus busType] == AUAudioUnitBusTypeInput); const int busIdx = static_cast ([auBus index]); const int newNumChannels = static_cast ([format channelCount]); AudioProcessor& processor = getAudioProcessor(); if (AudioProcessor::Bus* bus = processor.getBus (isInput, busIdx)) { #ifdef JucePlugin_PreferredChannelConfigurations ignoreUnused (bus); short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; if (! AudioUnitHelpers::isLayoutSupported (processor, isInput, busIdx, newNumChannels, configs)) return false; #else const AVAudioChannelLayout* layout = [format channelLayout]; const AudioChannelLayoutTag layoutTag = (layout != nullptr ? [layout layoutTag] : 0); if (layoutTag != 0) { AudioChannelSet newLayout = CoreAudioLayouts::fromCoreAudio (layoutTag); if (newLayout.size() != newNumChannels) return false; if (! bus->isLayoutSupported (newLayout)) return false; } else { if (! bus->isNumberOfChannelsSupported (newNumChannels)) return false; } #endif return true; } return false; } //============================================================================== int getVirtualMIDICableCount() override { #if JucePlugin_WantsMidiInput return 1; #else return 0; #endif } bool getSupportsMPE() override { return getAudioProcessor().supportsMPE(); } NSArray* getMIDIOutputNames() override { #if JucePlugin_ProducesMidiOutput return @[@"MIDI Out"]; #else return @[]; #endif } //============================================================================== AUInternalRenderBlock getInternalRenderBlock() override { return internalRenderBlock; } bool getRenderingOffline() override { return getAudioProcessor().isNonRealtime(); } void setRenderingOffline (bool offline) override { auto& processor = getAudioProcessor(); auto isCurrentlyNonRealtime = processor.isNonRealtime(); if (isCurrentlyNonRealtime != offline) { ScopedLock callbackLock (processor.getCallbackLock()); processor.setNonRealtime (offline); processor.prepareToPlay (processor.getSampleRate(), processor.getBlockSize()); } } bool getShouldBypassEffect() override { if (bypassParam != nullptr) return (bypassParam->getValue() != 0.0f); return JuceAudioUnitv3Base::getShouldBypassEffect(); } void setShouldBypassEffect (bool shouldBypass) override { if (bypassParam != nullptr) bypassParam->setValue (shouldBypass ? 1.0f : 0.0f); JuceAudioUnitv3Base::setShouldBypassEffect (shouldBypass); } //============================================================================== NSString* getContextName() const override { return juceStringToNS (contextName); } void setContextName (NSString* str) override { if (str != nullptr) { AudioProcessor::TrackProperties props; props.name = nsStringToJuce (str); getAudioProcessor().updateTrackProperties (props); } } //============================================================================== bool allocateRenderResourcesAndReturnError (NSError **outError) override { AudioProcessor& processor = getAudioProcessor(); const AUAudioFrameCount maxFrames = [getAudioUnit() maximumFramesToRender]; if (! JuceAudioUnitv3Base::allocateRenderResourcesAndReturnError (outError)) return false; if (outError != nullptr) *outError = nullptr; AudioProcessor::BusesLayout layouts; for (int dir = 0; dir < 2; ++dir) { const bool isInput = (dir == 0); const int n = AudioUnitHelpers::getBusCount (&processor, isInput); Array& channelSets = (isInput ? layouts.inputBuses : layouts.outputBuses); AUAudioUnitBusArray* auBuses = (isInput ? [getAudioUnit() inputBusses] : [getAudioUnit() outputBusses]); jassert ([auBuses count] == static_cast (n)); for (int busIdx = 0; busIdx < n; ++busIdx) { AudioProcessor::Bus* bus = processor.getBus (isInput, busIdx); AVAudioFormat* format = [[auBuses objectAtIndexedSubscript:static_cast (busIdx)] format]; AudioChannelSet newLayout; const AVAudioChannelLayout* layout = [format channelLayout]; const AudioChannelLayoutTag layoutTag = (layout != nullptr ? [layout layoutTag] : 0); if (layoutTag != 0) newLayout = CoreAudioLayouts::fromCoreAudio (layoutTag); else newLayout = bus->supportedLayoutWithChannels (static_cast ([format channelCount])); if (newLayout.isDisabled()) return false; channelSets.add (newLayout); } } #ifdef JucePlugin_PreferredChannelConfigurations short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; if (! AudioProcessor::containsLayout (layouts, configs)) { if (outError != nullptr) *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:kAudioUnitErr_FormatNotSupported userInfo:nullptr]; return false; } #endif if (! AudioUnitHelpers::setBusesLayout (&getAudioProcessor(), layouts)) { if (outError != nullptr) *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:kAudioUnitErr_FormatNotSupported userInfo:nullptr]; return false; } totalInChannels = processor.getTotalNumInputChannels(); totalOutChannels = processor.getTotalNumOutputChannels(); allocateBusBuffer (true); allocateBusBuffer (false); mapper.alloc(); audioBuffer.prepare (totalInChannels, totalOutChannels, static_cast (maxFrames)); double sampleRate = (jmax (AudioUnitHelpers::getBusCount (&processor, true), AudioUnitHelpers::getBusCount (&processor, false)) > 0 ? [[[([inputBusses.get() count] > 0 ? inputBusses.get() : outputBusses.get()) objectAtIndexedSubscript: 0] format] sampleRate] : 44100.0); processor.setRateAndBufferSizeDetails (sampleRate, static_cast (maxFrames)); processor.prepareToPlay (sampleRate, static_cast (maxFrames)); midiMessages.ensureSize (2048); midiMessages.clear(); zeromem (&lastAudioHead, sizeof (lastAudioHead)); hostMusicalContextCallback = [getAudioUnit() musicalContextBlock]; hostTransportStateCallback = [getAudioUnit() transportStateBlock]; reset(); return true; } void deallocateRenderResources() override { hostMusicalContextCallback = nullptr; hostTransportStateCallback = nullptr; getAudioProcessor().releaseResources(); audioBuffer.release(); inBusBuffers. clear(); outBusBuffers.clear(); mapper.release(); JuceAudioUnitv3Base::deallocateRenderResources(); } //============================================================================== #if JUCE_AUV3_VIEW_CONFIG_SUPPORTED NSIndexSet* getSupportedViewConfigurations (NSArray* configs) override { auto supportedViewIndecies = [[NSMutableIndexSet alloc] init]; auto n = [configs count]; if (auto* editor = getAudioProcessor().createEditorIfNeeded()) { // If you hit this assertion then your plug-in's editor is reporting that it doesn't support // any host MIDI controller configurations! jassert (editor->supportsHostMIDIControllerPresence (true) || editor->supportsHostMIDIControllerPresence (false)); for (auto i = 0u; i < n; ++i) { if (auto viewConfiguration = [configs objectAtIndex: i]) { if (editor->supportsHostMIDIControllerPresence ([viewConfiguration hostHasController] == YES)) { auto* constrainer = editor->getConstrainer(); auto height = (int) [viewConfiguration height]; auto width = (int) [viewConfiguration width]; if (height <= constrainer->getMaximumHeight() && height >= constrainer->getMinimumHeight() && width <= constrainer->getMaximumWidth() && width >= constrainer->getMinimumWidth()) [supportedViewIndecies addIndex: i]; } } } } return [supportedViewIndecies autorelease]; } void selectViewConfiguration (AUAudioUnitViewConfiguration* config) override { processorHolder->viewConfiguration.reset (new AudioProcessorHolder::ViewConfig { [config width], [config height], [config hostHasController] == YES }); } #endif //============================================================================== void audioProcessorChanged (AudioProcessor* processor) override { ignoreUnused (processor); [au willChangeValueForKey: @"allParameterValues"]; addPresets(); [au didChangeValueForKey: @"allParameterValues"]; } void audioProcessorParameterChanged (AudioProcessor*, int idx, float newValue) override { if (inParameterChangedCallback.get()) { inParameterChangedCallback = false; return; } if (auto* juceParam = juceParameters.getParamForIndex (idx)) { if (AUParameter* param = [paramTree.get() parameterWithAddress: getAUParameterAddressForIndex (idx)]) { newValue *= getMaximumParameterValue (juceParam); if (editorObserverToken != nullptr) [param setValue: newValue originator: editorObserverToken]; else [param setValue: newValue]; } } } //============================================================================== bool getCurrentPosition (CurrentPositionInfo& info) override { bool musicContextCallSucceeded = false; bool transportStateCallSucceeded = false; info = lastAudioHead; info.timeInSamples = (int64) (lastTimeStamp.mSampleTime + 0.5); info.timeInSeconds = info.timeInSamples / getAudioProcessor().getSampleRate(); switch (lastTimeStamp.mSMPTETime.mType) { case kSMPTETimeType2398: info.frameRate = AudioPlayHead::fps23976; break; case kSMPTETimeType24: info.frameRate = AudioPlayHead::fps24; break; case kSMPTETimeType25: info.frameRate = AudioPlayHead::fps25; break; case kSMPTETimeType2997: info.frameRate = AudioPlayHead::fps2997; break; case kSMPTETimeType2997Drop: info.frameRate = AudioPlayHead::fps2997drop; break; case kSMPTETimeType30Drop: info.frameRate = AudioPlayHead::fps30drop; break; case kSMPTETimeType30: info.frameRate = AudioPlayHead::fps30; break; case kSMPTETimeType60Drop: info.frameRate = AudioPlayHead::fps60drop; break; case kSMPTETimeType60: info.frameRate = AudioPlayHead::fps60; break; default: info.frameRate = AudioPlayHead::fpsUnknown; break; } double num; NSInteger den; NSInteger outDeltaSampleOffsetToNextBeat; double outCurrentMeasureDownBeat, bpm; double ppqPosition; if (hostMusicalContextCallback != nullptr) { AUHostMusicalContextBlock musicalContextCallback = hostMusicalContextCallback; if (musicalContextCallback (&bpm, &num, &den, &ppqPosition, &outDeltaSampleOffsetToNextBeat, &outCurrentMeasureDownBeat)) { musicContextCallSucceeded = true; info.timeSigNumerator = (int) num; info.timeSigDenominator = (int) den; info.ppqPositionOfLastBarStart = outCurrentMeasureDownBeat; info.bpm = bpm; info.ppqPosition = ppqPosition; info.ppqPositionOfLastBarStart = outCurrentMeasureDownBeat; } } double outCurrentSampleInTimeLine, outCycleStartBeat = 0, outCycleEndBeat = 0; AUHostTransportStateFlags flags; if (hostTransportStateCallback != nullptr) { AUHostTransportStateBlock transportStateCallback = hostTransportStateCallback; if (transportStateCallback (&flags, &outCurrentSampleInTimeLine, &outCycleStartBeat, &outCycleEndBeat)) { transportStateCallSucceeded = true; info.timeInSamples = (int64) (outCurrentSampleInTimeLine + 0.5); info.timeInSeconds = info.timeInSamples / getAudioProcessor().getSampleRate(); info.isPlaying = ((flags & AUHostTransportStateMoving) != 0); info.isLooping = ((flags & AUHostTransportStateCycling) != 0); info.isRecording = ((flags & AUHostTransportStateRecording) != 0); info.ppqLoopStart = outCycleStartBeat; info.ppqLoopEnd = outCycleEndBeat; } } if (musicContextCallSucceeded && transportStateCallSucceeded) lastAudioHead = info; return true; } //============================================================================== static void removeEditor (AudioProcessor& processor) { ScopedLock editorLock (processor.getCallbackLock()); if (AudioProcessorEditor* editor = processor.getActiveEditor()) { processor.editorBeingDeleted (editor); delete editor; } } private: //============================================================================== struct BusBuffer { BusBuffer (AUAudioUnitBus* bus, int maxFramesPerBuffer) : auBus (bus), maxFrames (maxFramesPerBuffer), numberOfChannels (static_cast ([[auBus format] channelCount])), isInterleaved ([[auBus format] isInterleaved]) { alloc(); } //============================================================================== void alloc() { const int numBuffers = isInterleaved ? 1 : numberOfChannels; int bytes = static_cast (sizeof (AudioBufferList)) + ((numBuffers - 1) * static_cast (sizeof (::AudioBuffer))); jassert (bytes > 0); bufferListStorage.calloc (static_cast (bytes)); bufferList = reinterpret_cast (bufferListStorage.getData()); const int bufferChannels = isInterleaved ? numberOfChannels : 1; scratchBuffer.setSize (numBuffers, bufferChannels * maxFrames); } void dealloc() { bufferList = nullptr; bufferListStorage.free(); scratchBuffer.setSize (0, 0); } //============================================================================== int numChannels() const noexcept { return numberOfChannels; } bool interleaved() const noexcept { return isInterleaved; } AudioBufferList* get() const noexcept { return bufferList; } //============================================================================== void prepare (UInt32 nFrames, const AudioBufferList* other = nullptr) noexcept { const int numBuffers = isInterleaved ? 1 : numberOfChannels; const bool isCompatible = isCompatibleWith (other); bufferList->mNumberBuffers = static_cast (numBuffers); for (int i = 0; i < numBuffers; ++i) { const UInt32 bufferChannels = static_cast (isInterleaved ? numberOfChannels : 1); bufferList->mBuffers[i].mNumberChannels = bufferChannels; bufferList->mBuffers[i].mData = (isCompatible ? other->mBuffers[i].mData : scratchBuffer.getWritePointer (i)); bufferList->mBuffers[i].mDataByteSize = nFrames * bufferChannels * sizeof (float); } } //============================================================================== bool isCompatibleWith (const AudioBufferList* other) const noexcept { if (other == nullptr) return false; if (other->mNumberBuffers > 0) { const bool otherInterleaved = AudioUnitHelpers::isAudioBufferInterleaved (*other); const int otherChannels = static_cast (otherInterleaved ? other->mBuffers[0].mNumberChannels : other->mNumberBuffers); return otherInterleaved == isInterleaved && numberOfChannels == otherChannels; } return numberOfChannels == 0; } private: AUAudioUnitBus* auBus; HeapBlock bufferListStorage; AudioBufferList* bufferList = nullptr; int maxFrames, numberOfChannels; bool isInterleaved; AudioBuffer scratchBuffer; }; //============================================================================== void addAudioUnitBusses (bool isInput) { std::unique_ptr, NSObjectDeleter> array ([[NSMutableArray alloc] init]); AudioProcessor& processor = getAudioProcessor(); const int n = AudioUnitHelpers::getBusCount (&processor, isInput); for (int i = 0; i < n; ++i) { std::unique_ptr audioUnitBus; { std::unique_ptr defaultFormat ([[AVAudioFormat alloc] initStandardFormatWithSampleRate: kDefaultSampleRate channels: static_cast (processor.getChannelCountOfBus (isInput, i))]); audioUnitBus.reset ([[AUAudioUnitBus alloc] initWithFormat: defaultFormat.get() error: nullptr]); } [array.get() addObject: audioUnitBus.get()]; } (isInput ? inputBusses : outputBusses).reset ([[AUAudioUnitBusArray alloc] initWithAudioUnit: au busType: (isInput ? AUAudioUnitBusTypeInput : AUAudioUnitBusTypeOutput) busses: array.get()]); } // When parameters are discrete we need to use integer values. float getMaximumParameterValue (AudioProcessorParameter* juceParam) { #if JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE ignoreUnused (juceParam); return 1.0f; #else return juceParam->isDiscrete() ? (float) (juceParam->getNumSteps() - 1) : 1.0f; #endif } std::unique_ptr createParameter (AudioProcessorParameter* parameter) { const String name (parameter->getName (512)); AudioUnitParameterUnit unit = kAudioUnitParameterUnit_Generic; AudioUnitParameterOptions flags = (UInt32) (kAudioUnitParameterFlag_IsWritable | kAudioUnitParameterFlag_IsReadable | kAudioUnitParameterFlag_HasCFNameString | kAudioUnitParameterFlag_ValuesHaveStrings); if (! forceLegacyParamIDs) flags |= (UInt32) kAudioUnitParameterFlag_IsHighResolution; // Set whether the param is automatable (unnamed parameters aren't allowed to be automated). if (name.isEmpty() || ! parameter->isAutomatable()) flags |= kAudioUnitParameterFlag_NonRealTime; const bool isParameterDiscrete = parameter->isDiscrete(); if (! isParameterDiscrete) flags |= kAudioUnitParameterFlag_CanRamp; if (parameter->isMetaParameter()) flags |= kAudioUnitParameterFlag_IsGlobalMeta; std::unique_ptr valueStrings; // Is this a meter? if (((parameter->getCategory() & 0xffff0000) >> 16) == 2) { flags &= ~kAudioUnitParameterFlag_IsWritable; flags |= kAudioUnitParameterFlag_MeterReadOnly | kAudioUnitParameterFlag_DisplayLogarithmic; unit = kAudioUnitParameterUnit_LinearGain; } else { if (! forceLegacyParamIDs) { if (parameter->isDiscrete()) { unit = parameter->isBoolean() ? kAudioUnitParameterUnit_Boolean : kAudioUnitParameterUnit_Indexed; auto maxValue = getMaximumParameterValue (parameter); auto numSteps = parameter->getNumSteps(); // Some hosts can't handle the huge numbers of discrete parameter values created when // using the default number of steps. jassert (numSteps != AudioProcessor::getDefaultNumParameterSteps()); valueStrings.reset ([NSMutableArray new]); for (int i = 0; i < numSteps; ++i) [valueStrings.get() addObject: juceStringToNS (parameter->getText ((float) i / maxValue, 0))]; } } } AUParameterAddress address = generateAUParameterAddress (parameter); #if ! JUCE_FORCE_USE_LEGACY_PARAM_IDS // If you hit this assertion then you have either put a parameter in two groups or you are // very unlucky and the hash codes of your parameter ids are not unique. jassert (! paramMap.contains (static_cast (address))); paramAddresses.add (address); paramMap.set (static_cast (address), parameter->getParameterIndex()); #endif auto getParameterIdentifier = [parameter] { if (auto* paramWithID = dynamic_cast (parameter)) return paramWithID->paramID; // This could clash if any groups have been given integer IDs! return String (parameter->getParameterIndex()); }; std::unique_ptr param; @try { // Create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h param.reset([[AUParameterTree createParameterWithIdentifier: juceStringToNS (getParameterIdentifier()) name: juceStringToNS (name) address: address min: 0.0f max: getMaximumParameterValue (parameter) unit: unit unitName: nullptr flags: flags valueStrings: valueStrings.get() dependentParameters: nullptr] retain]); } @catch (NSException* exception) { // Do you have duplicate identifiers in any of your groups or parameters, // or do your identifiers have unusual characters in them? jassertfalse; } [param.get() setValue: parameter->getDefaultValue()]; [overviewParams.get() addObject: [NSNumber numberWithUnsignedLongLong: address]]; return param; } std::unique_ptr createParameterGroup (AudioProcessorParameterGroup* group) { std::unique_ptr, NSObjectDeleter> children ([NSMutableArray new]); for (auto* node : *group) { if (auto* childGroup = node->getGroup()) [children.get() addObject: createParameterGroup (childGroup).get()]; else [children.get() addObject: createParameter (node->getParameter()).get()]; } std::unique_ptr result; @try { // Create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h result.reset ([[AUParameterTree createGroupWithIdentifier: juceStringToNS (group->getID()) name: juceStringToNS (group->getName()) children: children.get()] retain]); } @catch (NSException* exception) { // Do you have duplicate identifiers in any of your groups or parameters, // or do your identifiers have unusual characters in them? jassertfalse; } return result; } void addParameters() { auto& processor = getAudioProcessor(); juceParameters.update (processor, forceLegacyParamIDs); // This is updated when we build the tree. overviewParams.reset ([NSMutableArray new]); std::unique_ptr, NSObjectDeleter> topLevelNodes ([NSMutableArray new]); for (auto* node : processor.getParameterTree()) if (auto* childGroup = node->getGroup()) [topLevelNodes.get() addObject: createParameterGroup (childGroup).get()]; else [topLevelNodes.get() addObject: createParameter (node->getParameter()).get()]; @try { // Create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h paramTree.reset ([[AUParameterTree createTreeWithChildren: topLevelNodes.get()] retain]); } @catch (NSException* exception) { // Do you have duplicate identifiers in any of your groups or parameters, // or do your identifiers have unusual characters in them? jassertfalse; } paramObserver = CreateObjCBlock (this, &JuceAudioUnitv3::valueChangedFromHost); paramProvider = CreateObjCBlock (this, &JuceAudioUnitv3::getValue); stringFromValueProvider = CreateObjCBlock (this, &JuceAudioUnitv3::stringFromValue); valueFromStringProvider = CreateObjCBlock (this, &JuceAudioUnitv3::valueFromString); [paramTree.get() setImplementorValueObserver: paramObserver]; [paramTree.get() setImplementorValueProvider: paramProvider]; [paramTree.get() setImplementorStringFromValueCallback: stringFromValueProvider]; [paramTree.get() setImplementorValueFromStringCallback: valueFromStringProvider]; if (processor.hasEditor()) { editorParamObserver = CreateObjCBlock (this, &JuceAudioUnitv3::valueChangedForObserver); editorObserverToken = [paramTree.get() tokenByAddingParameterObserver: editorParamObserver]; } if ((bypassParam = processor.getBypassParameter()) != nullptr) bypassParam->addListener (this); } void setAudioProcessorParameter (AudioProcessorParameter* juceParam, float value) { if (value != juceParam->getValue()) { juceParam->setValue (value); inParameterChangedCallback = true; juceParam->sendValueChangedMessageToListeners (value); } } void addPresets() { factoryPresets.reset ([[NSMutableArray alloc] init]); const int n = getAudioProcessor().getNumPrograms(); for (int idx = 0; idx < n; ++idx) { String name = getAudioProcessor().getProgramName (idx); std::unique_ptr preset ([[AUAudioUnitPreset alloc] init]); [preset.get() setName: juceStringToNS (name)]; [preset.get() setNumber: static_cast (idx)]; [factoryPresets.get() addObject: preset.get()]; } } //============================================================================== void allocateBusBuffer (bool isInput) { OwnedArray& busBuffers = isInput ? inBusBuffers : outBusBuffers; busBuffers.clear(); const int n = AudioUnitHelpers::getBusCount (&getAudioProcessor(), isInput); const AUAudioFrameCount maxFrames = [getAudioUnit() maximumFramesToRender]; for (int busIdx = 0; busIdx < n; ++busIdx) busBuffers.add (new BusBuffer ([(isInput ? inputBusses.get() : outputBusses.get()) objectAtIndexedSubscript: static_cast (busIdx)], static_cast (maxFrames))); } //============================================================================== void processEvents (const AURenderEvent *__nullable realtimeEventListHead, int numParams, AUEventSampleTime startTime) { ignoreUnused (numParams); for (const AURenderEvent* event = realtimeEventListHead; event != nullptr; event = event->head.next) { switch (event->head.eventType) { case AURenderEventMIDI: { const AUMIDIEvent& midiEvent = event->MIDI; midiMessages.addEvent (midiEvent.data, midiEvent.length, static_cast (midiEvent.eventSampleTime - startTime)); } break; case AURenderEventParameter: case AURenderEventParameterRamp: { const AUParameterEvent& paramEvent = event->parameter; if (auto* p = getJuceParameterForAUAddress (paramEvent.parameterAddress)) setAudioProcessorParameter (p, paramEvent.value); } break; default: break; } } } AUAudioUnitStatus renderCallback (AudioUnitRenderActionFlags* actionFlags, const AudioTimeStamp* timestamp, AUAudioFrameCount frameCount, NSInteger outputBusNumber, AudioBufferList* outputData, const AURenderEvent *__nullable realtimeEventListHead, AURenderPullInputBlock __nullable pullInputBlock) { auto& processor = getAudioProcessor(); jassert (static_cast (frameCount) <= getAudioProcessor().getBlockSize()); // process params const int numParams = juceParameters.getNumParameters(); processEvents (realtimeEventListHead, numParams, static_cast (timestamp->mSampleTime)); if (lastTimeStamp.mSampleTime != timestamp->mSampleTime) { lastTimeStamp = *timestamp; const int numInputBuses = inBusBuffers. size(); const int numOutputBuses = outBusBuffers.size(); // prepare buffers { for (int busIdx = 0; busIdx < numOutputBuses; ++busIdx) { BusBuffer& busBuffer = *outBusBuffers[busIdx]; const bool canUseDirectOutput = (busIdx == outputBusNumber && outputData != nullptr && outputData->mNumberBuffers > 0); busBuffer.prepare (frameCount, canUseDirectOutput ? outputData : nullptr); } for (int busIdx = 0; busIdx < numInputBuses; ++busIdx) { BusBuffer& busBuffer = *inBusBuffers[busIdx]; busBuffer.prepare (frameCount, busIdx < numOutputBuses ? outBusBuffers[busIdx]->get() : nullptr); } audioBuffer.reset(); } // pull inputs { for (int busIdx = 0; busIdx < numInputBuses; ++busIdx) { BusBuffer& busBuffer = *inBusBuffers[busIdx]; AudioBufferList* buffer = busBuffer.get(); if (pullInputBlock == nullptr || pullInputBlock (actionFlags, timestamp, frameCount, busIdx, buffer) != noErr) AudioUnitHelpers::clearAudioBuffer (*buffer); if (actionFlags != nullptr && (*actionFlags & kAudioUnitRenderAction_OutputIsSilence) != 0) AudioUnitHelpers::clearAudioBuffer (*buffer); } } // set buffer pointer to minimize copying { int chIdx = 0; for (int busIdx = 0; busIdx < numOutputBuses; ++busIdx) { BusBuffer& busBuffer = *outBusBuffers[busIdx]; AudioBufferList* buffer = busBuffer.get(); const bool interleaved = busBuffer.interleaved(); const int numChannels = busBuffer.numChannels(); const int* outLayoutMap = mapper.get (false, busIdx); for (int ch = 0; ch < numChannels; ++ch) audioBuffer.setBuffer (chIdx++, interleaved ? nullptr : static_cast (buffer->mBuffers[outLayoutMap[ch]].mData)); } // use input pointers on remaining channels for (int busIdx = 0; chIdx < totalInChannels;) { const int channelOffset = processor.getOffsetInBusBufferForAbsoluteChannelIndex (true, chIdx, busIdx); BusBuffer& busBuffer = *inBusBuffers[busIdx]; AudioBufferList* buffer = busBuffer.get(); const int* inLayoutMap = mapper.get (true, busIdx); audioBuffer.setBuffer (chIdx++, busBuffer.interleaved() ? nullptr : static_cast (buffer->mBuffers[inLayoutMap[channelOffset]].mData)); } } // copy input { for (int busIdx = 0; busIdx < numInputBuses; ++busIdx) audioBuffer.push (*inBusBuffers[busIdx]->get(), mapper.get (true, busIdx)); // clear remaining channels for (int i = totalInChannels; i < totalOutChannels; ++i) zeromem (audioBuffer.push(), sizeof (float) * frameCount); } // process audio processBlock (audioBuffer.getBuffer (frameCount), midiMessages); // send MIDI #if JucePlugin_ProducesMidiOutput && JUCE_AUV3_MIDI_OUTPUT_SUPPORTED auto midiOut = [au MIDIOutputEventBlock]; MidiMessage msg; int samplePosition; for (MidiBuffer::Iterator it (midiMessages); it.getNextEvent (msg, samplePosition);) midiOut (samplePosition, 0, msg.getRawDataSize(), msg.getRawData()); #endif midiMessages.clear(); // copy back audioBuffer.pop (*outBusBuffers[(int) outputBusNumber]->get(), mapper.get (false, (int) outputBusNumber)); } return noErr; } void processBlock (AudioBuffer& buffer, MidiBuffer& midiBuffer) noexcept { auto& processor = getAudioProcessor(); const ScopedLock sl (processor.getCallbackLock()); if (processor.isSuspended()) buffer.clear(); else if (bypassParam != nullptr && [au shouldBypassEffect]) processor.processBlockBypassed (buffer, midiBuffer); else processor.processBlock (buffer, midiBuffer); } //============================================================================== void valueChangedFromHost (AUParameter* param, AUValue value) { if (param != nullptr) { if (auto* p = getJuceParameterForAUAddress ([param address])) { auto normalisedValue = value / getMaximumParameterValue (p); setAudioProcessorParameter (p, normalisedValue); } } } AUValue getValue (AUParameter* param) { if (param != nullptr) { if (auto* p = getJuceParameterForAUAddress ([param address])) return p->getValue() * getMaximumParameterValue (p); } return 0; } void valueChangedForObserver (AUParameterAddress, AUValue) { // this will have already been handled by valueChangedFromHost } NSString* stringFromValue (AUParameter* param, const AUValue* value) { String text; if (param != nullptr && value != nullptr) { if (auto* p = getJuceParameterForAUAddress ([param address])) { if (LegacyAudioParameter::isLegacy (p)) text = String (*value); else text = p->getText (*value / getMaximumParameterValue (p), 0); } } return juceStringToNS (text); } AUValue valueFromString (AUParameter* param, NSString* str) { if (param != nullptr && str != nullptr) { if (auto* p = getJuceParameterForAUAddress ([param address])) { const String text (nsStringToJuce (str)); if (LegacyAudioParameter::isLegacy (p)) return text.getFloatValue(); else return p->getValueForText (text) * getMaximumParameterValue (p); } } return 0; } //============================================================================== // this is only ever called for the bypass parameter void parameterValueChanged (int, float newValue) override { JuceAudioUnitv3Base::setShouldBypassEffect (newValue != 0.0f); } void parameterGestureChanged (int, bool) override {} //============================================================================== inline AUParameterAddress getAUParameterAddressForIndex (int paramIndex) const noexcept { #if JUCE_FORCE_USE_LEGACY_PARAM_IDS return static_cast (paramIndex); #else return paramAddresses.getReference (paramIndex); #endif } inline int getJuceParameterIndexForAUAddress (AUParameterAddress address) const noexcept { #if JUCE_FORCE_USE_LEGACY_PARAM_IDS return static_cast (address); #else return paramMap[static_cast (address)]; #endif } AUParameterAddress generateAUParameterAddress (AudioProcessorParameter* param) const { const String& juceParamID = LegacyAudioParameter::getParamID (param, forceLegacyParamIDs); return static_cast (forceLegacyParamIDs ? juceParamID.getIntValue() : juceParamID.hashCode64()); } AudioProcessorParameter* getJuceParameterForAUAddress (AUParameterAddress address) const noexcept { return juceParameters.getParamForIndex (getJuceParameterIndexForAUAddress (address)); } //============================================================================== static const double kDefaultSampleRate; AudioProcessorHolder::Ptr processorHolder; int totalInChannels, totalOutChannels; std::unique_ptr inputBusses, outputBusses; ObjCBlock paramObserver; ObjCBlock paramProvider; ObjCBlock stringFromValueProvider; ObjCBlock valueFromStringProvider; #if ! JUCE_FORCE_USE_LEGACY_PARAM_IDS Array paramAddresses; HashMap paramMap; #endif LegacyAudioParametersWrapper juceParameters; // to avoid recursion on parameter changes, we need to add an // editor observer to do the parameter changes ObjCBlock editorParamObserver; AUParameterObserverToken editorObserverToken; std::unique_ptr paramTree; std::unique_ptr, NSObjectDeleter> overviewParams, channelCapabilities; std::unique_ptr, NSObjectDeleter> factoryPresets; ObjCBlock internalRenderBlock; AudioUnitHelpers::CoreAudioBufferList audioBuffer; AudioUnitHelpers::ChannelRemapper mapper; OwnedArray inBusBuffers, outBusBuffers; MidiBuffer midiMessages; ObjCBlock hostMusicalContextCallback; ObjCBlock hostTransportStateCallback; AudioTimeStamp lastTimeStamp; CurrentPositionInfo lastAudioHead; String contextName; ThreadLocalValue inParameterChangedCallback; #if JUCE_FORCE_USE_LEGACY_PARAM_IDS static constexpr bool forceLegacyParamIDs = true; #else static constexpr bool forceLegacyParamIDs = false; #endif AudioProcessorParameter* bypassParam = nullptr; }; const double JuceAudioUnitv3::kDefaultSampleRate = 44100.0; JuceAudioUnitv3Base* JuceAudioUnitv3Base::create (AUAudioUnit* audioUnit, AudioComponentDescription descr, AudioComponentInstantiationOptions options, NSError** error) { PluginHostType::jucePlugInClientCurrentWrapperType = AudioProcessor::wrapperType_AudioUnitv3; return new JuceAudioUnitv3 (audioUnit, descr, options, error); } #if JUCE_IOS namespace juce { struct UIViewPeerControllerReceiver { virtual ~UIViewPeerControllerReceiver(); virtual void setViewController (UIViewController*) = 0; }; } #endif //============================================================================== class JuceAUViewController { public: JuceAUViewController (AUViewController* p) : myself (p) { PluginHostType::jucePlugInClientCurrentWrapperType = AudioProcessor::wrapperType_AudioUnitv3; initialiseJuce_GUI(); } ~JuceAUViewController() { JUCE_ASSERT_MESSAGE_THREAD if (processorHolder != nullptr) JuceAudioUnitv3::removeEditor (getAudioProcessor()); } //============================================================================== void loadView() { JUCE_ASSERT_MESSAGE_THREAD if (AudioProcessor* p = createPluginFilterOfType (AudioProcessor::wrapperType_AudioUnitv3)) { processorHolder = new AudioProcessorHolder (p); auto& processor = getAudioProcessor(); if (processor.hasEditor()) { if (AudioProcessorEditor* editor = processor.createEditorIfNeeded()) { preferredSize = editor->getBounds(); JUCE_IOS_MAC_VIEW* view = [[[JUCE_IOS_MAC_VIEW alloc] initWithFrame: convertToCGRect (editor->getBounds())] autorelease]; [myself setView: view]; #if JUCE_IOS editor->setVisible (false); #else editor->setVisible (true); #endif editor->addToDesktop (0, view); #if JUCE_IOS if (JUCE_IOS_MAC_VIEW* peerView = [[[myself view] subviews] objectAtIndex: 0]) [peerView setContentMode: UIViewContentModeTop]; if (auto* peer = dynamic_cast (editor->getPeer())) peer->setViewController (myself); #endif } } } } void viewDidLayoutSubviews() { if (processorHolder != nullptr && [myself view] != nullptr) { if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) { if (processorHolder->viewConfiguration != nullptr) editor->hostMIDIControllerIsAvailable (processorHolder->viewConfiguration->hostHasMIDIController); editor->setBounds (convertToRectInt ([[myself view] bounds])); if (JUCE_IOS_MAC_VIEW* peerView = [[[myself view] subviews] objectAtIndex: 0]) { #if JUCE_IOS [peerView setNeedsDisplay]; #else [peerView setNeedsDisplay: YES]; #endif } } } } void didReceiveMemoryWarning() { if (processorHolder != nullptr) if (auto* processor = processorHolder->get()) processor->memoryWarningReceived(); } void viewDidAppear (bool) { if (processorHolder != nullptr) if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) editor->setVisible (true); } void viewDidDisappear (bool) { if (processorHolder != nullptr) if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) editor->setVisible (false); } CGSize getPreferredContentSize() const { return CGSizeMake (static_cast (preferredSize.getWidth()), static_cast (preferredSize.getHeight())); } //============================================================================== AUAudioUnit* createAudioUnit (const AudioComponentDescription& descr, NSError** error) { AUAudioUnit* retval = nil; if (! MessageManager::getInstance()->isThisTheMessageThread()) { WaitableEvent creationEvent; // AUv3 headers say that we may block this thread and that the message thread is guaranteed // to be unblocked struct AUCreator : public CallbackMessage { JuceAUViewController& owner; AudioComponentDescription pDescr; NSError** pError; AUAudioUnit*& outAU; WaitableEvent& e; AUCreator (JuceAUViewController& parent, const AudioComponentDescription& paramDescr, NSError** paramError, AUAudioUnit*& outputAU, WaitableEvent& event) : owner (parent), pDescr (paramDescr), pError (paramError), outAU (outputAU), e (event) {} void messageCallback() override { outAU = owner.createAudioUnitOnMessageThread (pDescr, pError); e.signal(); } }; (new AUCreator (*this, descr, error, retval, creationEvent))->post(); creationEvent.wait (-1); } else { retval = createAudioUnitOnMessageThread (descr, error); } return [retval autorelease]; } private: //============================================================================== AUViewController* myself; AudioProcessorHolder::Ptr processorHolder = nullptr; Rectangle preferredSize { 1, 1 }; //============================================================================== AUAudioUnit* createAudioUnitOnMessageThread (const AudioComponentDescription& descr, NSError** error) { JUCE_ASSERT_MESSAGE_THREAD [myself view]; // this will call [view load] and ensure that the AudioProcessor has been instantiated if (processorHolder == nullptr) return nullptr; return (new JuceAudioUnitv3 (processorHolder, descr, 0, error))->getAudioUnit(); } AudioProcessor& getAudioProcessor() const noexcept { return **processorHolder; } }; //============================================================================== // necessary glue code @interface JUCE_VIEWCONTROLLER_OBJC_NAME (JucePlugin_AUExportPrefix) : AUViewController @end @implementation JUCE_VIEWCONTROLLER_OBJC_NAME (JucePlugin_AUExportPrefix) { std::unique_ptr cpp; } - (instancetype) initWithNibName: (nullable NSString*) nib bundle: (nullable NSBundle*) bndl { self = [super initWithNibName: nib bundle: bndl]; cpp.reset (new JuceAUViewController (self)); return self; } - (void) loadView { cpp->loadView(); } - (AUAudioUnit *) createAudioUnitWithComponentDescription: (AudioComponentDescription) desc error: (NSError **) error { return cpp->createAudioUnit (desc, error); } - (CGSize) preferredContentSize { return cpp->getPreferredContentSize(); } - (void) viewDidLayoutSubviews { cpp->viewDidLayoutSubviews(); } - (void) didReceiveMemoryWarning { cpp->didReceiveMemoryWarning(); } #if JUCE_IOS - (void) viewDidAppear: (BOOL) animated { cpp->viewDidAppear (animated); [super viewDidAppear:animated]; } - (void) viewDidDisappear: (BOOL) animated { cpp->viewDidDisappear (animated); [super viewDidDisappear:animated]; } #endif @end //============================================================================== #if JUCE_IOS bool JUCE_CALLTYPE juce_isInterAppAudioConnected() { return false; } void JUCE_CALLTYPE juce_switchToHostApplication() {} #if JUCE_MODULE_AVAILABLE_juce_gui_basics Image JUCE_CALLTYPE juce_getIAAHostIcon (int) { return {}; } #endif #endif #pragma clang diagnostic pop #endif