/* ============================================================================== 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 #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 #endif #ifndef __OBJC2__ #error AUv3 needs Objective-C 2 support (compile with 64-bit) #endif #include "../utility/juce_IncludeSystemHeaders.h" #include "../utility/juce_IncludeModuleHeaders.h" #include "../../juce_core/native/juce_osx_ObjCHelpers.h" #include "../../juce_graphics/native/juce_mac_CoreGraphicsHelpers.h" #include "../../juce_audio_processors/format_types/juce_AU_Shared.h" #define JUCE_VIEWCONTROLLER_OBJC_NAME(x) JUCE_JOIN_MACRO (x, FactoryAUv3) #if ! JUCE_COMPILER_SUPPORTS_VARIADIC_TEMPLATES #error AUv3 wrapper requires variadic template support #endif #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" // TODO: ask Timur: use SFINAE to automatically generate this for all NSObjects template <> struct ContainerDeletePolicy { static void destroy (NSObject* o) { [o release]; } }; template <> struct ContainerDeletePolicy { static void destroy (NSObject* o) { [o release]; } }; template <> struct ContainerDeletePolicy > { static void destroy (NSObject* o) { [o release]; } }; template <> struct ContainerDeletePolicy { static void destroy (NSObject* o) { [o release]; } }; template <> struct ContainerDeletePolicy > { static void destroy (NSObject* o) { [o release]; } }; template <> struct ContainerDeletePolicy { static void destroy (NSObject* o) { [o release]; } }; template <> struct ContainerDeletePolicy { static void destroy (NSObject* o) { [o release]; } }; template <> struct ContainerDeletePolicy { static void destroy (NSObject* o) { [o release]; } }; template <> struct ContainerDeletePolicy > { static void destroy (NSObject* o) { [o release]; } }; template <> struct ContainerDeletePolicy { static void destroy (NSObject* o) { [o release]; } }; template <> struct ContainerDeletePolicy > { static void destroy (NSObject* o) { [o release]; } }; template <> struct ContainerDeletePolicy { static void destroy (NSObject* o) { [o release]; } }; //============================================================================== struct AudioProcessorHolder : public ReferenceCountedObject { AudioProcessorHolder() {} AudioProcessorHolder (AudioProcessor* p) : processor (p) {} AudioProcessor& operator*() noexcept { return *processor; } AudioProcessor* operator->() noexcept { return processor; } AudioProcessor* get() noexcept { return processor; } typedef ReferenceCountedObjectPtr Ptr; private: ScopedPointer processor; AudioProcessorHolder& operator= (AudioProcessor*) JUCE_DELETED_FUNCTION; AudioProcessorHolder (AudioProcessorHolder&) JUCE_DELETED_FUNCTION; AudioProcessorHolder& operator= (AudioProcessorHolder&) JUCE_DELETED_FUNCTION; }; //============================================================================== 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 int getVirtualMIDICableCount() { return 0; } virtual void reset() {} virtual bool shouldChangeToFormat (AVAudioFormat* format, AUAudioUnitBus* bus) { objc_super s = { getAudioUnit(), [AUAudioUnit class] }; return (ObjCMsgSendSuper (&s, @selector (shouldChangeToFormat:forBus:), format, bus) == YES); } virtual AUAudioUnitPreset* getCurrentPreset() { return nullptr; } virtual void setCurrentPreset(AUAudioUnitPreset*) {} virtual NSTimeInterval getLatency() { return 0.0; } virtual NSTimeInterval getTailTime() { return 0.0; } virtual bool getCanProcessInPlace() { return false; } virtual bool getRenderingOffline() { return false; } //============================================================================== virtual AUAudioUnitBusArray* getInputBusses() = 0; virtual AUAudioUnitBusArray* getOutputBusses() = 0; virtual AUParameterTree* getParameterTree() = 0; virtual AUInternalRenderBlock getInternalRenderBlock() = 0; virtual void setRenderingOffline (bool offline) = 0; virtual NSArray *getChannelCapabilities() = 0; //============================================================================== virtual NSArray* parametersForOverviewWithCount (int) { return [NSArray array]; } virtual NSArray* getFactoryPresets() { return [NSArray array]; } 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 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)); } private: struct Class : public ObjCClass { Class() : ObjCClass ("AUAudioUnit_") { addIvar ("cppObject"); addMethod (@selector (initWithComponentDescription:options:error:juceClass:), initWithComponentDescriptionAndJuceClass, "@@:", @encode (AudioComponentDescription), @encode (AudioComponentInstantiationOptions), "^@@"); addMethod (@selector (initWithComponentDescription:options:error:), initWithComponentDescription, "@@:", @encode (AudioComponentDescription), @encode (AudioComponentInstantiationOptions), "^@"); addMethod (@selector (dealloc), dealloc, "v@:"); addMethod (@selector (inputBusses), getInputBusses, "@@:"); addMethod (@selector (outputBusses), getOutputBusses, "@@:"); addMethod (@selector (parameterTree), getParameterTree, "@@:"); addMethod (@selector (deallocateRenderResources), deallocateRenderResources, "v@:"); addMethod (@selector (reset), reset, "v@:"); addMethod (@selector (shouldChangeToFormat:forBus:), shouldChangeToFormat, "B@:@@"); addMethod (@selector (factoryPresets), getFactoryPresets, "@@:"); addMethod (@selector (currentPreset), getCurrentPreset, "@@:"); addMethod (@selector (setCurrentPreset:), setCurrentPreset, "v@:@"); addMethod (@selector (fullState), getFullState, "@@:"); addMethod (@selector (setFullState:), setFullState, "v@:@"); addMethod (@selector (channelCapabilities), getChannelCapabilities, "@@:"); addMethod (@selector (allocateRenderResourcesAndReturnError:), allocateRenderResourcesAndReturnError, "B@:^@"); addMethod (@selector (parametersForOverviewWithCount:), parametersForOverviewWithCount, "@@:", @encode (NSInteger)); addMethod (@selector (setRenderingOffline:), setRenderingOffline, "v@:", @encode (BOOL)); addMethod (@selector (internalRenderBlock), getInternalRenderBlock, @encode (AUInternalRenderBlock), "@:"); addMethod (@selector (virtualMIDICableCount), getVirtualMIDICableCount, @encode (NSInteger), "@:"); addMethod (@selector (latency), getLatency, @encode (NSTimeInterval), "@:"); addMethod (@selector (tailTime), getTailTime, @encode (NSTimeInterval), "@:"); addMethod (@selector (canProcessInPlace), getCanProcessInPlace, @encode (BOOL), "@:"); addMethod (@selector (isRenderingOffline), getRenderingOffline, @encode (BOOL), "@:"); 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) { delete _this (self); } static AUAudioUnitBusArray* getInputBusses (id self, SEL) { return _this (self)->getInputBusses(); } static AUAudioUnitBusArray* getOutputBusses (id self, SEL) { return _this (self)->getOutputBusses(); } static AUParameterTree* getParameterTree (id self, SEL) { return _this (self)->getParameterTree(); } static AUInternalRenderBlock getInternalRenderBlock (id self, SEL) { return _this (self)->getInternalRenderBlock(); } 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 void reset (id self, SEL) { _this (self)->reset(); } static NSInteger getVirtualMIDICableCount (id self, SEL) { return _this (self)->getVirtualMIDICableCount(); } static BOOL shouldChangeToFormat (id self, SEL, AVAudioFormat* format, AUAudioUnitBus* bus) { return _this (self)->shouldChangeToFormat (format, bus) ? YES : NO; } static NSArray* parametersForOverviewWithCount (id self, SEL, NSInteger count) { return _this (self)->parametersForOverviewWithCount (static_cast (count)); } static NSArray* getFactoryPresets (id self, SEL) { return _this (self)->getFactoryPresets(); } static AUAudioUnitPreset* getCurrentPreset (id self, SEL) { return _this (self)->getCurrentPreset(); } static void setCurrentPreset (id self, SEL, AUAudioUnitPreset* preset) { return _this (self)->setCurrentPreset (preset); } static NSDictionary* getFullState (id self, SEL) { return _this (self)->getFullState(); } static void setFullState (id self, SEL, NSDictionary* state) { return _this (self)->setFullState (state); } static NSTimeInterval getLatency (id self, SEL) { return _this (self)->getLatency(); } static NSTimeInterval getTailTime (id self, SEL) { return _this (self)->getTailTime(); } 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 NSArray* getChannelCapabilities (id self, SEL) { return _this (self)->getChannelCapabilities(); } }; 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 { 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); removeEditor (processor); if (editorObserverToken != nullptr) { [paramTree removeParameterObserver: editorObserverToken]; editorObserverToken = nullptr; } } //============================================================================== void init() { 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 = [[NSMutableArray alloc] init]; for (int i = 0; i < channelInfos.size(); ++i) { AUChannelInfo& info = channelInfos.getReference (i); [channelCapabilities addObject: [NSNumber numberWithInteger: info.inChannels]]; [channelCapabilities 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; } AUAudioUnitBusArray* getInputBusses() override { return inputBusses; } AUAudioUnitBusArray* getOutputBusses() override { return outputBusses; } AUParameterTree* getParameterTree() override { return paramTree; } AUInternalRenderBlock getInternalRenderBlock() override { return internalRenderBlock; } NSArray* getFactoryPresets() override { return factoryPresets; } bool getRenderingOffline() override { return getAudioProcessor().isNonRealtime(); } void setRenderingOffline (bool offline) override { getAudioProcessor().setNonRealtime (offline); } NSArray* getChannelCapabilities() override { return channelCapabilities; } //============================================================================== AUAudioUnitPreset* getCurrentPreset() override { const int n = static_cast ([factoryPresets count]); const int idx = static_cast (getAudioProcessor().getCurrentProgram()); if (idx < n) return [factoryPresets objectAtIndex:static_cast (idx)]; return nullptr; } void setCurrentPreset(AUAudioUnitPreset* preset) override { const int n = static_cast ([factoryPresets count]); const int idx = static_cast ([preset number]); if (isPositiveAndBelow (idx, n)) getAudioProcessor().setCurrentProgram (idx); } //============================================================================== 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]; } //============================================================================== NSArray* parametersForOverviewWithCount (int count) override { const int n = static_cast ([overviewParams count]); if (count >= n) return overviewParams; NSMutableArray* retval = [[NSMutableArrayalloc] initWithArray: overviewParams]; [retval removeObjectsInRange: NSMakeRange (static_cast (count), static_cast (n - count))]; return [retval autorelease]; } int getVirtualMIDICableCount() override { #if JucePlugin_WantsMidiInput return 1; #else return 0; #endif } //============================================================================== 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 = AudioUnitHelpers::CALayoutTagToChannelSet (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 count] > 0 ? inputBusses : outputBusses) objectAtIndexedSubscript: 0] format] sampleRate] : 44100.0); processor.setRateAndBufferSizeDetails (sampleRate, static_cast (maxFrames)); processor.prepareToPlay (sampleRate, static_cast (maxFrames)); 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(); } void reset() override { midiMessages.clear(); lastTimeStamp.mSampleTime = std::numeric_limits::max(); } //============================================================================== 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 = AudioUnitHelpers::CALayoutTagToChannelSet (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; } //============================================================================== void audioProcessorChanged (AudioProcessor* processor) override { ignoreUnused (processor); [au willChangeValueForKey: @"allParameterValues"]; [au didChangeValueForKey: @"allParameterValues"]; } void audioProcessorParameterChanged (AudioProcessor*, int idx, float newValue) override { if (isPositiveAndBelow (idx, getAudioProcessor().getNumParameters())) { if (AUParameter* param = [paramTree parameterWithAddress: getAUParameterAddressForIndex (idx)]) { if (editorObserverToken != nullptr) [param setValue: newValue originator: editorObserverToken]; else [param setValue: newValue]; } } } //============================================================================== NSTimeInterval getLatency() override { auto& p = getAudioProcessor(); return p.getLatencySamples() / p.getSampleRate(); } NSTimeInterval getTailTime() override { return getAudioProcessor().getTailLengthSeconds(); } //============================================================================== 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 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), bufferList (nullptr), 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; int maxFrames, numberOfChannels; bool isInterleaved; AudioSampleBuffer scratchBuffer; }; //============================================================================== void addAudioUnitBusses (bool isInput) { ScopedPointer > array = [[NSMutableArray alloc] init]; AudioProcessor& processor = getAudioProcessor(); const int n = AudioUnitHelpers::getBusCount (&processor, isInput); for (int i = 0; i < n; ++i) { ScopedPointer audioUnitBus; { ScopedPointer defaultFormat = [[AVAudioFormat alloc] initStandardFormatWithSampleRate: kDefaultSampleRate channels: static_cast (processor.getChannelCountOfBus (isInput, i))]; audioUnitBus = [[AUAudioUnitBus alloc] initWithFormat: defaultFormat error: nullptr]; } [array addObject: audioUnitBus]; } (isInput ? inputBusses : outputBusses) = [[AUAudioUnitBusArray alloc] initWithAudioUnit: au busType: (isInput ? AUAudioUnitBusTypeInput : AUAudioUnitBusTypeOutput) busses: array]; } void addParameters() { ScopedPointer > params = [[NSMutableArray alloc] init]; paramObserver = CreateObjCBlock (this, &JuceAudioUnitv3::valueChangedFromHost); paramProvider = CreateObjCBlock (this, &JuceAudioUnitv3::getValue); overviewParams = [[NSMutableArray alloc] init]; auto& processor = getAudioProcessor(); const int n = processor.getNumParameters(); #if ! JUCE_FORCE_USE_LEGACY_PARAM_IDS // check if all parameters are managed? usingManagedParameter = (processor.getParameters().size() == processor.getNumParameters()); #endif for (int idx = 0; idx < n; ++idx) { const String identifier (idx); const String name = processor.getParameterName (idx); AudioUnitParameterUnit unit = kAudioUnitParameterUnit_Generic; AudioUnitParameterOptions flags = (UInt32) (kAudioUnitParameterFlag_IsWritable | kAudioUnitParameterFlag_IsReadable | kAudioUnitParameterFlag_HasCFNameString | kAudioUnitParameterFlag_ValuesHaveStrings); #if JucePlugin_AUHighResolutionParameters flags |= (UInt32) kAudioUnitParameterFlag_IsHighResolution; #endif // set whether the param is automatable (unnamed parameters aren't allowed to be automated) if (name.isEmpty() || ! processor.isParameterAutomatable (idx)) flags |= kAudioUnitParameterFlag_NonRealTime; if (processor.isMetaParameter (idx)) flags |= kAudioUnitParameterFlag_IsGlobalMeta; // is this a meter? if (((processor.getParameterCategory (idx) & 0xffff0000) >> 16) == 2) { flags &= ~kAudioUnitParameterFlag_IsWritable; flags |= kAudioUnitParameterFlag_MeterReadOnly | kAudioUnitParameterFlag_DisplayLogarithmic; unit = kAudioUnitParameterUnit_LinearGain; } #if JUCE_FORCE_USE_LEGACY_PARAM_IDS AUParameterAddress address = static_cast (idx); #else AUParameterAddress address = generateAUParameterAddressForIndex (idx); // Consider yourself very unlucky if you hit this assertion. The hash code of your // parameter ids are not unique. jassert (! paramMap.contains (static_cast (address))); paramAddresses.add (address); paramMap.set (static_cast (address), idx); #endif // create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h ScopedPointer param = [[AUParameterTree createParameterWithIdentifier: juceStringToNS (identifier) name: juceStringToNS (name) address: address min: 0.0f max: 1.0f unit: unit unitName: nullptr flags: flags valueStrings: nullptr dependentParameters: nullptr] retain]; [params addObject: param]; [overviewParams addObject: [NSNumber numberWithUnsignedLongLong:address]]; } // create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h paramTree = [[AUParameterTree createTreeWithChildren: params] retain]; [paramTree setImplementorValueObserver: paramObserver]; [paramTree setImplementorValueProvider: paramProvider]; if (processor.hasEditor()) { editorParamObserver = CreateObjCBlock (this, &JuceAudioUnitv3::valueChangedForObserver); editorObserverToken = [paramTree tokenByAddingParameterObserver: editorParamObserver]; } } void addPresets() { factoryPresets = [[NSMutableArray alloc] init]; const int n = getAudioProcessor().getNumPrograms(); for (int idx = 0; idx < n; ++idx) { String name = getAudioProcessor().getProgramName (idx); ScopedPointer preset = [[AUAudioUnitPreset alloc] init]; [preset setName: juceStringToNS (name)]; [preset setNumber: static_cast (idx)]; [factoryPresets addObject: preset]; } } //============================================================================== 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 : outputBusses) objectAtIndexedSubscript: static_cast (busIdx)], static_cast (maxFrames))); } void processEvents (const AURenderEvent *__nullable realtimeEventListHead, int numParams, AUEventSampleTime startTime) { 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; const int idx = getJuceParameterIndexForAUAddress (paramEvent.parameterAddress); if (isPositiveAndBelow (idx, numParams)) getAudioProcessor().setParameter (idx, 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 = processor.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); midiMessages.clear(); } // copy back audioBuffer.pop (*outBusBuffers[(int) outputBusNumber]->get(), mapper.get (false, (int) outputBusNumber)); return noErr; } void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiBuffer) noexcept { auto& processor = getAudioProcessor(); const ScopedLock sl (processor.getCallbackLock()); if (processor.isSuspended()) buffer.clear(); else if ([au shouldBypassEffect]) processor.processBlockBypassed (buffer, midiBuffer); else processor.processBlock (buffer, midiBuffer); } //============================================================================== void valueChangedFromHost (AUParameter* param, AUValue value) { if (param != nullptr) { const int idx = getJuceParameterIndexForAUAddress ([param address]); auto& processor = getAudioProcessor(); if (isPositiveAndBelow (idx, processor.getNumParameters())) processor.setParameter (idx, value); } } AUValue getValue (AUParameter* param) { if (param != nullptr) { const int idx = getJuceParameterIndexForAUAddress ([param address]); auto& processor = getAudioProcessor(); if (isPositiveAndBelow (idx, processor.getNumParameters())) return processor.getParameter (idx); } return 0; } void valueChangedForObserver(AUParameterAddress, AUValue) { // this will have already been handled bny valueChangedFromHost } //============================================================================== #if JUCE_FORCE_USE_LEGACY_PARAM_IDS inline AUParameterAddress getAUParameterAddressForIndex (int paramIndex) const noexcept { return static_cast (paramIndex); } inline int getJuceParameterIndexForAUAddress (AUParameterAddress address) const noexcept { return static_cast (address); } #else AUParameterAddress generateAUParameterAddressForIndex (int paramIndex) const { auto& processor = getAudioProcessor(); const int n = processor.getNumParameters(); if (isPositiveAndBelow (paramIndex, n)) { const String& juceParamID = processor.getParameterID (paramIndex); return usingManagedParameter ? static_cast (juceParamID.hashCode64()) : static_cast (juceParamID.getIntValue()); } return static_cast (-1); } inline AUParameterAddress getAUParameterAddressForIndex (int paramIndex) const noexcept { return usingManagedParameter ? paramAddresses.getReference (paramIndex) : static_cast (paramIndex); } inline int getJuceParameterIndexForAUAddress (AUParameterAddress address) const noexcept { return usingManagedParameter ? paramMap[static_cast (address)] : static_cast (address); } #endif //============================================================================== static const double kDefaultSampleRate; AudioProcessorHolder::Ptr processorHolder; int totalInChannels, totalOutChannels; ScopedPointer inputBusses; ScopedPointer outputBusses; ObjCBlock paramObserver; ObjCBlock paramProvider; #if ! JUCE_FORCE_USE_LEGACY_PARAM_IDS bool usingManagedParameter; Array paramAddresses; HashMap paramMap; #endif // to avoid recursion on parameter changes, we need to add an // editor observer to do the parameter changes ObjCBlock editorParamObserver; AUParameterObserverToken editorObserverToken; ScopedPointer paramTree; ScopedPointer > overviewParams; ScopedPointer > channelCapabilities; ScopedPointer > factoryPresets; ObjCBlock internalRenderBlock; AudioUnitHelpers::CoreAudioBufferList audioBuffer; AudioUnitHelpers::ChannelRemapper mapper; OwnedArray inBusBuffers, outBusBuffers; MidiBuffer midiMessages; ObjCBlock hostMusicalContextCallback; ObjCBlock hostTransportStateCallback; AudioTimeStamp lastTimeStamp; CurrentPositionInfo lastAudioHead; }; 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); } //============================================================================== class JuceAUViewController { public: JuceAUViewController (AUViewController* p) : myself (p), processorHolder (nullptr), preferredSize (1.0f, 1.0f) { jassert (MessageManager::getInstance()->isThisTheMessageThread()); PluginHostType::jucePlugInClientCurrentWrapperType = AudioProcessor::wrapperType_AudioUnitv3; initialiseJuce_GUI(); } ~JuceAUViewController() { jassert (MessageManager::getInstance()->isThisTheMessageThread()); if (processorHolder != nullptr) JuceAudioUnitv3::removeEditor (getAudioProcessor()); } //============================================================================== void loadView() { jassert (MessageManager::getInstance()->isThisTheMessageThread()); 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]; editor->setVisible (true); editor->addToDesktop (0, view); } } } } void viewDidLayoutSubviews() { if (processorHolder != nullptr && [myself view] != nullptr) { if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) { 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 } } } } 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; Rectangle preferredSize; //============================================================================== AUAudioUnit* createAudioUnitOnMessageThread (const AudioComponentDescription& descr, NSError** error) { jassert (MessageManager::getInstance()->isThisTheMessageThread()); [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) { ScopedPointer cpp; } - (instancetype) initWithNibName: (nullable NSString*) nib bundle: (nullable NSBundle*) bndl { self = [super initWithNibName: nib bundle: bndl]; cpp = 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 { return cpp->viewDidLayoutSubviews(); } @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 Image(); } #endif #endif #pragma clang diagnostic pop #endif