1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2017 - ROLI Ltd.
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    The code included in this file is provided under the terms of the ISC license
11    http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12    To use, copy, modify, and/or distribute this software for any purpose with or
13    without fee is hereby granted provided that the above copyright notice and
14    this permission notice appear in all copies.
15 
16    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18    DISCLAIMED.
19 
20   ==============================================================================
21 */
22 
23 namespace juce
24 {
25 
26 class iOSAudioIODevice;
27 
28 static const char* const iOSAudioDeviceName = "iOS Audio";
29 
30 //==============================================================================
31 struct AudioSessionHolder
32 {
33     AudioSessionHolder();
34     ~AudioSessionHolder();
35 
36     void handleStatusChange (bool enabled, const char* reason) const;
37     void handleRouteChange (AVAudioSessionRouteChangeReason reason);
38 
39     Array<iOSAudioIODevice::Pimpl*> activeDevices;
40     Array<iOSAudioIODeviceType*> activeDeviceTypes;
41 
42     id nativeSession;
43 };
44 
getRoutingChangeReason(AVAudioSessionRouteChangeReason reason)45 static const char* getRoutingChangeReason (AVAudioSessionRouteChangeReason reason) noexcept
46 {
47     switch (reason)
48     {
49         case AVAudioSessionRouteChangeReasonNewDeviceAvailable:         return "New device available";
50         case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:       return "Old device unavailable";
51         case AVAudioSessionRouteChangeReasonCategoryChange:             return "Category change";
52         case AVAudioSessionRouteChangeReasonOverride:                   return "Override";
53         case AVAudioSessionRouteChangeReasonWakeFromSleep:              return "Wake from sleep";
54         case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory: return "No suitable route for category";
55         case AVAudioSessionRouteChangeReasonRouteConfigurationChange:   return "Route configuration change";
56         case AVAudioSessionRouteChangeReasonUnknown:
57         default:                                                        return "Unknown";
58     }
59 }
60 
getNotificationValueForKey(NSNotification * notification,NSString * key,NSUInteger & value)61 bool getNotificationValueForKey (NSNotification* notification, NSString* key, NSUInteger& value) noexcept
62 {
63     if (notification != nil)
64     {
65         if (NSDictionary* userInfo = [notification userInfo])
66         {
67             if (NSNumber* number = [userInfo objectForKey: key])
68             {
69                 value = [number unsignedIntegerValue];
70                 return true;
71             }
72         }
73     }
74 
75     jassertfalse;
76     return false;
77 }
78 
79 } // juce namespace
80 
81 //==============================================================================
82 @interface iOSAudioSessionNative  : NSObject
83 {
84 @private
85     juce::AudioSessionHolder* audioSessionHolder;
86 };
87 
88 - (id) init: (juce::AudioSessionHolder*) holder;
89 - (void) dealloc;
90 
91 - (void) audioSessionChangedInterruptionType: (NSNotification*) notification;
92 - (void) handleMediaServicesReset;
93 - (void) handleMediaServicesLost;
94 - (void) handleRouteChange: (NSNotification*) notification;
95 @end
96 
97 @implementation iOSAudioSessionNative
98 
99 - (id) init: (juce::AudioSessionHolder*) holder
100 {
101     self = [super init];
102 
103     if (self != nil)
104     {
105         audioSessionHolder = holder;
106 
107         auto session = [AVAudioSession sharedInstance];
108         auto centre = [NSNotificationCenter defaultCenter];
109 
110         [centre addObserver: self
111                    selector: @selector (audioSessionChangedInterruptionType:)
112                        name: AVAudioSessionInterruptionNotification
113                      object: session];
114 
115         [centre addObserver: self
116                    selector: @selector (handleMediaServicesLost)
117                        name: AVAudioSessionMediaServicesWereLostNotification
118                      object: session];
119 
120         [centre addObserver: self
121                    selector: @selector (handleMediaServicesReset)
122                        name: AVAudioSessionMediaServicesWereResetNotification
123                      object: session];
124 
125         [centre addObserver: self
126                    selector: @selector (handleRouteChange:)
127                        name: AVAudioSessionRouteChangeNotification
128                      object: session];
129     }
130     else
131     {
132         jassertfalse;
133     }
134 
135     return self;
136 }
137 
138 - (void) dealloc
139 {
140     [[NSNotificationCenter defaultCenter] removeObserver: self];
141     [super dealloc];
142 }
143 
144 - (void) audioSessionChangedInterruptionType: (NSNotification*) notification
145 {
146     NSUInteger value;
147 
148     if (juce::getNotificationValueForKey (notification, AVAudioSessionInterruptionTypeKey, value))
149     {
150         switch ((AVAudioSessionInterruptionType) value)
151         {
152             case AVAudioSessionInterruptionTypeBegan:
153                 audioSessionHolder->handleStatusChange (false, "AVAudioSessionInterruptionTypeBegan");
154                 break;
155 
156             case AVAudioSessionInterruptionTypeEnded:
157                 audioSessionHolder->handleStatusChange (true, "AVAudioSessionInterruptionTypeEnded");
158                 break;
159 
160             // No default so the code doesn't compile if this enum is extended.
161         }
162     }
163 }
164 
165 - (void) handleMediaServicesReset
166 {
167     audioSessionHolder->handleStatusChange (true, "AVAudioSessionMediaServicesWereResetNotification");
168 }
169 
170 - (void) handleMediaServicesLost
171 {
172     audioSessionHolder->handleStatusChange (false, "AVAudioSessionMediaServicesWereLostNotification");
173 }
174 
175 - (void) handleRouteChange: (NSNotification*) notification
176 {
177     NSUInteger value;
178 
179     if (juce::getNotificationValueForKey (notification, AVAudioSessionRouteChangeReasonKey, value))
180         audioSessionHolder->handleRouteChange ((AVAudioSessionRouteChangeReason) value);
181 }
182 
183 @end
184 
185 //==============================================================================
186 #if JUCE_MODULE_AVAILABLE_juce_graphics
187  #include <juce_graphics/native/juce_mac_CoreGraphicsHelpers.h>
188 #endif
189 
190 namespace juce {
191 
192 #ifndef JUCE_IOS_AUDIO_LOGGING
193  #define JUCE_IOS_AUDIO_LOGGING 0
194 #endif
195 
196 #if JUCE_IOS_AUDIO_LOGGING
197  #define JUCE_IOS_AUDIO_LOG(x)  DBG(x)
198 #else
199  #define JUCE_IOS_AUDIO_LOG(x)
200 #endif
201 
logNSError(NSError * e)202 static void logNSError (NSError* e)
203 {
204     if (e != nil)
205     {
206         JUCE_IOS_AUDIO_LOG ("iOS Audio error: " << [e.localizedDescription UTF8String]);
207         jassertfalse;
208     }
209 }
210 
211 #define JUCE_NSERROR_CHECK(X)     { NSError* error = nil; X; logNSError (error); }
212 
213 //==============================================================================
214 class iOSAudioIODeviceType  : public AudioIODeviceType,
215                               public AsyncUpdater
216 {
217 public:
218     iOSAudioIODeviceType();
219     ~iOSAudioIODeviceType() override;
220 
221     void scanForDevices() override;
222     StringArray getDeviceNames (bool) const override;
223     int getDefaultDeviceIndex (bool) const override;
224     int getIndexOfDevice (AudioIODevice*, bool) const override;
225     bool hasSeparateInputsAndOutputs() const override;
226     AudioIODevice* createDevice (const String&, const String&) override;
227 
228 private:
229     void handleRouteChange (AVAudioSessionRouteChangeReason);
230 
231     void handleAsyncUpdate() override;
232 
233     friend struct AudioSessionHolder;
234     friend struct iOSAudioIODevice::Pimpl;
235 
236     SharedResourcePointer<AudioSessionHolder> sessionHolder;
237 
238     JUCE_DECLARE_WEAK_REFERENCEABLE (iOSAudioIODeviceType)
239     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSAudioIODeviceType)
240 };
241 
242 //==============================================================================
243 struct iOSAudioIODevice::Pimpl      : public AudioPlayHead,
244                                       public AsyncUpdater
245 {
Pimpljuce::iOSAudioIODevice::Pimpl246     Pimpl (iOSAudioIODeviceType* ioDeviceType, iOSAudioIODevice& ioDevice)
247         : deviceType (ioDeviceType),
248           owner (ioDevice)
249     {
250         JUCE_IOS_AUDIO_LOG ("Creating iOS audio device");
251 
252         // We need to activate the audio session here to obtain the available sample rates and buffer sizes,
253         // but if we don't set a category first then background audio will always be stopped. This category
254         // may be changed later.
255         setAudioSessionCategory (AVAudioSessionCategoryPlayAndRecord);
256 
257         setAudioSessionActive (true);
258         updateHardwareInfo();
259         channelData.reconfigure ({}, {});
260         setAudioSessionActive (false);
261 
262         sessionHolder->activeDevices.add (this);
263     }
264 
~Pimpljuce::iOSAudioIODevice::Pimpl265     ~Pimpl() override
266     {
267         sessionHolder->activeDevices.removeFirstMatchingValue (this);
268 
269         close();
270     }
271 
setAudioSessionCategoryjuce::iOSAudioIODevice::Pimpl272     static void setAudioSessionCategory (NSString* category)
273     {
274         NSUInteger options = 0;
275 
276        #if ! JUCE_DISABLE_AUDIO_MIXING_WITH_OTHER_APPS
277         options |= AVAudioSessionCategoryOptionMixWithOthers; // Alternatively AVAudioSessionCategoryOptionDuckOthers
278        #endif
279 
280         if (category == AVAudioSessionCategoryPlayAndRecord)
281             options |= (AVAudioSessionCategoryOptionDefaultToSpeaker
282                       | AVAudioSessionCategoryOptionAllowBluetooth
283                       | AVAudioSessionCategoryOptionAllowBluetoothA2DP);
284 
285         JUCE_NSERROR_CHECK ([[AVAudioSession sharedInstance] setCategory: category
286                                                              withOptions: options
287                                                                    error: &error]);
288     }
289 
setAudioSessionActivejuce::iOSAudioIODevice::Pimpl290     static void setAudioSessionActive (bool enabled)
291     {
292         JUCE_NSERROR_CHECK ([[AVAudioSession sharedInstance] setActive: enabled
293                                                                  error: &error]);
294     }
295 
getBufferSizejuce::iOSAudioIODevice::Pimpl296     int getBufferSize (const double currentSampleRate)
297     {
298         return roundToInt (currentSampleRate * [AVAudioSession sharedInstance].IOBufferDuration);
299     }
300 
tryBufferSizejuce::iOSAudioIODevice::Pimpl301     int tryBufferSize (const double currentSampleRate, const int newBufferSize)
302     {
303         NSTimeInterval bufferDuration = currentSampleRate > 0 ? (NSTimeInterval) ((newBufferSize + 1) / currentSampleRate) : 0.0;
304 
305         auto session = [AVAudioSession sharedInstance];
306         JUCE_NSERROR_CHECK ([session setPreferredIOBufferDuration: bufferDuration
307                                                             error: &error]);
308 
309         return getBufferSize (currentSampleRate);
310     }
311 
updateAvailableBufferSizesjuce::iOSAudioIODevice::Pimpl312     void updateAvailableBufferSizes()
313     {
314         availableBufferSizes.clear();
315 
316         auto newBufferSize = tryBufferSize (sampleRate, 64);
317         jassert (newBufferSize > 0);
318 
319         const auto longestBufferSize  = tryBufferSize (sampleRate, 4096);
320 
321         while (newBufferSize <= longestBufferSize)
322         {
323             availableBufferSizes.add (newBufferSize);
324             newBufferSize *= 2;
325         }
326 
327         // Sometimes the largest supported buffer size is not a power of 2
328         availableBufferSizes.addIfNotAlreadyThere (longestBufferSize);
329 
330         bufferSize = tryBufferSize (sampleRate, bufferSize);
331 
332        #if JUCE_IOS_AUDIO_LOGGING
333         {
334             String info ("Available buffer sizes:");
335 
336             for (auto size : availableBufferSizes)
337                 info << " " << size;
338 
339             JUCE_IOS_AUDIO_LOG (info);
340         }
341        #endif
342 
343         JUCE_IOS_AUDIO_LOG ("Buffer size after detecting available buffer sizes: " << bufferSize);
344     }
345 
trySampleRatejuce::iOSAudioIODevice::Pimpl346     double trySampleRate (double rate)
347     {
348         auto session = [AVAudioSession sharedInstance];
349         JUCE_NSERROR_CHECK ([session setPreferredSampleRate: rate
350                                                       error: &error]);
351 
352         return session.sampleRate;
353     }
354 
355     // Important: the supported audio sample rates change on the iPhone 6S
356     // depending on whether the headphones are plugged in or not!
updateAvailableSampleRatesjuce::iOSAudioIODevice::Pimpl357     void updateAvailableSampleRates()
358     {
359         availableSampleRates.clear();
360 
361         AudioUnitRemovePropertyListenerWithUserData (audioUnit,
362                                                      kAudioUnitProperty_StreamFormat,
363                                                      dispatchAudioUnitPropertyChange,
364                                                      this);
365 
366         const double lowestRate = trySampleRate (4000);
367         availableSampleRates.add (lowestRate);
368         const double highestRate = trySampleRate (192000);
369 
370         JUCE_IOS_AUDIO_LOG ("Lowest supported sample rate: "  << lowestRate);
371         JUCE_IOS_AUDIO_LOG ("Highest supported sample rate: " << highestRate);
372 
373         for (double rate = lowestRate + 1000; rate < highestRate; rate += 1000)
374         {
375             const double supportedRate = trySampleRate (rate);
376             JUCE_IOS_AUDIO_LOG ("Trying a sample rate of " << rate << ", got " << supportedRate);
377             availableSampleRates.addIfNotAlreadyThere (supportedRate);
378             rate = jmax (rate, supportedRate);
379         }
380 
381         availableSampleRates.addIfNotAlreadyThere (highestRate);
382 
383         // Restore the original values.
384         sampleRate = trySampleRate (sampleRate);
385         bufferSize = tryBufferSize (sampleRate, bufferSize);
386 
387         AudioUnitAddPropertyListener (audioUnit,
388                                       kAudioUnitProperty_StreamFormat,
389                                       dispatchAudioUnitPropertyChange,
390                                       this);
391 
392         // Check the current stream format in case things have changed whilst we
393         // were going through the sample rates
394         handleStreamFormatChange();
395 
396        #if JUCE_IOS_AUDIO_LOGGING
397         {
398             String info ("Available sample rates:");
399 
400             for (auto rate : availableSampleRates)
401                 info << " " << rate;
402 
403             JUCE_IOS_AUDIO_LOG (info);
404         }
405        #endif
406 
407         JUCE_IOS_AUDIO_LOG ("Sample rate after detecting available sample rates: " << sampleRate);
408     }
409 
updateHardwareInfojuce::iOSAudioIODevice::Pimpl410     void updateHardwareInfo (bool forceUpdate = false)
411     {
412         if (! forceUpdate && ! hardwareInfoNeedsUpdating.compareAndSetBool (false, true))
413             return;
414 
415         JUCE_IOS_AUDIO_LOG ("Updating hardware info");
416 
417         updateAvailableSampleRates();
418         updateAvailableBufferSizes();
419 
420         if (deviceType != nullptr)
421             deviceType->callDeviceChangeListeners();
422     }
423 
setTargetSampleRateAndBufferSizejuce::iOSAudioIODevice::Pimpl424     void setTargetSampleRateAndBufferSize()
425     {
426         JUCE_IOS_AUDIO_LOG ("Setting target sample rate: " << targetSampleRate);
427         sampleRate = trySampleRate (targetSampleRate);
428         JUCE_IOS_AUDIO_LOG ("Actual sample rate: " << sampleRate);
429 
430         JUCE_IOS_AUDIO_LOG ("Setting target buffer size: " << targetBufferSize);
431         bufferSize = tryBufferSize (sampleRate, targetBufferSize);
432         JUCE_IOS_AUDIO_LOG ("Actual buffer size: " << bufferSize);
433     }
434 
openjuce::iOSAudioIODevice::Pimpl435     String open (const BigInteger& inputChannelsWanted,
436                  const BigInteger& outputChannelsWanted,
437                  double sampleRateWanted, int bufferSizeWanted)
438     {
439         close();
440 
441         firstHostTime = true;
442         lastNumFrames = 0;
443         xrun = 0;
444         lastError.clear();
445 
446         requestedInputChannels  = inputChannelsWanted;
447         requestedOutputChannels = outputChannelsWanted;
448         targetSampleRate = sampleRateWanted;
449         targetBufferSize = bufferSizeWanted > 0 ? bufferSizeWanted : defaultBufferSize;
450 
451         JUCE_IOS_AUDIO_LOG ("Opening audio device:"
452                             <<  " inputChannelsWanted: "  << requestedInputChannels .toString (2)
453                             << ", outputChannelsWanted: " << requestedOutputChannels.toString (2)
454                             << ", targetSampleRate: " << targetSampleRate
455                             << ", targetBufferSize: " << targetBufferSize);
456 
457         setAudioSessionActive (true);
458         setAudioSessionCategory (requestedInputChannels > 0 ? AVAudioSessionCategoryPlayAndRecord
459                                                             : AVAudioSessionCategoryPlayback);
460         channelData.reconfigure (requestedInputChannels, requestedOutputChannels);
461         updateHardwareInfo (true);
462         setTargetSampleRateAndBufferSize();
463         fixAudioRouteIfSetToReceiver();
464 
465         isRunning = true;
466 
467         if (! createAudioUnit())
468         {
469             lastError = "Couldn't open the device";
470             return lastError;
471         }
472 
473         const ScopedLock sl (callbackLock);
474 
475         AudioOutputUnitStart (audioUnit);
476 
477         if (callback != nullptr)
478             callback->audioDeviceAboutToStart (&owner);
479 
480         return lastError;
481     }
482 
closejuce::iOSAudioIODevice::Pimpl483     void close()
484     {
485         stop();
486 
487         if (isRunning)
488         {
489             isRunning = false;
490 
491             if (audioUnit != nullptr)
492             {
493                 AudioOutputUnitStart (audioUnit);
494                 AudioComponentInstanceDispose (audioUnit);
495                 audioUnit = nullptr;
496             }
497 
498             setAudioSessionActive (false);
499         }
500     }
501 
startjuce::iOSAudioIODevice::Pimpl502     void start (AudioIODeviceCallback* newCallback)
503     {
504         if (isRunning && callback != newCallback)
505         {
506             if (newCallback != nullptr)
507                 newCallback->audioDeviceAboutToStart (&owner);
508 
509             const ScopedLock sl (callbackLock);
510             callback = newCallback;
511         }
512     }
513 
stopjuce::iOSAudioIODevice::Pimpl514     void stop()
515     {
516         if (isRunning)
517         {
518             AudioIODeviceCallback* lastCallback;
519 
520             {
521                 const ScopedLock sl (callbackLock);
522                 lastCallback = callback;
523                 callback = nullptr;
524             }
525 
526             if (lastCallback != nullptr)
527                 lastCallback->audioDeviceStopped();
528         }
529     }
530 
setAudioPreprocessingEnabledjuce::iOSAudioIODevice::Pimpl531     bool setAudioPreprocessingEnabled (bool enable)
532     {
533         auto session = [AVAudioSession sharedInstance];
534 
535         NSString* mode = (enable ? AVAudioSessionModeDefault
536                                  : AVAudioSessionModeMeasurement);
537 
538         JUCE_NSERROR_CHECK ([session setMode: mode
539                                        error: &error]);
540 
541         return session.mode == mode;
542     }
543 
544     //==============================================================================
canControlTransportjuce::iOSAudioIODevice::Pimpl545     bool canControlTransport() override                    { return interAppAudioConnected; }
546 
transportPlayjuce::iOSAudioIODevice::Pimpl547     void transportPlay (bool shouldSartPlaying) override
548     {
549         if (! canControlTransport())
550             return;
551 
552         HostCallbackInfo callbackInfo;
553         fillHostCallbackInfo (callbackInfo);
554 
555         Boolean hostIsPlaying = NO;
556         OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData,
557                                                          &hostIsPlaying,
558                                                          nullptr,
559                                                          nullptr,
560                                                          nullptr,
561                                                          nullptr,
562                                                          nullptr,
563                                                          nullptr);
564 
565         ignoreUnused (err);
566         jassert (err == noErr);
567 
568         if (hostIsPlaying != shouldSartPlaying)
569             handleAudioTransportEvent (kAudioUnitRemoteControlEvent_TogglePlayPause);
570     }
571 
transportRecordjuce::iOSAudioIODevice::Pimpl572     void transportRecord (bool shouldStartRecording) override
573     {
574         if (! canControlTransport())
575             return;
576 
577         HostCallbackInfo callbackInfo;
578         fillHostCallbackInfo (callbackInfo);
579 
580         Boolean hostIsRecording = NO;
581         OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData,
582                                                          nullptr,
583                                                          &hostIsRecording,
584                                                          nullptr,
585                                                          nullptr,
586                                                          nullptr,
587                                                          nullptr,
588                                                          nullptr);
589         ignoreUnused (err);
590         jassert (err == noErr);
591 
592         if (hostIsRecording != shouldStartRecording)
593             handleAudioTransportEvent (kAudioUnitRemoteControlEvent_ToggleRecord);
594     }
595 
transportRewindjuce::iOSAudioIODevice::Pimpl596     void transportRewind() override
597     {
598         if (canControlTransport())
599             handleAudioTransportEvent (kAudioUnitRemoteControlEvent_Rewind);
600     }
601 
getCurrentPositionjuce::iOSAudioIODevice::Pimpl602     bool getCurrentPosition (CurrentPositionInfo& result) override
603     {
604         if (! canControlTransport())
605             return false;
606 
607         zerostruct (result);
608 
609         HostCallbackInfo callbackInfo;
610         fillHostCallbackInfo (callbackInfo);
611 
612         if (callbackInfo.hostUserData == nullptr)
613             return false;
614 
615         Boolean hostIsPlaying               = NO;
616         Boolean hostIsRecording             = NO;
617         Float64 hostCurrentSampleInTimeLine = 0;
618         Boolean hostIsCycling               = NO;
619         Float64 hostCycleStartBeat          = 0;
620         Float64 hostCycleEndBeat            = 0;
621         OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData,
622                                                          &hostIsPlaying,
623                                                          &hostIsRecording,
624                                                          nullptr,
625                                                          &hostCurrentSampleInTimeLine,
626                                                          &hostIsCycling,
627                                                          &hostCycleStartBeat,
628                                                          &hostCycleEndBeat);
629         if (err == kAUGraphErr_CannotDoInCurrentContext)
630             return false;
631 
632         jassert (err == noErr);
633 
634         result.timeInSamples = (int64) hostCurrentSampleInTimeLine;
635         result.isPlaying     = hostIsPlaying;
636         result.isRecording   = hostIsRecording;
637         result.isLooping     = hostIsCycling;
638         result.ppqLoopStart  = hostCycleStartBeat;
639         result.ppqLoopEnd    = hostCycleEndBeat;
640 
641         result.timeInSeconds = result.timeInSamples / sampleRate;
642 
643         Float64 hostBeat = 0;
644         Float64 hostTempo = 0;
645         err = callbackInfo.beatAndTempoProc (callbackInfo.hostUserData,
646                                              &hostBeat,
647                                              &hostTempo);
648         jassert (err == noErr);
649 
650         result.ppqPosition = hostBeat;
651         result.bpm         = hostTempo;
652 
653         Float32 hostTimeSigNumerator = 0;
654         UInt32 hostTimeSigDenominator = 0;
655         Float64 hostCurrentMeasureDownBeat = 0;
656         err = callbackInfo.musicalTimeLocationProc (callbackInfo.hostUserData,
657                                                     nullptr,
658                                                     &hostTimeSigNumerator,
659                                                     &hostTimeSigDenominator,
660                                                     &hostCurrentMeasureDownBeat);
661         jassert (err == noErr);
662 
663         result.ppqPositionOfLastBarStart = hostCurrentMeasureDownBeat;
664         result.timeSigNumerator          = (int) hostTimeSigNumerator;
665         result.timeSigDenominator        = (int) hostTimeSigDenominator;
666 
667         result.frameRate = AudioPlayHead::fpsUnknown;
668 
669         return true;
670     }
671 
672     //==============================================================================
673    #if JUCE_MODULE_AVAILABLE_juce_graphics
getIconjuce::iOSAudioIODevice::Pimpl674     Image getIcon (int size)
675     {
676         if (interAppAudioConnected)
677         {
678             UIImage* hostUIImage = AudioOutputUnitGetHostIcon (audioUnit, size);
679             if (hostUIImage != nullptr)
680                 return juce_createImageFromUIImage (hostUIImage);
681         }
682         return Image();
683     }
684    #endif
685 
switchApplicationjuce::iOSAudioIODevice::Pimpl686     void switchApplication()
687     {
688         if (! interAppAudioConnected)
689             return;
690 
691         CFURLRef hostUrl;
692         UInt32 dataSize = sizeof (hostUrl);
693         OSStatus err = AudioUnitGetProperty(audioUnit,
694                                             kAudioUnitProperty_PeerURL,
695                                             kAudioUnitScope_Global,
696                                             0,
697                                             &hostUrl,
698                                             &dataSize);
699         if (err == noErr)
700         {
701            #if (! defined __IPHONE_10_0) || (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_10_0)
702             [[UIApplication sharedApplication] openURL: (NSURL*)hostUrl];
703            #else
704             [[UIApplication sharedApplication] openURL: (NSURL*)hostUrl options: @{} completionHandler: nil];
705            #endif
706         }
707     }
708 
709     //==============================================================================
invokeAudioDeviceErrorCallbackjuce::iOSAudioIODevice::Pimpl710     void invokeAudioDeviceErrorCallback (const String& reason)
711     {
712         const ScopedLock sl (callbackLock);
713 
714         if (callback != nullptr)
715             callback->audioDeviceError (reason);
716     }
717 
handleStatusChangejuce::iOSAudioIODevice::Pimpl718     void handleStatusChange (bool enabled, const char* reason)
719     {
720         const ScopedLock myScopedLock (callbackLock);
721 
722         JUCE_IOS_AUDIO_LOG ("handleStatusChange: enabled: " << (int) enabled << ", reason: " << reason);
723 
724         isRunning = enabled;
725         setAudioSessionActive (enabled);
726 
727         if (enabled)
728             AudioOutputUnitStart (audioUnit);
729         else
730             AudioOutputUnitStop (audioUnit);
731 
732         if (! enabled)
733             invokeAudioDeviceErrorCallback (reason);
734     }
735 
handleRouteChangejuce::iOSAudioIODevice::Pimpl736     void handleRouteChange (AVAudioSessionRouteChangeReason reason)
737     {
738         const ScopedLock myScopedLock (callbackLock);
739 
740         const String reasonString (getRoutingChangeReason (reason));
741         JUCE_IOS_AUDIO_LOG ("handleRouteChange: " << reasonString);
742 
743         if (isRunning)
744             invokeAudioDeviceErrorCallback (reasonString);
745 
746         switch (reason)
747         {
748         case AVAudioSessionRouteChangeReasonCategoryChange:
749         case AVAudioSessionRouteChangeReasonOverride:
750         case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
751             break;
752         case AVAudioSessionRouteChangeReasonUnknown:
753         case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
754         case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
755         case AVAudioSessionRouteChangeReasonWakeFromSleep:
756         case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
757         {
758             hardwareInfoNeedsUpdating = true;
759             triggerAsyncUpdate();
760             break;
761         }
762 
763         // No default so the code doesn't compile if this enum is extended.
764         }
765     }
766 
handleAudioUnitPropertyChangejuce::iOSAudioIODevice::Pimpl767     void handleAudioUnitPropertyChange (AudioUnit,
768                                         AudioUnitPropertyID propertyID,
769                                         AudioUnitScope scope,
770                                         AudioUnitElement element)
771     {
772         ignoreUnused (scope);
773         ignoreUnused (element);
774         JUCE_IOS_AUDIO_LOG ("handleAudioUnitPropertyChange: propertyID: " << String (propertyID)
775                                                             << " scope: " << String (scope)
776                                                           << " element: " << String (element));
777 
778         switch (propertyID)
779         {
780             case kAudioUnitProperty_IsInterAppConnected:
781                 handleInterAppAudioConnectionChange();
782                 return;
783             case kAudioUnitProperty_StreamFormat:
784                 handleStreamFormatChange();
785                 return;
786             default:
787                 jassertfalse;
788         }
789     }
790 
handleInterAppAudioConnectionChangejuce::iOSAudioIODevice::Pimpl791     void handleInterAppAudioConnectionChange()
792     {
793         UInt32 connected;
794         UInt32 dataSize = sizeof (connected);
795         OSStatus err = AudioUnitGetProperty (audioUnit, kAudioUnitProperty_IsInterAppConnected,
796                                              kAudioUnitScope_Global, 0, &connected, &dataSize);
797         ignoreUnused (err);
798         jassert (err == noErr);
799 
800         JUCE_IOS_AUDIO_LOG ("handleInterAppAudioConnectionChange: " << (connected ? "connected"
801                                                                                   : "disconnected"));
802 
803         if (connected != interAppAudioConnected)
804         {
805             const ScopedLock myScopedLock (callbackLock);
806 
807             interAppAudioConnected = connected;
808 
809             UIApplicationState appstate = [UIApplication sharedApplication].applicationState;
810             bool inForeground = (appstate != UIApplicationStateBackground);
811 
812             if (interAppAudioConnected || inForeground)
813             {
814                 setAudioSessionActive (true);
815                 AudioOutputUnitStart (audioUnit);
816 
817                 if (callback != nullptr)
818                     callback->audioDeviceAboutToStart (&owner);
819             }
820             else if (! inForeground)
821             {
822                 AudioOutputUnitStop (audioUnit);
823                 setAudioSessionActive (false);
824 
825                 if (callback != nullptr)
826                     callback->audioDeviceStopped();
827             }
828         }
829     }
830 
831     //==============================================================================
processjuce::iOSAudioIODevice::Pimpl832     OSStatus process (AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time,
833                       const UInt32 numFrames, AudioBufferList* data)
834     {
835         OSStatus err = noErr;
836 
837         recordXruns (time, numFrames);
838 
839         const bool useInput = channelData.areInputChannelsAvailable();
840 
841         if (useInput)
842             err = AudioUnitRender (audioUnit, flags, time, 1, numFrames, data);
843 
844         const auto channelDataSize = sizeof (float) * numFrames;
845 
846         const ScopedTryLock stl (callbackLock);
847 
848         if (stl.isLocked() && callback != nullptr)
849         {
850             if ((int) numFrames > channelData.getFloatBufferSize())
851                 channelData.setFloatBufferSize ((int) numFrames);
852 
853             float** const inputData = channelData.audioData.getArrayOfWritePointers();
854             float** const outputData = inputData + channelData.inputs->numActiveChannels;
855 
856             if (useInput)
857             {
858                 for (int c = 0; c < channelData.inputs->numActiveChannels; ++c)
859                 {
860                     auto channelIndex = channelData.inputs->activeChannelIndices[c];
861                     memcpy (inputData[c], (float*) data->mBuffers[channelIndex].mData, channelDataSize);
862                 }
863             }
864             else
865             {
866                 for (int c = 0; c < channelData.inputs->numActiveChannels; ++c)
867                     zeromem (inputData[c], channelDataSize);
868             }
869 
870             callback->audioDeviceIOCallback ((const float**) inputData,  channelData.inputs ->numActiveChannels,
871                                                              outputData, channelData.outputs->numActiveChannels,
872                                              (int) numFrames);
873 
874             for (int c = 0; c < channelData.outputs->numActiveChannels; ++c)
875             {
876                 auto channelIndex = channelData.outputs->activeChannelIndices[c];
877                 memcpy (data->mBuffers[channelIndex].mData, outputData[c], channelDataSize);
878             }
879 
880             for (auto c : channelData.outputs->inactiveChannelIndices)
881                 zeromem (data->mBuffers[c].mData, channelDataSize);
882         }
883         else
884         {
885             for (uint32 c = 0; c < data->mNumberBuffers; ++c)
886                 zeromem (data->mBuffers[c].mData, channelDataSize);
887         }
888 
889         return err;
890     }
891 
recordXrunsjuce::iOSAudioIODevice::Pimpl892     void recordXruns (const AudioTimeStamp* time, UInt32 numFrames)
893     {
894         if (time != nullptr && (time->mFlags & kAudioTimeStampSampleTimeValid) != 0)
895         {
896             if (! firstHostTime)
897             {
898                 if ((time->mSampleTime - lastSampleTime) != lastNumFrames)
899                     xrun++;
900             }
901             else
902                 firstHostTime = false;
903 
904             lastSampleTime = time->mSampleTime;
905         }
906         else
907             firstHostTime = true;
908 
909         lastNumFrames = numFrames;
910     }
911 
912     //==============================================================================
processStaticjuce::iOSAudioIODevice::Pimpl913     static OSStatus processStatic (void* client, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time,
914                                    UInt32 /*busNumber*/, UInt32 numFrames, AudioBufferList* data)
915     {
916         return static_cast<Pimpl*> (client)->process (flags, time, numFrames, data);
917     }
918 
919     //==============================================================================
createAudioUnitjuce::iOSAudioIODevice::Pimpl920     bool createAudioUnit()
921     {
922         JUCE_IOS_AUDIO_LOG ("Creating the audio unit");
923 
924         if (audioUnit != nullptr)
925         {
926             AudioComponentInstanceDispose (audioUnit);
927             audioUnit = nullptr;
928         }
929 
930         AudioComponentDescription desc;
931         desc.componentType = kAudioUnitType_Output;
932         desc.componentSubType = kAudioUnitSubType_RemoteIO;
933         desc.componentManufacturer = kAudioUnitManufacturer_Apple;
934         desc.componentFlags = 0;
935         desc.componentFlagsMask = 0;
936 
937         AudioComponent comp = AudioComponentFindNext (nullptr, &desc);
938         AudioComponentInstanceNew (comp, &audioUnit);
939 
940         if (audioUnit == nullptr)
941             return false;
942 
943        #if JucePlugin_Enable_IAA
944         AudioComponentDescription appDesc;
945         appDesc.componentType = JucePlugin_IAAType;
946         appDesc.componentSubType = JucePlugin_IAASubType;
947         appDesc.componentManufacturer = JucePlugin_ManufacturerCode;
948         appDesc.componentFlags = 0;
949         appDesc.componentFlagsMask = 0;
950         OSStatus err = AudioOutputUnitPublish (&appDesc,
951                                                CFSTR(JucePlugin_IAAName),
952                                                JucePlugin_VersionCode,
953                                                audioUnit);
954 
955         // This assert will be hit if the Inter-App Audio entitlement has not
956         // been enabled, or the description being published with
957         // AudioOutputUnitPublish is different from any in the AudioComponents
958         // array in this application's .plist file.
959         jassert (err == noErr);
960 
961         err = AudioUnitAddPropertyListener (audioUnit,
962                                             kAudioUnitProperty_IsInterAppConnected,
963                                             dispatchAudioUnitPropertyChange,
964                                             this);
965         jassert (err == noErr);
966 
967         AudioOutputUnitMIDICallbacks midiCallbacks;
968         midiCallbacks.userData = this;
969         midiCallbacks.MIDIEventProc = midiEventCallback;
970         midiCallbacks.MIDISysExProc = midiSysExCallback;
971         err = AudioUnitSetProperty (audioUnit,
972                                     kAudioOutputUnitProperty_MIDICallbacks,
973                                     kAudioUnitScope_Global,
974                                     0,
975                                     &midiCallbacks,
976                                     sizeof (midiCallbacks));
977         jassert (err == noErr);
978        #endif
979 
980         if (channelData.areInputChannelsAvailable())
981         {
982             const UInt32 one = 1;
983             AudioUnitSetProperty (audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &one, sizeof (one));
984         }
985 
986         {
987             AURenderCallbackStruct inputProc;
988             inputProc.inputProc = processStatic;
989             inputProc.inputProcRefCon = this;
990             AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &inputProc, sizeof (inputProc));
991         }
992 
993         {
994             AudioStreamBasicDescription format;
995             zerostruct (format);
996             format.mSampleRate = sampleRate;
997             format.mFormatID = kAudioFormatLinearPCM;
998             format.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsNonInterleaved | kAudioFormatFlagsNativeEndian | kLinearPCMFormatFlagIsPacked;
999             format.mBitsPerChannel = 8 * sizeof (float);
1000             format.mFramesPerPacket = 1;
1001             format.mChannelsPerFrame = (UInt32) jmax (channelData.inputs->numHardwareChannels, channelData.outputs->numHardwareChannels);
1002             format.mBytesPerFrame = format.mBytesPerPacket = sizeof (float);
1003 
1004             AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,  0, &format, sizeof (format));
1005             AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, sizeof (format));
1006         }
1007 
1008         AudioUnitInitialize (audioUnit);
1009 
1010         {
1011             // Querying the kAudioUnitProperty_MaximumFramesPerSlice property after calling AudioUnitInitialize
1012             // seems to be more reliable than calling it before.
1013             UInt32 framesPerSlice, dataSize = sizeof (framesPerSlice);
1014 
1015             if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_MaximumFramesPerSlice,
1016                                       kAudioUnitScope_Global, 0, &framesPerSlice, &dataSize) == noErr
1017                     && dataSize == sizeof (framesPerSlice)
1018                     && static_cast<int> (framesPerSlice) != bufferSize)
1019             {
1020                 JUCE_IOS_AUDIO_LOG ("Internal buffer size: " << String (framesPerSlice));
1021                 channelData.setFloatBufferSize (static_cast<int> (framesPerSlice));
1022             }
1023         }
1024 
1025         AudioUnitAddPropertyListener (audioUnit, kAudioUnitProperty_StreamFormat, dispatchAudioUnitPropertyChange, this);
1026 
1027         return true;
1028     }
1029 
fillHostCallbackInfojuce::iOSAudioIODevice::Pimpl1030     void fillHostCallbackInfo (HostCallbackInfo& callbackInfo)
1031     {
1032         zerostruct (callbackInfo);
1033         UInt32 dataSize = sizeof (HostCallbackInfo);
1034         OSStatus err = AudioUnitGetProperty (audioUnit,
1035                                              kAudioUnitProperty_HostCallbacks,
1036                                              kAudioUnitScope_Global,
1037                                              0,
1038                                              &callbackInfo,
1039                                              &dataSize);
1040         ignoreUnused (err);
1041         jassert (err == noErr);
1042     }
1043 
handleAudioTransportEventjuce::iOSAudioIODevice::Pimpl1044     void handleAudioTransportEvent (AudioUnitRemoteControlEvent event)
1045     {
1046         OSStatus err = AudioUnitSetProperty (audioUnit, kAudioOutputUnitProperty_RemoteControlToHost,
1047                                              kAudioUnitScope_Global, 0, &event, sizeof (event));
1048         ignoreUnused (err);
1049         jassert (err == noErr);
1050     }
1051 
1052     // If the routing is set to go through the receiver (i.e. the speaker, but quiet), this re-routes it
1053     // to make it loud. Needed because by default when using an input + output, the output is kept quiet.
fixAudioRouteIfSetToReceiverjuce::iOSAudioIODevice::Pimpl1054     static void fixAudioRouteIfSetToReceiver()
1055     {
1056         auto session = [AVAudioSession sharedInstance];
1057         auto route = session.currentRoute;
1058 
1059         for (AVAudioSessionPortDescription* port in route.outputs)
1060         {
1061             if ([port.portName isEqualToString: @"Receiver"])
1062             {
1063                 JUCE_NSERROR_CHECK ([session overrideOutputAudioPort: AVAudioSessionPortOverrideSpeaker
1064                                                                error: &error]);
1065                 setAudioSessionActive (true);
1066             }
1067         }
1068     }
1069 
restartjuce::iOSAudioIODevice::Pimpl1070     void restart()
1071     {
1072         const ScopedLock sl (callbackLock);
1073 
1074         updateHardwareInfo();
1075         setTargetSampleRateAndBufferSize();
1076 
1077         if (isRunning)
1078         {
1079             if (audioUnit != nullptr)
1080             {
1081                 AudioComponentInstanceDispose (audioUnit);
1082                 audioUnit = nullptr;
1083 
1084                 if (callback != nullptr)
1085                     callback->audioDeviceStopped();
1086             }
1087 
1088             channelData.reconfigure (requestedInputChannels, requestedOutputChannels);
1089 
1090             createAudioUnit();
1091 
1092             if (audioUnit != nullptr)
1093             {
1094                 isRunning = true;
1095 
1096                 if (callback != nullptr)
1097                     callback->audioDeviceAboutToStart (&owner);
1098 
1099                 AudioOutputUnitStart (audioUnit);
1100             }
1101         }
1102     }
1103 
handleAsyncUpdatejuce::iOSAudioIODevice::Pimpl1104     void handleAsyncUpdate() override
1105     {
1106         restart();
1107     }
1108 
handleStreamFormatChangejuce::iOSAudioIODevice::Pimpl1109     void handleStreamFormatChange()
1110     {
1111         AudioStreamBasicDescription desc;
1112         zerostruct (desc);
1113         UInt32 dataSize = sizeof (desc);
1114         AudioUnitGetProperty (audioUnit,
1115                               kAudioUnitProperty_StreamFormat,
1116                               kAudioUnitScope_Output,
1117                               0,
1118                               &desc,
1119                               &dataSize);
1120 
1121         if (desc.mSampleRate != 0 && desc.mSampleRate != sampleRate)
1122         {
1123             JUCE_IOS_AUDIO_LOG ("Stream format has changed: Sample rate " << desc.mSampleRate);
1124             triggerAsyncUpdate();
1125         }
1126     }
1127 
dispatchAudioUnitPropertyChangejuce::iOSAudioIODevice::Pimpl1128     static void dispatchAudioUnitPropertyChange (void* data, AudioUnit unit, AudioUnitPropertyID propertyID,
1129                                                  AudioUnitScope scope, AudioUnitElement element)
1130     {
1131         static_cast<Pimpl*> (data)->handleAudioUnitPropertyChange (unit, propertyID, scope, element);
1132     }
1133 
getTimestampForMIDIjuce::iOSAudioIODevice::Pimpl1134     static double getTimestampForMIDI()
1135     {
1136         return Time::getMillisecondCounter() / 1000.0;
1137     }
1138 
midiEventCallbackjuce::iOSAudioIODevice::Pimpl1139     static void midiEventCallback (void *client, UInt32 status, UInt32 data1, UInt32 data2, UInt32)
1140     {
1141         return static_cast<Pimpl*> (client)->handleMidiMessage (MidiMessage ((int) status,
1142                                                                              (int) data1,
1143                                                                              (int) data2,
1144                                                                              getTimestampForMIDI()));
1145     }
1146 
midiSysExCallbackjuce::iOSAudioIODevice::Pimpl1147     static void midiSysExCallback (void *client, const UInt8 *data, UInt32 length)
1148     {
1149         return static_cast<Pimpl*> (client)->handleMidiMessage (MidiMessage (data, (int) length, getTimestampForMIDI()));
1150     }
1151 
handleMidiMessagejuce::iOSAudioIODevice::Pimpl1152     void handleMidiMessage (MidiMessage msg)
1153     {
1154         if (messageCollector != nullptr)
1155             messageCollector->addMessageToQueue (msg);
1156     }
1157 
1158     struct IOChannelData
1159     {
1160         class IOChannelConfig
1161         {
1162         public:
IOChannelConfig(const bool isInput,const BigInteger requiredChannels)1163             IOChannelConfig (const bool isInput, const BigInteger requiredChannels)
1164                 : hardwareChannelNames (getHardwareChannelNames (isInput)),
1165                   numHardwareChannels (hardwareChannelNames.size()),
1166                   areChannelsAccessible ((! isInput) || [AVAudioSession sharedInstance].isInputAvailable),
1167                   activeChannels (limitRequiredChannelsToHardware (numHardwareChannels, requiredChannels)),
1168                   numActiveChannels (activeChannels.countNumberOfSetBits()),
1169                   activeChannelIndices (getActiveChannelIndices (activeChannels)),
1170                   inactiveChannelIndices (getInactiveChannelIndices (activeChannelIndices, numHardwareChannels))
1171             {
1172                #if JUCE_IOS_AUDIO_LOGGING
1173                 {
1174                     String info;
1175 
1176                     info << "Number of hardware channels: " << numHardwareChannels
1177                          << ", Hardware channel names:";
1178 
1179                     for (auto& name : hardwareChannelNames)
1180                         info << " \"" << name << "\"";
1181 
1182                     info << ", Are channels available: " << (areChannelsAccessible ? "yes" : "no")
1183                          << ", Active channel indices:";
1184 
1185                     for (auto i : activeChannelIndices)
1186                         info << " " << i;
1187 
1188                     info << ", Inactive channel indices:";
1189 
1190                     for (auto i : inactiveChannelIndices)
1191                         info << " " << i;
1192 
1193                     JUCE_IOS_AUDIO_LOG ((isInput ? "Input" : "Output") << " channel configuration: {" << info << "}");
1194                 }
1195                #endif
1196             }
1197 
1198             const StringArray hardwareChannelNames;
1199             const int numHardwareChannels;
1200             const bool areChannelsAccessible;
1201 
1202             const BigInteger activeChannels;
1203             const int numActiveChannels;
1204 
1205             const Array<int> activeChannelIndices, inactiveChannelIndices;
1206 
1207         private:
getHardwareChannelNames(const bool isInput)1208             static StringArray getHardwareChannelNames (const bool isInput)
1209             {
1210                 StringArray result;
1211 
1212                 auto route = [AVAudioSession sharedInstance].currentRoute;
1213 
1214                 for (AVAudioSessionPortDescription* port in (isInput ? route.inputs : route.outputs))
1215                 {
1216                     for (AVAudioSessionChannelDescription* desc in port.channels)
1217                         result.add (nsStringToJuce (desc.channelName));
1218                 }
1219 
1220                 // A fallback for the iOS simulator and older iOS versions
1221                 if (result.isEmpty())
1222                     return { "Left", "Right" };
1223 
1224                 return result;
1225             }
1226 
limitRequiredChannelsToHardware(const int numHardwareChannelsAvailable,BigInteger requiredChannels)1227             static BigInteger limitRequiredChannelsToHardware (const int numHardwareChannelsAvailable,
1228                                                                BigInteger requiredChannels)
1229             {
1230                 requiredChannels.setRange (numHardwareChannelsAvailable,
1231                                            requiredChannels.getHighestBit() + 1,
1232                                            false);
1233 
1234                 return requiredChannels;
1235             }
1236 
getActiveChannelIndices(const BigInteger activeChannelsToIndex)1237             static Array<int> getActiveChannelIndices (const BigInteger activeChannelsToIndex)
1238             {
1239                 Array<int> result;
1240 
1241                 auto index = activeChannelsToIndex.findNextSetBit (0);
1242 
1243                 while (index != -1)
1244                 {
1245                     result.add (index);
1246                     index = activeChannelsToIndex.findNextSetBit (++index);
1247                 }
1248 
1249                 return result;
1250             }
1251 
getInactiveChannelIndices(const Array<int> & activeIndices,int numChannels)1252             static Array<int> getInactiveChannelIndices (const Array<int>& activeIndices, int numChannels)
1253             {
1254                 Array<int> result;
1255 
1256                 auto nextActiveChannel = activeIndices.begin();
1257 
1258                 for (int i = 0; i < numChannels; ++i)
1259                     if (nextActiveChannel != activeIndices.end() && i == *nextActiveChannel)
1260                         ++nextActiveChannel;
1261                     else
1262                         result.add (i);
1263 
1264                 return result;
1265             }
1266         };
1267 
reconfigurejuce::iOSAudioIODevice::Pimpl::IOChannelData1268         void reconfigure (const BigInteger requiredInputChannels,
1269                           const BigInteger requiredOutputChannels)
1270         {
1271             inputs .reset (new IOChannelConfig (true,  requiredInputChannels));
1272             outputs.reset (new IOChannelConfig (false, requiredOutputChannels));
1273 
1274             audioData.setSize (inputs->numActiveChannels + outputs->numActiveChannels,
1275                                audioData.getNumSamples());
1276         }
1277 
getFloatBufferSizejuce::iOSAudioIODevice::Pimpl::IOChannelData1278         int getFloatBufferSize() const
1279         {
1280             return audioData.getNumSamples();
1281         }
1282 
setFloatBufferSizejuce::iOSAudioIODevice::Pimpl::IOChannelData1283         void setFloatBufferSize (const int newSize)
1284         {
1285             audioData.setSize (audioData.getNumChannels(), newSize);
1286         }
1287 
areInputChannelsAvailablejuce::iOSAudioIODevice::Pimpl::IOChannelData1288         bool areInputChannelsAvailable() const
1289         {
1290             return inputs->areChannelsAccessible && inputs->numActiveChannels > 0;
1291         }
1292 
1293         std::unique_ptr<IOChannelConfig> inputs;
1294         std::unique_ptr<IOChannelConfig> outputs;
1295 
1296         AudioBuffer<float> audioData { 0, 0 };
1297     };
1298 
1299     IOChannelData channelData;
1300 
1301     BigInteger requestedInputChannels, requestedOutputChannels;
1302 
1303     bool isRunning = false;
1304 
1305     AudioIODeviceCallback* callback = nullptr;
1306 
1307     String lastError;
1308 
1309    #if TARGET_IPHONE_SIMULATOR
1310     static constexpr int defaultBufferSize = 512;
1311    #else
1312     static constexpr int defaultBufferSize = 256;
1313    #endif
1314     int targetBufferSize = defaultBufferSize, bufferSize = targetBufferSize;
1315 
1316     double targetSampleRate = 44100.0, sampleRate = targetSampleRate;
1317 
1318     Array<double> availableSampleRates;
1319     Array<int> availableBufferSizes;
1320 
1321     bool interAppAudioConnected = false;
1322 
1323     MidiMessageCollector* messageCollector = nullptr;
1324 
1325     WeakReference<iOSAudioIODeviceType> deviceType;
1326     iOSAudioIODevice& owner;
1327 
1328     CriticalSection callbackLock;
1329 
1330     Atomic<bool> hardwareInfoNeedsUpdating { true };
1331 
1332     AudioUnit audioUnit {};
1333 
1334     SharedResourcePointer<AudioSessionHolder> sessionHolder;
1335 
1336     bool firstHostTime;
1337     Float64 lastSampleTime;
1338     unsigned int lastNumFrames;
1339     int xrun;
1340 
1341     JUCE_DECLARE_NON_COPYABLE (Pimpl)
1342 };
1343 
1344 //==============================================================================
iOSAudioIODevice(iOSAudioIODeviceType * ioDeviceType,const String &,const String &)1345 iOSAudioIODevice::iOSAudioIODevice (iOSAudioIODeviceType* ioDeviceType, const String&, const String&)
1346     : AudioIODevice (iOSAudioDeviceName, iOSAudioDeviceName),
1347       pimpl (new Pimpl (ioDeviceType, *this))
1348 {
1349 }
1350 
1351 //==============================================================================
open(const BigInteger & inChans,const BigInteger & outChans,double requestedSampleRate,int requestedBufferSize)1352 String iOSAudioIODevice::open (const BigInteger& inChans, const BigInteger& outChans,
1353                                double requestedSampleRate, int requestedBufferSize)
1354 {
1355     return pimpl->open (inChans, outChans, requestedSampleRate, requestedBufferSize);
1356 }
1357 
close()1358 void iOSAudioIODevice::close()                                      { pimpl->close(); }
1359 
start(AudioIODeviceCallback * callbackToUse)1360 void iOSAudioIODevice::start (AudioIODeviceCallback* callbackToUse) { pimpl->start (callbackToUse); }
stop()1361 void iOSAudioIODevice::stop()                                       { pimpl->stop(); }
1362 
getAvailableSampleRates()1363 Array<double> iOSAudioIODevice::getAvailableSampleRates()           { return pimpl->availableSampleRates; }
getAvailableBufferSizes()1364 Array<int> iOSAudioIODevice::getAvailableBufferSizes()              { return pimpl->availableBufferSizes; }
1365 
setAudioPreprocessingEnabled(bool enabled)1366 bool iOSAudioIODevice::setAudioPreprocessingEnabled (bool enabled)  { return pimpl->setAudioPreprocessingEnabled (enabled); }
1367 
isPlaying()1368 bool iOSAudioIODevice::isPlaying()                                  { return pimpl->isRunning && pimpl->callback != nullptr; }
isOpen()1369 bool iOSAudioIODevice::isOpen()                                     { return pimpl->isRunning; }
getLastError()1370 String iOSAudioIODevice::getLastError()                             { return pimpl->lastError; }
1371 
getOutputChannelNames()1372 StringArray iOSAudioIODevice::getOutputChannelNames()               { return pimpl->channelData.outputs->hardwareChannelNames; }
getInputChannelNames()1373 StringArray iOSAudioIODevice::getInputChannelNames()                { return pimpl->channelData.inputs->areChannelsAccessible ? pimpl->channelData.inputs->hardwareChannelNames : StringArray(); }
1374 
getDefaultBufferSize()1375 int iOSAudioIODevice::getDefaultBufferSize()                        { return pimpl->defaultBufferSize; }
getCurrentBufferSizeSamples()1376 int iOSAudioIODevice::getCurrentBufferSizeSamples()                 { return pimpl->bufferSize; }
1377 
getCurrentSampleRate()1378 double iOSAudioIODevice::getCurrentSampleRate()                     { return pimpl->sampleRate; }
1379 
getCurrentBitDepth()1380 int iOSAudioIODevice::getCurrentBitDepth()                          { return 16; }
1381 
getActiveInputChannels() const1382 BigInteger iOSAudioIODevice::getActiveInputChannels() const         { return pimpl->channelData.inputs->activeChannels; }
getActiveOutputChannels() const1383 BigInteger iOSAudioIODevice::getActiveOutputChannels() const        { return pimpl->channelData.outputs->activeChannels; }
1384 
getInputLatencyInSamples()1385 int iOSAudioIODevice::getInputLatencyInSamples()                    { return roundToInt (pimpl->sampleRate * [AVAudioSession sharedInstance].inputLatency); }
getOutputLatencyInSamples()1386 int iOSAudioIODevice::getOutputLatencyInSamples()                   { return roundToInt (pimpl->sampleRate * [AVAudioSession sharedInstance].outputLatency); }
getXRunCount() const1387 int iOSAudioIODevice::getXRunCount() const noexcept                 { return pimpl->xrun; }
1388 
setMidiMessageCollector(MidiMessageCollector * collector)1389 void iOSAudioIODevice::setMidiMessageCollector (MidiMessageCollector* collector) { pimpl->messageCollector = collector; }
getAudioPlayHead() const1390 AudioPlayHead* iOSAudioIODevice::getAudioPlayHead() const           { return pimpl.get(); }
1391 
isInterAppAudioConnected() const1392 bool iOSAudioIODevice::isInterAppAudioConnected() const             { return pimpl->interAppAudioConnected; }
1393 #if JUCE_MODULE_AVAILABLE_juce_graphics
getIcon(int size)1394 Image iOSAudioIODevice::getIcon (int size)                          { return pimpl->getIcon (size); }
1395 #endif
switchApplication()1396 void iOSAudioIODevice::switchApplication()                          { return pimpl->switchApplication(); }
1397 
1398 //==============================================================================
iOSAudioIODeviceType()1399 iOSAudioIODeviceType::iOSAudioIODeviceType()
1400     : AudioIODeviceType (iOSAudioDeviceName)
1401 {
1402     sessionHolder->activeDeviceTypes.add (this);
1403 }
1404 
~iOSAudioIODeviceType()1405 iOSAudioIODeviceType::~iOSAudioIODeviceType()
1406 {
1407     sessionHolder->activeDeviceTypes.removeFirstMatchingValue (this);
1408 }
1409 
1410 // The list of devices is updated automatically
scanForDevices()1411 void iOSAudioIODeviceType::scanForDevices() {}
getDeviceNames(bool) const1412 StringArray iOSAudioIODeviceType::getDeviceNames (bool) const             { return { iOSAudioDeviceName }; }
getDefaultDeviceIndex(bool) const1413 int iOSAudioIODeviceType::getDefaultDeviceIndex (bool) const              { return 0; }
getIndexOfDevice(AudioIODevice *,bool) const1414 int iOSAudioIODeviceType::getIndexOfDevice (AudioIODevice*, bool) const   { return 0; }
hasSeparateInputsAndOutputs() const1415 bool iOSAudioIODeviceType::hasSeparateInputsAndOutputs() const            { return false; }
1416 
createDevice(const String & outputDeviceName,const String & inputDeviceName)1417 AudioIODevice* iOSAudioIODeviceType::createDevice (const String& outputDeviceName, const String& inputDeviceName)
1418 {
1419     return new iOSAudioIODevice (this, outputDeviceName, inputDeviceName);
1420 }
1421 
handleRouteChange(AVAudioSessionRouteChangeReason)1422 void iOSAudioIODeviceType::handleRouteChange (AVAudioSessionRouteChangeReason)
1423 {
1424     triggerAsyncUpdate();
1425 }
1426 
handleAsyncUpdate()1427 void iOSAudioIODeviceType::handleAsyncUpdate()
1428 {
1429     callDeviceChangeListeners();
1430 }
1431 
1432 //==============================================================================
createAudioIODeviceType_iOSAudio()1433 AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio()
1434 {
1435     return new iOSAudioIODeviceType();
1436 }
1437 
1438 //==============================================================================
AudioSessionHolder()1439 AudioSessionHolder::AudioSessionHolder()    { nativeSession = [[iOSAudioSessionNative alloc] init: this]; }
~AudioSessionHolder()1440 AudioSessionHolder::~AudioSessionHolder()   { [nativeSession release]; }
1441 
handleStatusChange(bool enabled,const char * reason) const1442 void AudioSessionHolder::handleStatusChange (bool enabled, const char* reason) const
1443 {
1444     for (auto device: activeDevices)
1445         device->handleStatusChange (enabled, reason);
1446 }
1447 
handleRouteChange(AVAudioSessionRouteChangeReason reason)1448 void AudioSessionHolder::handleRouteChange (AVAudioSessionRouteChangeReason reason)
1449 {
1450     for (auto device: activeDevices)
1451         device->handleRouteChange (reason);
1452 
1453     for (auto deviceType: activeDeviceTypes)
1454         deviceType->handleRouteChange (reason);
1455 }
1456 
1457 #undef JUCE_NSERROR_CHECK
1458 
1459 } // namespace juce
1460