2 ==============================================================================
\r
4 This file is part of the JUCE library - "Jules' Utility Class Extensions"
\r
5 Copyright 2004-11 by Raw Material Software Ltd.
\r
7 ------------------------------------------------------------------------------
\r
9 JUCE can be redistributed and/or modified under the terms of the GNU General
\r
10 Public License (Version 2), as published by the Free Software Foundation.
\r
11 A copy of the license is included in the JUCE distribution, or can be found
\r
12 online at www.gnu.org/licenses.
\r
14 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
\r
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
\r
16 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
\r
18 ------------------------------------------------------------------------------
\r
20 To release a closed-source product which uses JUCE, commercial licenses are
\r
21 available: visit www.rawmaterialsoftware.com/juce for more information.
\r
23 ==============================================================================
\r
26 #include <Cocoa/Cocoa.h>
\r
27 #include <AudioUnit/AUCocoaUIView.h>
\r
28 #include <AudioUnit/AudioUnit.h>
\r
29 #include <AudioToolbox/AudioUnitUtilities.h>
\r
30 #include "AUMIDIEffectBase.h"
\r
31 #include "MusicDeviceBase.h"
\r
32 #include "../juce_IncludeCharacteristics.h"
\r
34 #if JucePlugin_Build_AU
\r
36 /** The BUILD_AU_CARBON_UI flag lets you specify whether old-school carbon hosts are supported as
\r
37 well as ones that can open a cocoa view. If this is enabled, you'll need to also add the AUCarbonBase
\r
38 files to your project.
\r
40 #ifndef BUILD_AU_CARBON_UI
\r
41 #define BUILD_AU_CARBON_UI 1
\r
45 #undef BUILD_AU_CARBON_UI // (not possible in a 64-bit build)
\r
48 #if BUILD_AU_CARBON_UI
\r
50 #include "AUCarbonViewBase.h"
\r
54 #define JUCE_MAC_WINDOW_VISIBITY_BODGE 1
\r
56 #include "../juce_PluginHeaders.h"
\r
57 #include "../juce_PluginHostType.h"
\r
59 //==============================================================================
\r
60 #define juceFilterObjectPropertyID 0x1a45ffe9
\r
61 static Array<void*> activePlugins, activeUIs;
\r
63 static const short channelConfigs[][2] = { JucePlugin_PreferredChannelConfigurations };
\r
64 static const int numChannelConfigs = sizeof (channelConfigs) / sizeof (*channelConfigs);
\r
66 #if JucePlugin_IsSynth
\r
67 #define JuceAUBaseClass MusicDeviceBase
\r
69 #define JuceAUBaseClass AUMIDIEffectBase
\r
72 //==============================================================================
\r
73 /** Somewhere in the codebase of your plugin, you need to implement this function
\r
74 and make it create an instance of the filter subclass that you're building.
\r
76 extern AudioProcessor* JUCE_CALLTYPE createPluginFilter();
\r
78 //==============================================================================
\r
79 #define appendMacro1(a, b, c, d) a ## _ ## b ## _ ## c ## _ ## d
\r
80 #define appendMacro2(a, b, c, d) appendMacro1(a, b, c, d)
\r
81 #define MakeObjCClassName(rootName) appendMacro2 (rootName, JUCE_MAJOR_VERSION, JUCE_MINOR_VERSION, JucePlugin_AUExportPrefix)
\r
83 #define JuceUICreationClass JucePlugin_AUCocoaViewClassName
\r
84 #define JuceUIViewClass MakeObjCClassName(JuceUIViewClass)
\r
87 class EditorCompHolder;
\r
89 //==============================================================================
\r
90 @interface JuceUICreationClass : NSObject <AUCocoaUIBase>
\r
94 - (JuceUICreationClass*) init;
\r
96 - (unsigned) interfaceVersion;
\r
97 - (NSString *) description;
\r
98 - (NSView*) uiViewForAudioUnit: (AudioUnit) inAudioUnit
\r
99 withSize: (NSSize) inPreferredSize;
\r
102 //==============================================================================
\r
103 @interface JuceUIViewClass : NSView
\r
105 AudioProcessor* filter;
\r
107 EditorCompHolder* editorComp;
\r
110 - (JuceUIViewClass*) initWithFilter: (AudioProcessor*) filter
\r
111 withAU: (JuceAU*) au
\r
112 withComponent: (AudioProcessorEditor*) editorComp;
\r
115 - (void) applicationWillTerminate: (NSNotification*) aNotification;
\r
116 - (void) viewDidMoveToWindow;
\r
117 - (BOOL) mouseDownCanMoveWindow;
\r
118 - (void) filterBeingDeleted: (JuceAU*) au_;
\r
119 - (void) deleteEditor;
\r
124 //==============================================================================
\r
125 class JuceAU : public JuceAUBaseClass,
\r
126 public AudioProcessorListener,
\r
127 public AudioPlayHead,
\r
128 public ComponentListener
\r
131 //==============================================================================
\r
132 JuceAU (AudioUnit component)
\r
133 #if JucePlugin_IsSynth
\r
134 : MusicDeviceBase (component, 0, 1),
\r
136 : AUMIDIEffectBase (component),
\r
138 bufferSpace (2, 16),
\r
141 if (activePlugins.size() + activeUIs.size() == 0)
\r
143 #if BUILD_AU_CARBON_UI
\r
144 NSApplicationLoad();
\r
147 initialiseJuce_GUI();
\r
150 juceFilter = createPluginFilter();
\r
151 jassert (juceFilter != nullptr);
\r
153 juceFilter->setPlayHead (this);
\r
154 juceFilter->addListener (this);
\r
156 Globals()->UseIndexedParameters (juceFilter->getNumParameters());
\r
158 activePlugins.add (this);
\r
160 zerostruct (auEvent);
\r
161 auEvent.mArgument.mParameter.mAudioUnit = GetComponentInstance();
\r
162 auEvent.mArgument.mParameter.mScope = kAudioUnitScope_Global;
\r
163 auEvent.mArgument.mParameter.mElement = 0;
\r
168 for (int i = activeUIs.size(); --i >= 0;)
\r
169 [((JuceUIViewClass*) activeUIs.getUnchecked(i)) filterBeingDeleted: this];
\r
171 juceFilter = nullptr;
\r
173 jassert (activePlugins.contains (this));
\r
174 activePlugins.removeValue (this);
\r
176 if (activePlugins.size() + activeUIs.size() == 0)
\r
177 shutdownJuce_GUI();
\r
180 //==============================================================================
\r
181 ComponentResult GetPropertyInfo (AudioUnitPropertyID inID,
\r
182 AudioUnitScope inScope,
\r
183 AudioUnitElement inElement,
\r
184 UInt32& outDataSize,
\r
185 Boolean& outWritable)
\r
187 if (inScope == kAudioUnitScope_Global)
\r
189 if (inID == juceFilterObjectPropertyID)
\r
191 outWritable = false;
\r
192 outDataSize = sizeof (void*) * 2;
\r
195 else if (inID == kAudioUnitProperty_OfflineRender)
\r
197 outWritable = true;
\r
198 outDataSize = sizeof (UInt32);
\r
201 else if (inID == kMusicDeviceProperty_InstrumentCount)
\r
203 outDataSize = sizeof (UInt32);
\r
204 outWritable = false;
\r
207 else if (inID == kAudioUnitProperty_CocoaUI)
\r
209 #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
\r
210 // (On 10.4, there's a random obj-c dispatching crash when trying to load a cocoa UI)
\r
211 if (PlatformUtilities::getOSXMinorVersionNumber() > 4)
\r
214 outDataSize = sizeof (AudioUnitCocoaViewInfo);
\r
215 outWritable = true;
\r
221 return JuceAUBaseClass::GetPropertyInfo (inID, inScope, inElement, outDataSize, outWritable);
\r
224 ComponentResult GetProperty (AudioUnitPropertyID inID,
\r
225 AudioUnitScope inScope,
\r
226 AudioUnitElement inElement,
\r
229 if (inScope == kAudioUnitScope_Global)
\r
231 if (inID == juceFilterObjectPropertyID)
\r
233 ((void**) outData)[0] = (void*) static_cast <AudioProcessor*> (juceFilter);
\r
234 ((void**) outData)[1] = (void*) this;
\r
237 else if (inID == kAudioUnitProperty_OfflineRender)
\r
239 *(UInt32*) outData = (juceFilter != nullptr && juceFilter->isNonRealtime()) ? 1 : 0;
\r
242 else if (inID == kMusicDeviceProperty_InstrumentCount)
\r
244 *(UInt32*) outData = 1;
\r
247 else if (inID == kAudioUnitProperty_CocoaUI)
\r
249 #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
\r
250 // (On 10.4, there's a random obj-c dispatching crash when trying to load a cocoa UI)
\r
251 if (PlatformUtilities::getOSXMinorVersionNumber() > 4)
\r
254 JUCE_AUTORELEASEPOOL
\r
256 AudioUnitCocoaViewInfo* info = (AudioUnitCocoaViewInfo*) outData;
\r
258 const File bundleFile (File::getSpecialLocation (File::currentApplicationFile));
\r
259 NSString* bundlePath = [NSString stringWithUTF8String: (const char*) bundleFile.getFullPathName().toUTF8()];
\r
260 NSBundle* b = [NSBundle bundleWithPath: bundlePath];
\r
262 info->mCocoaAUViewClass[0] = (CFStringRef) [[[JuceUICreationClass class] className] retain];
\r
263 info->mCocoaAUViewBundleLocation = (CFURLRef) [[NSURL fileURLWithPath: [b bundlePath]] retain];
\r
270 return JuceAUBaseClass::GetProperty (inID, inScope, inElement, outData);
\r
273 ComponentResult SetProperty (AudioUnitPropertyID inID,
\r
274 AudioUnitScope inScope,
\r
275 AudioUnitElement inElement,
\r
276 const void* inData,
\r
279 if (inScope == kAudioUnitScope_Global && inID == kAudioUnitProperty_OfflineRender)
\r
281 if (juceFilter != nullptr)
\r
282 juceFilter->setNonRealtime ((*(UInt32*) inData) != 0);
\r
287 return JuceAUBaseClass::SetProperty (inID, inScope, inElement, inData, inDataSize);
\r
290 ComponentResult SaveState (CFPropertyListRef* outData)
\r
292 ComponentResult err = JuceAUBaseClass::SaveState (outData);
\r
297 jassert (CFGetTypeID (*outData) == CFDictionaryGetTypeID());
\r
299 CFMutableDictionaryRef dict = (CFMutableDictionaryRef) *outData;
\r
301 if (juceFilter != nullptr)
\r
304 juceFilter->getCurrentProgramStateInformation (state);
\r
306 if (state.getSize() > 0)
\r
308 CFDataRef ourState = CFDataCreate (kCFAllocatorDefault, (const UInt8*) state.getData(), state.getSize());
\r
309 CFDictionarySetValue (dict, CFSTR("jucePluginState"), ourState);
\r
310 CFRelease (ourState);
\r
317 ComponentResult RestoreState (CFPropertyListRef inData)
\r
319 ComponentResult err = JuceAUBaseClass::RestoreState (inData);
\r
324 if (juceFilter != nullptr)
\r
326 CFDictionaryRef dict = (CFDictionaryRef) inData;
\r
327 CFDataRef data = 0;
\r
329 if (CFDictionaryGetValueIfPresent (dict, CFSTR("jucePluginState"), (const void**) &data))
\r
333 const int numBytes = (int) CFDataGetLength (data);
\r
334 const JUCE_NAMESPACE::uint8* const rawBytes = CFDataGetBytePtr (data);
\r
337 juceFilter->setCurrentProgramStateInformation (rawBytes, numBytes);
\r
345 UInt32 SupportedNumChannels (const AUChannelInfo** outInfo)
\r
347 // You need to actually add some configurations to the JucePlugin_PreferredChannelConfigurations
\r
348 // value in your JucePluginCharacteristics.h file..
\r
349 jassert (numChannelConfigs > 0);
\r
351 if (outInfo != nullptr)
\r
353 *outInfo = channelInfo;
\r
355 for (int i = 0; i < numChannelConfigs; ++i)
\r
357 #if JucePlugin_IsSynth
\r
358 channelInfo[i].inChannels = 0;
\r
360 channelInfo[i].inChannels = channelConfigs[i][0];
\r
362 channelInfo[i].outChannels = channelConfigs[i][1];
\r
366 return numChannelConfigs;
\r
369 //==============================================================================
\r
370 ComponentResult GetParameterInfo (AudioUnitScope inScope,
\r
371 AudioUnitParameterID inParameterID,
\r
372 AudioUnitParameterInfo& outParameterInfo)
\r
374 const int index = (int) inParameterID;
\r
376 if (inScope == kAudioUnitScope_Global
\r
377 && juceFilter != nullptr
\r
378 && index < juceFilter->getNumParameters())
\r
380 outParameterInfo.flags = kAudioUnitParameterFlag_IsWritable
\r
381 | kAudioUnitParameterFlag_IsReadable
\r
382 | kAudioUnitParameterFlag_HasCFNameString;
\r
384 const String name (juceFilter->getParameterName (index));
\r
386 // set whether the param is automatable (unnamed parameters aren't allowed to be automated)
\r
387 if (name.isEmpty() || ! juceFilter->isParameterAutomatable (index))
\r
388 outParameterInfo.flags |= kAudioUnitParameterFlag_NonRealTime;
\r
390 if (juceFilter->isMetaParameter (index))
\r
391 outParameterInfo.flags |= kAudioUnitParameterFlag_IsGlobalMeta;
\r
393 AUBase::FillInParameterName (outParameterInfo,
\r
394 PlatformUtilities::juceStringToCFString (name),
\r
397 outParameterInfo.minValue = 0.0f;
\r
398 outParameterInfo.maxValue = 1.0f;
\r
399 outParameterInfo.defaultValue = 0.0f;
\r
400 outParameterInfo.unit = kAudioUnitParameterUnit_Generic;
\r
406 return kAudioUnitErr_InvalidParameter;
\r
410 ComponentResult GetParameter (AudioUnitParameterID inID,
\r
411 AudioUnitScope inScope,
\r
412 AudioUnitElement inElement,
\r
415 if (inScope == kAudioUnitScope_Global && juceFilter != nullptr)
\r
417 outValue = juceFilter->getParameter ((int) inID);
\r
421 return AUBase::GetParameter (inID, inScope, inElement, outValue);
\r
424 ComponentResult SetParameter (AudioUnitParameterID inID,
\r
425 AudioUnitScope inScope,
\r
426 AudioUnitElement inElement,
\r
428 UInt32 inBufferOffsetInFrames)
\r
430 if (inScope == kAudioUnitScope_Global && juceFilter != nullptr)
\r
432 juceFilter->setParameter ((int) inID, inValue);
\r
436 return AUBase::SetParameter (inID, inScope, inElement, inValue, inBufferOffsetInFrames);
\r
439 //==============================================================================
\r
440 ComponentResult Version() { return JucePlugin_VersionCode; }
\r
441 bool SupportsTail() { return true; }
\r
442 Float64 GetTailTime() { return (JucePlugin_TailLengthSeconds); }
\r
443 Float64 GetSampleRate() { return GetOutput(0)->GetStreamFormat().mSampleRate; }
\r
445 Float64 GetLatency()
\r
447 jassert (GetSampleRate() > 0);
\r
449 if (GetSampleRate() <= 0)
\r
452 return juceFilter->getLatencySamples() / GetSampleRate();
\r
455 //==============================================================================
\r
456 #if BUILD_AU_CARBON_UI
\r
457 int GetNumCustomUIComponents() { return 1; }
\r
459 void GetUIComponentDescs (ComponentDescription* inDescArray)
\r
461 inDescArray[0].componentType = kAudioUnitCarbonViewComponentType;
\r
462 inDescArray[0].componentSubType = JucePlugin_AUSubType;
\r
463 inDescArray[0].componentManufacturer = JucePlugin_AUManufacturerCode;
\r
464 inDescArray[0].componentFlags = 0;
\r
465 inDescArray[0].componentFlagsMask = 0;
\r
469 //==============================================================================
\r
470 bool getCurrentPosition (AudioPlayHead::CurrentPositionInfo& info)
\r
472 info.timeSigNumerator = 0;
\r
473 info.timeSigDenominator = 0;
\r
474 info.timeInSeconds = 0;
\r
475 info.editOriginTime = 0;
\r
476 info.ppqPositionOfLastBarStart = 0;
\r
477 info.isPlaying = false;
\r
478 info.isRecording = false;
\r
480 switch (lastSMPTETime.mType)
\r
482 case kSMPTETimeType24: info.frameRate = AudioPlayHead::fps24; break;
\r
483 case kSMPTETimeType25: info.frameRate = AudioPlayHead::fps25; break;
\r
484 case kSMPTETimeType30Drop: info.frameRate = AudioPlayHead::fps30drop; break;
\r
485 case kSMPTETimeType30: info.frameRate = AudioPlayHead::fps30; break;
\r
486 case kSMPTETimeType2997: info.frameRate = AudioPlayHead::fps2997; break;
\r
487 case kSMPTETimeType2997Drop: info.frameRate = AudioPlayHead::fps2997drop; break;
\r
488 //case kSMPTETimeType60:
\r
489 //case kSMPTETimeType5994:
\r
490 default: info.frameRate = AudioPlayHead::fpsUnknown; break;
\r
493 if (CallHostBeatAndTempo (&info.ppqPosition, &info.bpm) != noErr)
\r
495 info.ppqPosition = 0;
\r
499 UInt32 outDeltaSampleOffsetToNextBeat;
\r
500 double outCurrentMeasureDownBeat;
\r
504 if (CallHostMusicalTimeLocation (&outDeltaSampleOffsetToNextBeat, &num, &den,
\r
505 &outCurrentMeasureDownBeat) == noErr)
\r
507 info.timeSigNumerator = (int) num;
\r
508 info.timeSigDenominator = den;
\r
509 info.ppqPositionOfLastBarStart = outCurrentMeasureDownBeat;
\r
512 double outCurrentSampleInTimeLine, outCycleStartBeat, outCycleEndBeat;
\r
513 Boolean playing, playchanged, looping;
\r
515 if (CallHostTransportState (&playing,
\r
517 &outCurrentSampleInTimeLine,
\r
519 &outCycleStartBeat,
\r
520 &outCycleEndBeat) == noErr)
\r
522 info.isPlaying = playing;
\r
523 info.timeInSeconds = outCurrentSampleInTimeLine / GetSampleRate();
\r
529 void sendAUEvent (const AudioUnitEventType type, const int index)
\r
531 if (AUEventListenerNotify != 0)
\r
533 auEvent.mEventType = type;
\r
534 auEvent.mArgument.mParameter.mParameterID = (AudioUnitParameterID) index;
\r
535 AUEventListenerNotify (0, 0, &auEvent);
\r
539 void audioProcessorParameterChanged (AudioProcessor*, int index, float /*newValue*/)
\r
541 sendAUEvent (kAudioUnitEvent_ParameterValueChange, index);
\r
544 void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int index)
\r
546 sendAUEvent (kAudioUnitEvent_BeginParameterChangeGesture, index);
\r
549 void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int index)
\r
551 sendAUEvent (kAudioUnitEvent_EndParameterChangeGesture, index);
\r
554 void audioProcessorChanged (AudioProcessor*)
\r
556 // xxx is there an AU equivalent?
\r
559 bool StreamFormatWritable (AudioUnitScope, AudioUnitElement)
\r
561 return ! IsInitialized();
\r
564 // (these two slightly different versions are because the definition changed between 10.4 and 10.5)
\r
565 ComponentResult StartNote (MusicDeviceInstrumentID, MusicDeviceGroupID, NoteInstanceID&, UInt32, const MusicDeviceNoteParams&) { return noErr; }
\r
566 ComponentResult StartNote (MusicDeviceInstrumentID, MusicDeviceGroupID, NoteInstanceID*, UInt32, const MusicDeviceNoteParams&) { return noErr; }
\r
567 ComponentResult StopNote (MusicDeviceGroupID, NoteInstanceID, UInt32) { return noErr; }
\r
569 //==============================================================================
\r
570 ComponentResult Initialize()
\r
572 #if ! JucePlugin_IsSynth
\r
573 const int numIns = GetInput(0) != 0 ? GetInput(0)->GetStreamFormat().mChannelsPerFrame : 0;
\r
575 const int numOuts = GetOutput(0) != 0 ? GetOutput(0)->GetStreamFormat().mChannelsPerFrame : 0;
\r
577 bool isValidChannelConfig = false;
\r
579 for (int i = 0; i < numChannelConfigs; ++i)
\r
580 #if JucePlugin_IsSynth
\r
581 if (numOuts == channelConfigs[i][1])
\r
583 if (numIns == channelConfigs[i][0] && numOuts == channelConfigs[i][1])
\r
585 isValidChannelConfig = true;
\r
587 if (! isValidChannelConfig)
\r
588 return kAudioUnitErr_FormatNotSupported;
\r
590 JuceAUBaseClass::Initialize();
\r
597 JuceAUBaseClass::Cleanup();
\r
599 if (juceFilter != nullptr)
\r
600 juceFilter->releaseResources();
\r
602 bufferSpace.setSize (2, 16);
\r
603 midiEvents.clear();
\r
604 incomingEvents.clear();
\r
608 ComponentResult Reset (AudioUnitScope inScope, AudioUnitElement inElement)
\r
613 if (juceFilter != nullptr)
\r
614 juceFilter->reset();
\r
616 return JuceAUBaseClass::Reset (inScope, inElement);
\r
619 void prepareToPlay()
\r
621 if (juceFilter != nullptr)
\r
623 juceFilter->setPlayConfigDetails (
\r
624 #if ! JucePlugin_IsSynth
\r
625 GetInput(0)->GetStreamFormat().mChannelsPerFrame,
\r
629 GetOutput(0)->GetStreamFormat().mChannelsPerFrame,
\r
631 GetMaxFramesPerSlice());
\r
633 bufferSpace.setSize (juceFilter->getNumInputChannels() + juceFilter->getNumOutputChannels(),
\r
634 GetMaxFramesPerSlice() + 32);
\r
636 juceFilter->prepareToPlay (GetSampleRate(), GetMaxFramesPerSlice());
\r
638 midiEvents.ensureSize (2048);
\r
639 midiEvents.clear();
\r
640 incomingEvents.ensureSize (2048);
\r
641 incomingEvents.clear();
\r
643 channels.calloc (jmax (juceFilter->getNumInputChannels(),
\r
644 juceFilter->getNumOutputChannels()) + 4);
\r
650 ComponentResult Render (AudioUnitRenderActionFlags &ioActionFlags,
\r
651 const AudioTimeStamp& inTimeStamp,
\r
654 lastSMPTETime = inTimeStamp.mSMPTETime;
\r
656 #if ! JucePlugin_IsSynth
\r
657 return JuceAUBaseClass::Render (ioActionFlags, inTimeStamp, nFrames);
\r
659 // synths can't have any inputs..
\r
660 AudioBufferList inBuffer;
\r
661 inBuffer.mNumberBuffers = 0;
\r
663 return ProcessBufferLists (ioActionFlags, inBuffer, GetOutput(0)->GetBufferList(), nFrames);
\r
667 OSStatus ProcessBufferLists (AudioUnitRenderActionFlags& ioActionFlags,
\r
668 const AudioBufferList& inBuffer,
\r
669 AudioBufferList& outBuffer,
\r
672 if (juceFilter != nullptr)
\r
674 jassert (prepared);
\r
676 int numOutChans = 0;
\r
677 int nextSpareBufferChan = 0;
\r
678 bool needToReinterleave = false;
\r
679 const int numIn = juceFilter->getNumInputChannels();
\r
680 const int numOut = juceFilter->getNumOutputChannels();
\r
683 for (i = 0; i < outBuffer.mNumberBuffers; ++i)
\r
685 AudioBuffer& buf = outBuffer.mBuffers[i];
\r
687 if (buf.mNumberChannels == 1)
\r
689 channels [numOutChans++] = (float*) buf.mData;
\r
693 needToReinterleave = true;
\r
695 for (unsigned int subChan = 0; subChan < buf.mNumberChannels && numOutChans < numOut; ++subChan)
\r
696 channels [numOutChans++] = bufferSpace.getSampleData (nextSpareBufferChan++);
\r
699 if (numOutChans >= numOut)
\r
703 int numInChans = 0;
\r
705 for (i = 0; i < inBuffer.mNumberBuffers; ++i)
\r
707 const AudioBuffer& buf = inBuffer.mBuffers[i];
\r
709 if (buf.mNumberChannels == 1)
\r
711 if (numInChans < numOutChans)
\r
712 memcpy (channels [numInChans], (const float*) buf.mData, sizeof (float) * numSamples);
\r
714 channels [numInChans] = (float*) buf.mData;
\r
720 // need to de-interleave..
\r
721 for (unsigned int subChan = 0; subChan < buf.mNumberChannels && numInChans < numIn; ++subChan)
\r
725 if (numInChans < numOutChans)
\r
727 dest = channels [numInChans++];
\r
731 dest = bufferSpace.getSampleData (nextSpareBufferChan++);
\r
732 channels [numInChans++] = dest;
\r
735 const float* src = ((const float*) buf.mData) + subChan;
\r
737 for (int j = numSamples; --j >= 0;)
\r
740 src += buf.mNumberChannels;
\r
745 if (numInChans >= numIn)
\r
750 const ScopedLock sl (incomingMidiLock);
\r
751 midiEvents.clear();
\r
752 incomingEvents.swapWith (midiEvents);
\r
756 AudioSampleBuffer buffer (channels, jmax (numIn, numOut), numSamples);
\r
758 const ScopedLock sl (juceFilter->getCallbackLock());
\r
760 if (juceFilter->isSuspended())
\r
762 for (int i = 0; i < numOut; ++i)
\r
763 zeromem (channels [i], sizeof (float) * numSamples);
\r
767 juceFilter->processBlock (buffer, midiEvents);
\r
771 if (! midiEvents.isEmpty())
\r
773 #if JucePlugin_ProducesMidiOutput
\r
774 const JUCE_NAMESPACE::uint8* midiEventData;
\r
775 int midiEventSize, midiEventPosition;
\r
776 MidiBuffer::Iterator i (midiEvents);
\r
778 while (i.getNextEvent (midiEventData, midiEventSize, midiEventPosition))
\r
780 jassert (isPositiveAndBelow (midiEventPosition, (int) numSamples));
\r
787 // if your plugin creates midi messages, you'll need to set
\r
788 // the JucePlugin_ProducesMidiOutput macro to 1 in your
\r
789 // JucePluginCharacteristics.h file
\r
790 //jassert (midiEvents.getNumEvents() <= numMidiEventsComingIn);
\r
793 midiEvents.clear();
\r
796 if (needToReinterleave)
\r
798 nextSpareBufferChan = 0;
\r
800 for (i = 0; i < outBuffer.mNumberBuffers; ++i)
\r
802 AudioBuffer& buf = outBuffer.mBuffers[i];
\r
804 if (buf.mNumberChannels > 1)
\r
806 for (unsigned int subChan = 0; subChan < buf.mNumberChannels; ++subChan)
\r
808 const float* src = bufferSpace.getSampleData (nextSpareBufferChan++);
\r
809 float* dest = ((float*) buf.mData) + subChan;
\r
811 for (int j = numSamples; --j >= 0;)
\r
814 dest += buf.mNumberChannels;
\r
821 #if ! JucePlugin_SilenceInProducesSilenceOut
\r
822 ioActionFlags &= ~kAudioUnitRenderAction_OutputIsSilence;
\r
830 OSStatus HandleMidiEvent (UInt8 nStatus, UInt8 inChannel, UInt8 inData1, UInt8 inData2,
\r
831 #if defined (MAC_OS_X_VERSION_10_5)
\r
832 UInt32 inStartFrame)
\r
837 #if JucePlugin_WantsMidiInput
\r
838 const ScopedLock sl (incomingMidiLock);
\r
839 const JUCE_NAMESPACE::uint8 data[] = { (JUCE_NAMESPACE::uint8) (nStatus | inChannel),
\r
840 (JUCE_NAMESPACE::uint8) inData1,
\r
841 (JUCE_NAMESPACE::uint8) inData2 };
\r
843 incomingEvents.addEvent (data, 3, inStartFrame);
\r
849 OSStatus HandleSysEx (const UInt8* inData, UInt32 inLength)
\r
851 #if JucePlugin_WantsMidiInput
\r
852 const ScopedLock sl (incomingMidiLock);
\r
853 incomingEvents.addEvent (inData, inLength, 0);
\r
858 //==============================================================================
\r
859 ComponentResult GetPresets (CFArrayRef* outData) const
\r
861 if (outData != nullptr)
\r
863 const int numPrograms = juceFilter->getNumPrograms();
\r
864 presetsArray.ensureSize (sizeof (AUPreset) * numPrograms, true);
\r
865 AUPreset* const presets = (AUPreset*) presetsArray.getData();
\r
867 CFMutableArrayRef presetsArray = CFArrayCreateMutable (0, numPrograms, 0);
\r
869 for (int i = 0; i < numPrograms; ++i)
\r
871 presets[i].presetNumber = i;
\r
872 presets[i].presetName = PlatformUtilities::juceStringToCFString (juceFilter->getProgramName (i));
\r
874 CFArrayAppendValue (presetsArray, presets + i);
\r
877 *outData = (CFArrayRef) presetsArray;
\r
883 OSStatus NewFactoryPresetSet (const AUPreset& inNewFactoryPreset)
\r
885 const int numPrograms = juceFilter->getNumPrograms();
\r
886 const SInt32 chosenPresetNumber = (int) inNewFactoryPreset.presetNumber;
\r
888 if (chosenPresetNumber >= numPrograms)
\r
889 return kAudioUnitErr_InvalidProperty;
\r
891 AUPreset chosenPreset;
\r
892 chosenPreset.presetNumber = chosenPresetNumber;
\r
893 chosenPreset.presetName = PlatformUtilities::juceStringToCFString (juceFilter->getProgramName (chosenPresetNumber));
\r
895 juceFilter->setCurrentProgram (chosenPresetNumber);
\r
896 SetAFactoryPresetAsCurrent (chosenPreset);
\r
901 void componentMovedOrResized (Component& component, bool /*wasMoved*/, bool /*wasResized*/)
\r
903 NSView* view = (NSView*) component.getWindowHandle();
\r
904 NSRect r = [[view superview] frame];
\r
905 r.origin.y = r.origin.y + r.size.height - component.getHeight();
\r
906 r.size.width = component.getWidth();
\r
907 r.size.height = component.getHeight();
\r
908 [[view superview] setFrame: r];
\r
909 [view setFrame: NSMakeRect (0, 0, component.getWidth(), component.getHeight())];
\r
910 [view setNeedsDisplay: YES];
\r
914 //==============================================================================
\r
915 ScopedPointer<AudioProcessor> juceFilter;
\r
916 AudioSampleBuffer bufferSpace;
\r
917 HeapBlock <float*> channels;
\r
918 MidiBuffer midiEvents, incomingEvents;
\r
920 SMPTETime lastSMPTETime;
\r
921 AUChannelInfo channelInfo [numChannelConfigs];
\r
922 AudioUnitEvent auEvent;
\r
923 mutable MemoryBlock presetsArray;
\r
924 CriticalSection incomingMidiLock;
\r
926 JUCE_DECLARE_NON_COPYABLE (JuceAU);
\r
929 //==============================================================================
\r
930 class EditorCompHolder : public Component
\r
933 EditorCompHolder (AudioProcessorEditor* const editor)
\r
935 setSize (editor->getWidth(), editor->getHeight());
\r
936 addAndMakeVisible (editor);
\r
938 #if ! JucePlugin_EditorRequiresKeyboardFocus
\r
939 setWantsKeyboardFocus (false);
\r
941 setWantsKeyboardFocus (true);
\r
945 ~EditorCompHolder()
\r
947 deleteAllChildren(); // note that we can't use a ScopedPointer because the editor may
\r
948 // have been transferred to another parent which takes over ownership.
\r
951 void childBoundsChanged (Component*)
\r
953 Component* editor = getChildComponent(0);
\r
955 if (editor != nullptr)
\r
957 const int w = jmax (32, editor->getWidth());
\r
958 const int h = jmax (32, editor->getHeight());
\r
960 if (getWidth() != w || getHeight() != h)
\r
963 NSView* view = (NSView*) getWindowHandle();
\r
964 NSRect r = [[view superview] frame];
\r
965 r.size.width = editor->getWidth();
\r
966 r.size.height = editor->getHeight();
\r
967 [[view superview] setFrame: r];
\r
968 [view setFrame: NSMakeRect (0, 0, editor->getWidth(), editor->getHeight())];
\r
969 [view setNeedsDisplay: YES];
\r
974 JUCE_DECLARE_NON_COPYABLE (EditorCompHolder);
\r
977 //==============================================================================
\r
978 @implementation JuceUIViewClass
\r
980 - (JuceUIViewClass*) initWithFilter: (AudioProcessor*) filter_
\r
981 withAU: (JuceAU*) au_
\r
982 withComponent: (AudioProcessorEditor*) editorComp_
\r
986 editorComp = new EditorCompHolder (editorComp_);
\r
988 [super initWithFrame: NSMakeRect (0, 0, editorComp_->getWidth(), editorComp_->getHeight())];
\r
989 [self setHidden: NO];
\r
990 [self setPostsFrameChangedNotifications: YES];
\r
992 [[NSNotificationCenter defaultCenter] addObserver: self
\r
993 selector: @selector (applicationWillTerminate:)
\r
994 name: NSApplicationWillTerminateNotification
\r
996 activeUIs.add (self);
\r
998 editorComp->addToDesktop (0, (void*) self);
\r
999 editorComp->setVisible (true);
\r
1006 if (activeUIs.contains (self))
\r
1012 - (void) applicationWillTerminate: (NSNotification*) aNotification
\r
1014 (void) aNotification;
\r
1020 // there's some kind of component currently modal, but the host
\r
1021 // is trying to delete our plugin..
\r
1022 jassert (Component::getCurrentlyModalComponent() == nullptr);
\r
1024 [[NSNotificationCenter defaultCenter] removeObserver: self];
\r
1025 [self deleteEditor];
\r
1027 jassert (activeUIs.contains (self));
\r
1028 activeUIs.removeValue (self);
\r
1029 if (activePlugins.size() + activeUIs.size() == 0)
\r
1030 shutdownJuce_GUI();
\r
1033 - (void) viewDidMoveToWindow
\r
1035 if ([self window] != nil)
\r
1037 [[self window] setAcceptsMouseMovedEvents: YES];
\r
1039 if (editorComp != nullptr)
\r
1040 [[self window] makeFirstResponder: (NSView*) editorComp->getWindowHandle()];
\r
1044 - (BOOL) mouseDownCanMoveWindow
\r
1049 - (void) deleteEditor
\r
1051 if (editorComp != nullptr)
\r
1053 if (editorComp->getChildComponent(0) != nullptr)
\r
1054 if (activePlugins.contains ((void*) au)) // plugin may have been deleted before the UI
\r
1055 filter->editorBeingDeleted ((AudioProcessorEditor*) editorComp->getChildComponent(0));
\r
1057 deleteAndZero (editorComp);
\r
1060 editorComp = nullptr;
\r
1063 - (void) filterBeingDeleted: (JuceAU*) au_
\r
1066 [self deleteEditor];
\r
1071 //==============================================================================
\r
1072 @implementation JuceUICreationClass
\r
1074 - (JuceUICreationClass*) init
\r
1076 return [super init];
\r
1084 - (unsigned) interfaceVersion
\r
1089 - (NSString*) description
\r
1091 return [NSString stringWithString: @JucePlugin_Name];
\r
1094 - (NSView*) uiViewForAudioUnit: (AudioUnit) inAudioUnit
\r
1095 withSize: (NSSize) inPreferredSize
\r
1097 void* pointers[2];
\r
1098 UInt32 propertySize = sizeof (pointers);
\r
1100 if (AudioUnitGetProperty (inAudioUnit,
\r
1101 juceFilterObjectPropertyID,
\r
1102 kAudioUnitScope_Global,
\r
1105 &propertySize) != noErr)
\r
1108 AudioProcessor* filter = (AudioProcessor*) pointers[0];
\r
1109 JuceAU* au = (JuceAU*) pointers[1];
\r
1111 if (filter == nullptr)
\r
1114 AudioProcessorEditor* editorComp = filter->createEditorIfNeeded();
\r
1116 if (editorComp == nullptr)
\r
1119 return [[[JuceUIViewClass alloc] initWithFilter: filter
\r
1121 withComponent: editorComp] autorelease];
\r
1126 #if BUILD_AU_CARBON_UI
\r
1128 //==============================================================================
\r
1129 class JuceAUView : public AUCarbonViewBase
\r
1132 JuceAUView (AudioUnitCarbonView auview)
\r
1133 : AUCarbonViewBase (auview),
\r
1134 juceFilter (nullptr)
\r
1143 ComponentResult CreateUI (Float32 /*inXOffset*/, Float32 /*inYOffset*/)
\r
1145 JUCE_AUTORELEASEPOOL
\r
1147 if (juceFilter == nullptr)
\r
1149 void* pointers[2];
\r
1150 UInt32 propertySize = sizeof (pointers);
\r
1152 AudioUnitGetProperty (GetEditAudioUnit(),
\r
1153 juceFilterObjectPropertyID,
\r
1154 kAudioUnitScope_Global,
\r
1159 juceFilter = (AudioProcessor*) pointers[0];
\r
1162 if (juceFilter != nullptr)
\r
1166 AudioProcessorEditor* editorComp = juceFilter->createEditorIfNeeded();
\r
1167 editorComp->setOpaque (true);
\r
1168 windowComp = new ComponentInHIView (editorComp, mCarbonPane);
\r
1172 jassertfalse // can't get a pointer to our effect
\r
1178 AudioUnitCarbonViewEventListener getEventListener() const { return mEventListener; }
\r
1179 void* getEventListenerUserData() const { return mEventListenerUserData; }
\r
1182 //==============================================================================
\r
1183 AudioProcessor* juceFilter;
\r
1184 ScopedPointer<Component> windowComp;
\r
1185 FakeMouseMoveGenerator fakeMouseGenerator;
\r
1189 if (windowComp != nullptr)
\r
1191 PopupMenu::dismissAllActiveMenus();
\r
1193 /* This assertion is triggered when there's some kind of modal component active, and the
\r
1194 host is trying to delete our plugin.
\r
1195 If you must use modal components, always use them in a non-blocking way, by never
\r
1196 calling runModalLoop(), but instead using enterModalState() with a callback that
\r
1197 will be performed on completion. (Note that this assertion could actually trigger
\r
1198 a false alarm even if you're doing it correctly, but is here to catch people who
\r
1199 aren't so careful) */
\r
1200 jassert (Component::getCurrentlyModalComponent() == nullptr);
\r
1202 if (windowComp != nullptr && windowComp->getChildComponent(0) != nullptr)
\r
1203 juceFilter->editorBeingDeleted ((AudioProcessorEditor*) windowComp->getChildComponent(0));
\r
1205 windowComp = nullptr;
\r
1209 //==============================================================================
\r
1210 // Uses a child NSWindow to sit in front of a HIView and display our component
\r
1211 class ComponentInHIView : public Component
\r
1214 //==============================================================================
\r
1215 ComponentInHIView (AudioProcessorEditor* const editor_, HIViewRef parentHIView)
\r
1216 : parentView (parentHIView),
\r
1220 JUCE_AUTORELEASEPOOL
\r
1222 jassert (editor_ != nullptr);
\r
1223 addAndMakeVisible (&editor);
\r
1225 setVisible (true);
\r
1226 setBroughtToFrontOnMouseClick (true);
\r
1228 setSize (editor.getWidth(), editor.getHeight());
\r
1229 SizeControl (parentHIView, editor.getWidth(), editor.getHeight());
\r
1231 WindowRef windowRef = HIViewGetWindow (parentHIView);
\r
1232 hostWindow = [[NSWindow alloc] initWithWindowRef: windowRef];
\r
1234 [hostWindow retain];
\r
1235 [hostWindow setCanHide: YES];
\r
1236 [hostWindow setReleasedWhenClosed: YES];
\r
1238 updateWindowPos();
\r
1240 #if ! JucePlugin_EditorRequiresKeyboardFocus
\r
1241 addToDesktop (ComponentPeer::windowIsTemporary | ComponentPeer::windowIgnoresKeyPresses);
\r
1242 setWantsKeyboardFocus (false);
\r
1244 addToDesktop (ComponentPeer::windowIsTemporary);
\r
1245 setWantsKeyboardFocus (true);
\r
1248 setVisible (true);
\r
1253 NSWindow* pluginWindow = [((NSView*) getWindowHandle()) window];
\r
1254 [pluginWindow setNextResponder: hostWindow];
\r
1256 attachWindowHidingHooks (this, (WindowRef) windowRef, hostWindow);
\r
1259 ~ComponentInHIView()
\r
1261 JUCE_AUTORELEASEPOOL
\r
1263 removeWindowHidingHooks (this);
\r
1265 NSWindow* pluginWindow = [((NSView*) getWindowHandle()) window];
\r
1266 [hostWindow removeChildWindow: pluginWindow];
\r
1267 removeFromDesktop();
\r
1269 [hostWindow release];
\r
1273 void updateWindowPos()
\r
1277 HIPointConvert (&f, kHICoordSpaceView, parentView, kHICoordSpaceScreenPixel, 0);
\r
1278 setTopLeftPosition ((int) f.x, (int) f.y);
\r
1281 void addSubWindow()
\r
1283 NSWindow* pluginWindow = [((NSView*) getWindowHandle()) window];
\r
1284 [pluginWindow setExcludedFromWindowsMenu: YES];
\r
1285 [pluginWindow setCanHide: YES];
\r
1287 [hostWindow addChildWindow: pluginWindow
\r
1288 ordered: NSWindowAbove];
\r
1289 [hostWindow orderFront: nil];
\r
1290 [pluginWindow orderFront: nil];
\r
1295 Component* const child = getChildComponent (0);
\r
1296 if (child != nullptr)
\r
1297 child->setBounds (getLocalBounds());
\r
1300 void paint (Graphics&) {}
\r
1302 void childBoundsChanged (Component*)
\r
1308 const int w = jmax (32, editor.getWidth());
\r
1309 const int h = jmax (32, editor.getHeight());
\r
1311 SizeControl (parentView, w, h);
\r
1313 if (getWidth() != w || getHeight() != h)
\r
1318 updateWindowPos();
\r
1319 addSubWindow(); // (need this for AULab)
\r
1321 recursive = false;
\r
1325 bool keyPressed (const KeyPress& kp)
\r
1327 if (! kp.getModifiers().isCommandDown())
\r
1329 // If we have an unused keypress, move the key-focus to a host window
\r
1330 // and re-inject the event..
\r
1331 static NSTimeInterval lastEventTime = 0; // check we're not recursively sending the same event
\r
1333 if (lastEventTime != [[NSApp currentEvent] timestamp])
\r
1335 lastEventTime = [[NSApp currentEvent] timestamp];
\r
1336 [[hostWindow parentWindow] makeKeyWindow];
\r
1337 [NSApp postEvent: [NSApp currentEvent] atStart: YES];
\r
1345 //==============================================================================
\r
1346 HIViewRef parentView;
\r
1347 NSWindow* hostWindow;
\r
1348 EditorCompHolder editor;
\r
1355 //==============================================================================
\r
1356 #define JUCE_COMPONENT_ENTRYX(Class, Name, Suffix) \
\r
1357 extern "C" __attribute__((visibility("default"))) ComponentResult Name ## Suffix (ComponentParameters* params, Class* obj); \
\r
1358 extern "C" __attribute__((visibility("default"))) ComponentResult Name ## Suffix (ComponentParameters* params, Class* obj) \
\r
1360 return ComponentEntryPoint<Class>::Dispatch(params, obj); \
\r
1363 #define JUCE_COMPONENT_ENTRY(Class, Name, Suffix) JUCE_COMPONENT_ENTRYX(Class, Name, Suffix)
\r
1365 JUCE_COMPONENT_ENTRY (JuceAU, JucePlugin_AUExportPrefix, Entry)
\r
1367 #if BUILD_AU_CARBON_UI
\r
1368 JUCE_COMPONENT_ENTRY (JuceAUView, JucePlugin_AUExportPrefix, ViewEntry)
\r