The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1515 lines
57KB

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