juicysfplugin/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm

1941 lines
82 KiB
Plaintext

/*
==============================================================================
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 <CoreAudioKit/CoreAudioKit.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
#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<ViewConfig> viewConfiguration;
using Ptr = ReferenceCountedObjectPtr<AudioProcessorHolder>;
private:
std::unique_ptr<AudioProcessor> 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<AUAudioUnitPreset*>* getFactoryPresets() = 0;
virtual NSDictionary<NSString*, id>* getFullState()
{
objc_super s = { getAudioUnit(), [AUAudioUnit class] };
return ObjCMsgSendSuper<NSDictionary<NSString*, id>*> (&s, @selector (fullState));
}
virtual void setFullState (NSDictionary<NSString*, id>* state)
{
objc_super s = { getAudioUnit(), [AUAudioUnit class] };
ObjCMsgSendSuper<void, NSDictionary<NSString*, id>*> (&s, @selector (setFullState:), state);
}
virtual AUParameterTree* getParameterTree() = 0;
virtual NSArray<NSNumber*>* parametersForOverviewWithCount (int) = 0;
//==============================================================================
virtual NSTimeInterval getLatency() = 0;
virtual NSTimeInterval getTailTime() = 0;
//==============================================================================
virtual AUAudioUnitBusArray* getInputBusses() = 0;
virtual AUAudioUnitBusArray* getOutputBusses() = 0;
virtual NSArray<NSNumber*>* getChannelCapabilities() = 0;
virtual bool shouldChangeToFormat (AVAudioFormat*, AUAudioUnitBus*) = 0;
//==============================================================================
virtual int getVirtualMIDICableCount() = 0;
virtual bool getSupportsMPE() = 0;
virtual NSArray<NSString*>* 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<BOOL> (&s, @selector (shouldBypassEffect)) == YES);
}
virtual void setShouldBypassEffect (bool shouldBypass)
{
objc_super s = { getAudioUnit(), [AUAudioUnit class] };
ObjCMsgSendSuper<void, BOOL> (&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<BOOL, NSError**> (&s, @selector (allocateRenderResourcesAndReturnError:), outError) == YES);
}
virtual void deallocateRenderResources()
{
objc_super s = { getAudioUnit(), [AUAudioUnit class] };
ObjCMsgSendSuper<void> (&s, @selector (deallocateRenderResources));
}
//==============================================================================
#if JUCE_AUV3_VIEW_CONFIG_SUPPORTED
virtual NSIndexSet* getSupportedViewConfigurations (NSArray<AUAudioUnitViewConfiguration*>*) = 0;
virtual void selectViewConfiguration (AUAudioUnitViewConfiguration*) = 0;
#endif
private:
struct Class : public ObjCClass<AUAudioUnit>
{
Class() : ObjCClass<AUAudioUnit> ("AUAudioUnit_")
{
addIvar<JuceAudioUnitv3Base*> ("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<JuceAudioUnitv3Base*> (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<AUAudioUnit*, AudioComponentDescription,
AudioComponentInstantiationOptions, NSError**> (&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<AUAudioUnit*, AudioComponentDescription,
AudioComponentInstantiationOptions, NSError**> (&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<AUAudioUnitPreset*>* getFactoryPresets (id self, SEL) { return _this (self)->getFactoryPresets(); }
static NSDictionary<NSString*, id>* getFullState (id self, SEL) { return _this (self)->getFullState(); }
static void setFullState (id self, SEL, NSDictionary<NSString *, id>* state) { return _this (self)->setFullState (state); }
static AUParameterTree* getParameterTree (id self, SEL) { return _this (self)->getParameterTree(); }
static NSArray<NSNumber*>* parametersForOverviewWithCount (id self, SEL, NSInteger count) { return _this (self)->parametersForOverviewWithCount (static_cast<int> (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<NSNumber*>* 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<NSString*>* 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<AUAudioUnitViewConfiguration*>* 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<int> (maxFrames));
Array<AUChannelInfo> 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<AUChannelInfo> channelInfos = AudioUnitHelpers::getAUChannelInfo (processor);
#endif
processor.setPlayHead (this);
totalInChannels = processor.getTotalNumInputChannels();
totalOutChannels = processor.getTotalNumOutputChannels();
{
channelCapabilities.reset ([[NSMutableArray<NSNumber*> 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<int> (maxFrames));
processor.prepareToPlay (kDefaultSampleRate, static_cast<int> (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<Float64>::max();
}
//==============================================================================
AUAudioUnitPreset* getCurrentPreset() override
{
const int n = static_cast<int> ([factoryPresets.get() count]);
const int idx = static_cast<int> (getAudioProcessor().getCurrentProgram());
if (idx < n)
return [factoryPresets.get() objectAtIndex:static_cast<unsigned int> (idx)];
return nullptr;
}
void setCurrentPreset(AUAudioUnitPreset* preset) override
{
const int n = static_cast<int> ([factoryPresets.get() count]);
const int idx = static_cast<int> ([preset number]);
if (isPositiveAndBelow (idx, n))
getAudioProcessor().setCurrentProgram (idx);
}
NSArray<AUAudioUnitPreset*>* getFactoryPresets() override
{
return factoryPresets.get();
}
NSDictionary<NSString*, id>* getFullState() override
{
NSMutableDictionary<NSString*, id>* retval = [[NSMutableDictionary<NSString*, id> alloc] init];
{
NSDictionary<NSString*, id>* 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<NSString*, id>* state) override
{
if (state == nullptr)
return;
NSMutableDictionary<NSString*, id>* modifiedState = [[NSMutableDictionary<NSString*, id> 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<NSData*> (obj);
const int numBytes = static_cast<int> ([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<NSNumber*>* parametersForOverviewWithCount (int count) override
{
const int n = static_cast<int> ([overviewParams.get() count]);
if (count >= n)
return overviewParams.get();
NSMutableArray<NSNumber*>* retval = [[NSMutableArray<NSNumber*>alloc] initWithArray: overviewParams.get()];
[retval removeObjectsInRange: NSMakeRange (static_cast<unsigned int> (count), static_cast<unsigned int> (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<NSNumber*>* getChannelCapabilities() override { return channelCapabilities.get(); }
bool shouldChangeToFormat (AVAudioFormat* format, AUAudioUnitBus* auBus) override
{
const bool isInput = ([auBus busType] == AUAudioUnitBusTypeInput);
const int busIdx = static_cast<int> ([auBus index]);
const int newNumChannels = static_cast<int> ([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<NSString*>* 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<AudioChannelSet>& channelSets = (isInput ? layouts.inputBuses : layouts.outputBuses);
AUAudioUnitBusArray* auBuses = (isInput ? [getAudioUnit() inputBusses] : [getAudioUnit() outputBusses]);
jassert ([auBuses count] == static_cast<NSUInteger> (n));
for (int busIdx = 0; busIdx < n; ++busIdx)
{
AudioProcessor::Bus* bus = processor.getBus (isInput, busIdx);
AVAudioFormat* format = [[auBuses objectAtIndexedSubscript:static_cast<NSUInteger> (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<int> ([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<int> (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<int> (maxFrames));
processor.prepareToPlay (sampleRate, static_cast<int> (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<AUAudioUnitViewConfiguration*>* 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<int> ([[auBus format] channelCount])),
isInterleaved ([[auBus format] isInterleaved])
{
alloc();
}
//==============================================================================
void alloc()
{
const int numBuffers = isInterleaved ? 1 : numberOfChannels;
int bytes = static_cast<int> (sizeof (AudioBufferList))
+ ((numBuffers - 1) * static_cast<int> (sizeof (::AudioBuffer)));
jassert (bytes > 0);
bufferListStorage.calloc (static_cast<size_t> (bytes));
bufferList = reinterpret_cast<AudioBufferList*> (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<UInt32> (numBuffers);
for (int i = 0; i < numBuffers; ++i)
{
const UInt32 bufferChannels = static_cast<UInt32> (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<int> (otherInterleaved ? other->mBuffers[0].mNumberChannels
: other->mNumberBuffers);
return otherInterleaved == isInterleaved
&& numberOfChannels == otherChannels;
}
return numberOfChannels == 0;
}
private:
AUAudioUnitBus* auBus;
HeapBlock<char> bufferListStorage;
AudioBufferList* bufferList = nullptr;
int maxFrames, numberOfChannels;
bool isInterleaved;
AudioBuffer<float> scratchBuffer;
};
//==============================================================================
void addAudioUnitBusses (bool isInput)
{
std::unique_ptr<NSMutableArray<AUAudioUnitBus*>, NSObjectDeleter> array ([[NSMutableArray<AUAudioUnitBus*> alloc] init]);
AudioProcessor& processor = getAudioProcessor();
const int n = AudioUnitHelpers::getBusCount (&processor, isInput);
for (int i = 0; i < n; ++i)
{
std::unique_ptr<AUAudioUnitBus, NSObjectDeleter> audioUnitBus;
{
std::unique_ptr<AVAudioFormat, NSObjectDeleter> defaultFormat ([[AVAudioFormat alloc] initStandardFormatWithSampleRate: kDefaultSampleRate
channels: static_cast<AVAudioChannelCount> (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<AUParameter, NSObjectDeleter> 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<NSMutableArray, NSObjectDeleter> 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<int64> (address)));
paramAddresses.add (address);
paramMap.set (static_cast<int64> (address), parameter->getParameterIndex());
#endif
auto getParameterIdentifier = [parameter]
{
if (auto* paramWithID = dynamic_cast<AudioProcessorParameterWithID*> (parameter))
return paramWithID->paramID;
// This could clash if any groups have been given integer IDs!
return String (parameter->getParameterIndex());
};
std::unique_ptr<AUParameter, NSObjectDeleter> 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<AUParameterGroup, NSObjectDeleter> createParameterGroup (AudioProcessorParameterGroup* group)
{
std::unique_ptr<NSMutableArray<AUParameterNode*>, NSObjectDeleter> children ([NSMutableArray<AUParameterNode*> 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<AUParameterGroup, NSObjectDeleter> 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<NSNumber*> new]);
std::unique_ptr<NSMutableArray<AUParameterNode*>, NSObjectDeleter> topLevelNodes ([NSMutableArray<AUParameterNode*> 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<AUAudioUnitPreset*> alloc] init]);
const int n = getAudioProcessor().getNumPrograms();
for (int idx = 0; idx < n; ++idx)
{
String name = getAudioProcessor().getProgramName (idx);
std::unique_ptr<AUAudioUnitPreset, NSObjectDeleter> preset ([[AUAudioUnitPreset alloc] init]);
[preset.get() setName: juceStringToNS (name)];
[preset.get() setNumber: static_cast<NSInteger> (idx)];
[factoryPresets.get() addObject: preset.get()];
}
}
//==============================================================================
void allocateBusBuffer (bool isInput)
{
OwnedArray<BusBuffer>& 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<unsigned int> (busIdx)],
static_cast<int> (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<int> (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<int> (frameCount) <= getAudioProcessor().getBlockSize());
// process params
const int numParams = juceParameters.getNumParameters();
processEvents (realtimeEventListHead, numParams, static_cast<AUEventSampleTime> (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<float*> (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<float*> (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<float>& 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<AUParameterAddress> (paramIndex);
#else
return paramAddresses.getReference (paramIndex);
#endif
}
inline int getJuceParameterIndexForAUAddress (AUParameterAddress address) const noexcept
{
#if JUCE_FORCE_USE_LEGACY_PARAM_IDS
return static_cast<int> (address);
#else
return paramMap[static_cast<int64> (address)];
#endif
}
AUParameterAddress generateAUParameterAddress (AudioProcessorParameter* param) const
{
const String& juceParamID = LegacyAudioParameter::getParamID (param, forceLegacyParamIDs);
return static_cast<AUParameterAddress> (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<AUAudioUnitBusArray, NSObjectDeleter> inputBusses, outputBusses;
ObjCBlock<AUImplementorValueObserver> paramObserver;
ObjCBlock<AUImplementorValueProvider> paramProvider;
ObjCBlock<AUImplementorStringFromValueCallback> stringFromValueProvider;
ObjCBlock<AUImplementorValueFromStringCallback> valueFromStringProvider;
#if ! JUCE_FORCE_USE_LEGACY_PARAM_IDS
Array<AUParameterAddress> paramAddresses;
HashMap<int64, int> paramMap;
#endif
LegacyAudioParametersWrapper juceParameters;
// to avoid recursion on parameter changes, we need to add an
// editor observer to do the parameter changes
ObjCBlock<AUParameterObserver> editorParamObserver;
AUParameterObserverToken editorObserverToken;
std::unique_ptr<AUParameterTree, NSObjectDeleter> paramTree;
std::unique_ptr<NSMutableArray<NSNumber*>, NSObjectDeleter> overviewParams, channelCapabilities;
std::unique_ptr<NSMutableArray<AUAudioUnitPreset*>, NSObjectDeleter> factoryPresets;
ObjCBlock<AUInternalRenderBlock> internalRenderBlock;
AudioUnitHelpers::CoreAudioBufferList audioBuffer;
AudioUnitHelpers::ChannelRemapper mapper;
OwnedArray<BusBuffer> inBusBuffers, outBusBuffers;
MidiBuffer midiMessages;
ObjCBlock<AUHostMusicalContextBlock> hostMusicalContextCallback;
ObjCBlock<AUHostTransportStateBlock> hostTransportStateCallback;
AudioTimeStamp lastTimeStamp;
CurrentPositionInfo lastAudioHead;
String contextName;
ThreadLocalValue<bool> 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<AUAudioUnitFactory>* 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<UIViewPeerControllerReceiver*> (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<float> (preferredSize.getWidth()),
static_cast<float> (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<AUAudioUnitFactory>* myself;
AudioProcessorHolder::Ptr processorHolder = nullptr;
Rectangle<int> 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<AUAudioUnitFactory>
@end
@implementation JUCE_VIEWCONTROLLER_OBJC_NAME (JucePlugin_AUExportPrefix)
{
std::unique_ptr<JuceAUViewController> 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