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.

1136 lines
43KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2016 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of the ISC license
  6. http://www.isc.org/downloads/software-support-policy/isc-license/
  7. Permission to use, copy, modify, and/or distribute this software for any
  8. purpose with or without fee is hereby granted, provided that the above
  9. copyright notice and this permission notice appear in all copies.
  10. THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD
  11. TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
  12. FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
  13. OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
  14. USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
  15. TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
  16. OF THIS SOFTWARE.
  17. -----------------------------------------------------------------------------
  18. To release a closed-source product which uses other parts of JUCE not
  19. licensed under the ISC terms, commercial licenses are available: visit
  20. www.juce.com for more information.
  21. ==============================================================================
  22. */
  23. class iOSAudioIODevice;
  24. static const char* const iOSAudioDeviceName = "iOS Audio";
  25. //==============================================================================
  26. struct AudioSessionHolder : public AsyncUpdater
  27. {
  28. AudioSessionHolder();
  29. ~AudioSessionHolder();
  30. void handleAsyncUpdate() override;
  31. void handleStatusChange (bool enabled, const char* reason) const;
  32. void handleRouteChange (const char* reason);
  33. CriticalSection routeChangeLock;
  34. String lastRouteChangeReason;
  35. Array<iOSAudioIODevice*> activeDevices;
  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. bool getNotificationValueForKey (NSNotification* notification, NSString* key, NSUInteger& value) noexcept
  54. {
  55. if (notification != nil)
  56. {
  57. if (NSDictionary* userInfo = [notification userInfo])
  58. {
  59. if (NSNumber* number = [userInfo objectForKey: key])
  60. {
  61. value = [number unsignedIntegerValue];
  62. return true;
  63. }
  64. }
  65. }
  66. jassertfalse;
  67. return false;
  68. }
  69. } // juce namespace
  70. //==============================================================================
  71. @interface iOSAudioSessionNative : NSObject
  72. {
  73. @private
  74. juce::AudioSessionHolder* audioSessionHolder;
  75. };
  76. - (id) init: (juce::AudioSessionHolder*) holder;
  77. - (void) dealloc;
  78. - (void) audioSessionChangedInterruptionType: (NSNotification*) notification;
  79. - (void) handleMediaServicesReset;
  80. - (void) handleMediaServicesLost;
  81. - (void) handleRouteChange: (NSNotification*) notification;
  82. @end
  83. @implementation iOSAudioSessionNative
  84. - (id) init: (juce::AudioSessionHolder*) holder
  85. {
  86. self = [super init];
  87. if (self != nil)
  88. {
  89. audioSessionHolder = holder;
  90. auto session = [AVAudioSession sharedInstance];
  91. auto centre = [NSNotificationCenter defaultCenter];
  92. [centre addObserver: self
  93. selector: @selector (audioSessionChangedInterruptionType:)
  94. name: AVAudioSessionInterruptionNotification
  95. object: session];
  96. [centre addObserver: self
  97. selector: @selector (handleMediaServicesLost)
  98. name: AVAudioSessionMediaServicesWereLostNotification
  99. object: session];
  100. [centre addObserver: self
  101. selector: @selector (handleMediaServicesReset)
  102. name: AVAudioSessionMediaServicesWereResetNotification
  103. object: session];
  104. [centre addObserver: self
  105. selector: @selector (handleRouteChange:)
  106. name: AVAudioSessionRouteChangeNotification
  107. object: session];
  108. }
  109. else
  110. {
  111. jassertfalse;
  112. }
  113. return self;
  114. }
  115. - (void) dealloc
  116. {
  117. [[NSNotificationCenter defaultCenter] removeObserver: self];
  118. [super dealloc];
  119. }
  120. - (void) audioSessionChangedInterruptionType: (NSNotification*) notification
  121. {
  122. NSUInteger value;
  123. if (juce::getNotificationValueForKey (notification, AVAudioSessionInterruptionTypeKey, value))
  124. {
  125. switch ((AVAudioSessionInterruptionType) value)
  126. {
  127. case AVAudioSessionInterruptionTypeBegan:
  128. audioSessionHolder->handleStatusChange (false, "AVAudioSessionInterruptionTypeBegan");
  129. break;
  130. case AVAudioSessionInterruptionTypeEnded:
  131. audioSessionHolder->handleStatusChange (true, "AVAudioSessionInterruptionTypeEnded");
  132. break;
  133. // No default so the code doesn't compile if this enum is extended.
  134. }
  135. }
  136. }
  137. - (void) handleMediaServicesReset
  138. {
  139. audioSessionHolder->handleStatusChange (true, "AVAudioSessionMediaServicesWereResetNotification");
  140. }
  141. - (void) handleMediaServicesLost
  142. {
  143. audioSessionHolder->handleStatusChange (false, "AVAudioSessionMediaServicesWereLostNotification");
  144. }
  145. - (void) handleRouteChange: (NSNotification*) notification
  146. {
  147. NSUInteger value;
  148. if (juce::getNotificationValueForKey (notification, AVAudioSessionRouteChangeReasonKey, value))
  149. audioSessionHolder->handleRouteChange (juce::getRoutingChangeReason ((AVAudioSessionRouteChangeReason) value));
  150. }
  151. @end
  152. //==============================================================================
  153. namespace juce {
  154. #ifndef JUCE_IOS_AUDIO_LOGGING
  155. #define JUCE_IOS_AUDIO_LOGGING 0
  156. #endif
  157. #if JUCE_IOS_AUDIO_LOGGING
  158. #define JUCE_IOS_AUDIO_LOG(x) DBG(x)
  159. #else
  160. #define JUCE_IOS_AUDIO_LOG(x)
  161. #endif
  162. static void logNSError (NSError* e)
  163. {
  164. if (e != nil)
  165. {
  166. JUCE_IOS_AUDIO_LOG ("iOS Audio error: " << [e.localizedDescription UTF8String]);
  167. jassertfalse;
  168. }
  169. }
  170. #define JUCE_NSERROR_CHECK(X) { NSError* error = nil; X; logNSError (error); }
  171. #if JUCE_MODULE_AVAILABLE_juce_graphics
  172. #include <juce_graphics/native/juce_mac_CoreGraphicsHelpers.h>
  173. #endif
  174. //==============================================================================
  175. class iOSAudioIODevice::Pimpl : public AudioPlayHead,
  176. private AsyncUpdater
  177. {
  178. public:
  179. Pimpl (iOSAudioIODevice& ioDevice)
  180. : owner (ioDevice)
  181. {
  182. sessionHolder->activeDevices.add (&owner);
  183. }
  184. ~Pimpl()
  185. {
  186. sessionHolder->activeDevices.removeFirstMatchingValue (&owner);
  187. owner.close();
  188. }
  189. static void setAudioSessionActive (bool enabled)
  190. {
  191. JUCE_NSERROR_CHECK ([[AVAudioSession sharedInstance] setActive: enabled
  192. error: &error]);
  193. }
  194. static double trySampleRate (double rate)
  195. {
  196. auto session = [AVAudioSession sharedInstance];
  197. JUCE_NSERROR_CHECK ([session setPreferredSampleRate: rate
  198. error: &error]);
  199. return session.sampleRate;
  200. }
  201. Array<double> getAvailableSampleRates()
  202. {
  203. Array<double> rates;
  204. // Important: the supported audio sample rates change on the iPhone 6S
  205. // depending on whether the headphones are plugged in or not!
  206. setAudioSessionActive (true);
  207. AudioUnitRemovePropertyListenerWithUserData (audioUnit,
  208. kAudioUnitProperty_StreamFormat,
  209. handleStreamFormatChangeCallback,
  210. this);
  211. const double lowestRate = trySampleRate (4000);
  212. const double highestRate = trySampleRate (192000);
  213. for (double rate = lowestRate; rate <= highestRate; rate += 1000)
  214. {
  215. const double supportedRate = trySampleRate (rate);
  216. rates.addIfNotAlreadyThere (supportedRate);
  217. rate = jmax (rate, supportedRate);
  218. }
  219. trySampleRate (owner.getCurrentSampleRate());
  220. AudioUnitAddPropertyListener (audioUnit,
  221. kAudioUnitProperty_StreamFormat,
  222. handleStreamFormatChangeCallback,
  223. this);
  224. for (auto r : rates)
  225. {
  226. ignoreUnused (r);
  227. JUCE_IOS_AUDIO_LOG ("available rate = " + String (r, 0) + "Hz");
  228. }
  229. return rates;
  230. }
  231. Array<int> getAvailableBufferSizes()
  232. {
  233. Array<int> r;
  234. for (int i = 6; i < 13; ++i)
  235. r.add (1 << i);
  236. return r;
  237. }
  238. String open (const BigInteger& inputChannelsWanted,
  239. const BigInteger& outputChannelsWanted,
  240. double targetSampleRate, int bufferSize)
  241. {
  242. close();
  243. owner.lastError.clear();
  244. owner.preferredBufferSize = bufferSize <= 0 ? owner.getDefaultBufferSize() : bufferSize;
  245. // xxx set up channel mapping
  246. owner.activeOutputChans = outputChannelsWanted;
  247. owner.activeOutputChans.setRange (2, owner.activeOutputChans.getHighestBit(), false);
  248. owner.numOutputChannels = owner.activeOutputChans.countNumberOfSetBits();
  249. monoOutputChannelNumber = owner.activeOutputChans.findNextSetBit (0);
  250. owner.activeInputChans = inputChannelsWanted;
  251. owner.activeInputChans.setRange (2, owner.activeInputChans.getHighestBit(), false);
  252. owner.numInputChannels = owner.activeInputChans.countNumberOfSetBits();
  253. monoInputChannelNumber = owner.activeInputChans.findNextSetBit (0);
  254. setAudioSessionActive (true);
  255. // Set the session category & options:
  256. auto session = [AVAudioSession sharedInstance];
  257. const bool useInputs = (owner.numInputChannels > 0 && owner.audioInputIsAvailable);
  258. NSString* category = (useInputs ? AVAudioSessionCategoryPlayAndRecord : AVAudioSessionCategoryPlayback);
  259. NSUInteger options = AVAudioSessionCategoryOptionMixWithOthers; // Alternatively AVAudioSessionCategoryOptionDuckOthers
  260. if (useInputs) // These options are only valid for category = PlayAndRecord
  261. options |= (AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionAllowBluetooth);
  262. JUCE_NSERROR_CHECK ([session setCategory: category
  263. withOptions: options
  264. error: &error]);
  265. fixAudioRouteIfSetToReceiver();
  266. // Set the sample rate
  267. trySampleRate (targetSampleRate);
  268. owner.updateSampleRateAndAudioInput();
  269. updateCurrentBufferSize();
  270. prepareFloatBuffers (owner.actualBufferSize);
  271. owner.isRunning = true;
  272. handleRouteChange ("Started AudioUnit");
  273. owner.lastError = (audioUnit != 0 ? "" : "Couldn't open the device");
  274. setAudioSessionActive (true);
  275. return owner.lastError;
  276. }
  277. void close()
  278. {
  279. if (owner.isRunning)
  280. {
  281. owner.isRunning = false;
  282. if (audioUnit != 0)
  283. {
  284. AudioOutputUnitStart (audioUnit);
  285. AudioComponentInstanceDispose (audioUnit);
  286. audioUnit = 0;
  287. }
  288. setAudioSessionActive (false);
  289. }
  290. }
  291. void start (AudioIODeviceCallback* newCallback)
  292. {
  293. if (owner.isRunning && owner.callback != newCallback)
  294. {
  295. if (newCallback != nullptr)
  296. newCallback->audioDeviceAboutToStart (&owner);
  297. const ScopedLock sl (callbackLock);
  298. owner.callback = newCallback;
  299. }
  300. }
  301. void stop()
  302. {
  303. if (owner.isRunning)
  304. {
  305. AudioIODeviceCallback* lastCallback;
  306. {
  307. const ScopedLock sl (callbackLock);
  308. lastCallback = owner.callback;
  309. owner.callback = nullptr;
  310. }
  311. if (lastCallback != nullptr)
  312. lastCallback->audioDeviceStopped();
  313. }
  314. }
  315. bool setAudioPreprocessingEnabled (bool enable)
  316. {
  317. auto session = [AVAudioSession sharedInstance];
  318. NSString* mode = (enable ? AVAudioSessionModeMeasurement
  319. : AVAudioSessionModeDefault);
  320. JUCE_NSERROR_CHECK ([session setMode: mode
  321. error: &error]);
  322. return session.mode == mode;
  323. }
  324. //==============================================================================
  325. bool canControlTransport() override { return owner.interAppAudioConnected; }
  326. void transportPlay (bool shouldSartPlaying) override
  327. {
  328. if (! canControlTransport())
  329. return;
  330. HostCallbackInfo callbackInfo;
  331. fillHostCallbackInfo (callbackInfo);
  332. Boolean hostIsPlaying = NO;
  333. OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData,
  334. &hostIsPlaying,
  335. nullptr,
  336. nullptr,
  337. nullptr,
  338. nullptr,
  339. nullptr,
  340. nullptr);
  341. ignoreUnused (err);
  342. jassert (err == noErr);
  343. if (hostIsPlaying != shouldSartPlaying)
  344. handleAudioTransportEvent (kAudioUnitRemoteControlEvent_TogglePlayPause);
  345. }
  346. void transportRecord (bool shouldStartRecording) override
  347. {
  348. if (! canControlTransport())
  349. return;
  350. HostCallbackInfo callbackInfo;
  351. fillHostCallbackInfo (callbackInfo);
  352. Boolean hostIsRecording = NO;
  353. OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData,
  354. nullptr,
  355. &hostIsRecording,
  356. nullptr,
  357. nullptr,
  358. nullptr,
  359. nullptr,
  360. nullptr);
  361. ignoreUnused (err);
  362. jassert (err == noErr);
  363. if (hostIsRecording != shouldStartRecording)
  364. handleAudioTransportEvent (kAudioUnitRemoteControlEvent_ToggleRecord);
  365. }
  366. void transportRewind() override
  367. {
  368. if (canControlTransport())
  369. handleAudioTransportEvent (kAudioUnitRemoteControlEvent_Rewind);
  370. }
  371. bool getCurrentPosition (CurrentPositionInfo& result) override
  372. {
  373. if (! canControlTransport())
  374. return false;
  375. zerostruct (result);
  376. HostCallbackInfo callbackInfo;
  377. fillHostCallbackInfo (callbackInfo);
  378. if (callbackInfo.hostUserData == nullptr)
  379. return false;
  380. Boolean hostIsPlaying = NO;
  381. Boolean hostIsRecording = NO;
  382. Float64 hostCurrentSampleInTimeLine = 0;
  383. Boolean hostIsCycling = NO;
  384. Float64 hostCycleStartBeat = 0;
  385. Float64 hostCycleEndBeat = 0;
  386. OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData,
  387. &hostIsPlaying,
  388. &hostIsRecording,
  389. nullptr,
  390. &hostCurrentSampleInTimeLine,
  391. &hostIsCycling,
  392. &hostCycleStartBeat,
  393. &hostCycleEndBeat);
  394. if (err == kAUGraphErr_CannotDoInCurrentContext)
  395. return false;
  396. jassert (err == noErr);
  397. result.timeInSamples = (int64) hostCurrentSampleInTimeLine;
  398. result.isPlaying = hostIsPlaying;
  399. result.isRecording = hostIsRecording;
  400. result.isLooping = hostIsCycling;
  401. result.ppqLoopStart = hostCycleStartBeat;
  402. result.ppqLoopEnd = hostCycleEndBeat;
  403. result.timeInSeconds = result.timeInSamples / owner.sampleRate;
  404. Float64 hostBeat = 0;
  405. Float64 hostTempo = 0;
  406. err = callbackInfo.beatAndTempoProc (callbackInfo.hostUserData,
  407. &hostBeat,
  408. &hostTempo);
  409. jassert (err == noErr);
  410. result.ppqPosition = hostBeat;
  411. result.bpm = hostTempo;
  412. Float32 hostTimeSigNumerator = 0;
  413. UInt32 hostTimeSigDenominator = 0;
  414. Float64 hostCurrentMeasureDownBeat = 0;
  415. err = callbackInfo.musicalTimeLocationProc (callbackInfo.hostUserData,
  416. nullptr,
  417. &hostTimeSigNumerator,
  418. &hostTimeSigDenominator,
  419. &hostCurrentMeasureDownBeat);
  420. jassert (err == noErr);
  421. result.ppqPositionOfLastBarStart = hostCurrentMeasureDownBeat;
  422. result.timeSigNumerator = (int) hostTimeSigNumerator;
  423. result.timeSigDenominator = (int) hostTimeSigDenominator;
  424. result.frameRate = AudioPlayHead::fpsUnknown;
  425. return true;
  426. }
  427. //==============================================================================
  428. #if JUCE_MODULE_AVAILABLE_juce_graphics
  429. Image getIcon (int size)
  430. {
  431. if (owner.interAppAudioConnected)
  432. {
  433. UIImage* hostUIImage = AudioOutputUnitGetHostIcon (audioUnit, size);
  434. if (hostUIImage != nullptr)
  435. return juce_createImageFromUIImage (hostUIImage);
  436. }
  437. return Image();
  438. }
  439. #endif
  440. void switchApplication()
  441. {
  442. if (! owner.interAppAudioConnected)
  443. return;
  444. CFURLRef hostUrl;
  445. UInt32 dataSize = sizeof (hostUrl);
  446. OSStatus err = AudioUnitGetProperty(audioUnit,
  447. kAudioUnitProperty_PeerURL,
  448. kAudioUnitScope_Global,
  449. 0,
  450. &hostUrl,
  451. &dataSize);
  452. if (err == noErr)
  453. [[UIApplication sharedApplication] openURL:(NSURL*)hostUrl];
  454. }
  455. //==============================================================================
  456. void invokeAudioDeviceErrorCallback (const String& reason)
  457. {
  458. const ScopedLock sl (callbackLock);
  459. if (owner.callback != nullptr)
  460. owner.callback->audioDeviceError (reason);
  461. }
  462. void handleStatusChange (bool enabled, const char* reason)
  463. {
  464. const ScopedLock myScopedLock (callbackLock);
  465. JUCE_IOS_AUDIO_LOG ("handleStatusChange: enabled: " << (int) enabled << ", reason: " << reason);
  466. owner.isRunning = enabled;
  467. setAudioSessionActive (enabled);
  468. if (enabled)
  469. AudioOutputUnitStart (audioUnit);
  470. else
  471. AudioOutputUnitStop (audioUnit);
  472. if (! enabled)
  473. invokeAudioDeviceErrorCallback (reason);
  474. }
  475. void handleRouteChange (const char* reason)
  476. {
  477. const ScopedLock myScopedLock (callbackLock);
  478. JUCE_IOS_AUDIO_LOG ("handleRouteChange: reason: " << reason);
  479. fixAudioRouteIfSetToReceiver();
  480. if (owner.isRunning)
  481. {
  482. invokeAudioDeviceErrorCallback (reason);
  483. owner.updateSampleRateAndAudioInput();
  484. updateCurrentBufferSize();
  485. createAudioUnit();
  486. setAudioSessionActive (true);
  487. if (audioUnit != 0)
  488. {
  489. UInt32 formatSize = sizeof (format);
  490. AudioUnitGetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, &formatSize);
  491. AudioOutputUnitStart (audioUnit);
  492. }
  493. if (owner.callback != nullptr)
  494. {
  495. owner.callback->audioDeviceStopped();
  496. owner.callback->audioDeviceAboutToStart (&owner);
  497. }
  498. }
  499. }
  500. void handleAudioUnitPropertyChange (AudioUnit,
  501. AudioUnitPropertyID propertyID,
  502. AudioUnitScope,
  503. AudioUnitElement)
  504. {
  505. const ScopedLock myScopedLock (callbackLock);
  506. switch (propertyID)
  507. {
  508. case kAudioUnitProperty_IsInterAppConnected: return handleInterAppAudioConnectionChange();
  509. default: return;
  510. }
  511. }
  512. void handleInterAppAudioConnectionChange()
  513. {
  514. UInt32 connected;
  515. UInt32 dataSize = sizeof (connected);
  516. OSStatus err = AudioUnitGetProperty (audioUnit, kAudioUnitProperty_IsInterAppConnected,
  517. kAudioUnitScope_Global, 0, &connected, &dataSize);
  518. ignoreUnused (err);
  519. jassert (err == noErr);
  520. JUCE_IOS_AUDIO_LOG ("handleInterAppAudioConnectionChange: " << (connected ? "connected"
  521. : "disconnected"));
  522. if (connected != owner.interAppAudioConnected)
  523. {
  524. const ScopedLock myScopedLock (callbackLock);
  525. owner.interAppAudioConnected = connected;
  526. UIApplicationState appstate = [UIApplication sharedApplication].applicationState;
  527. bool inForeground = (appstate != UIApplicationStateBackground);
  528. if (owner.interAppAudioConnected || inForeground)
  529. {
  530. setAudioSessionActive (true);
  531. AudioOutputUnitStart (audioUnit);
  532. if (owner.callback != nullptr)
  533. owner.callback->audioDeviceAboutToStart (&owner);
  534. }
  535. else if (! inForeground)
  536. {
  537. AudioOutputUnitStop (audioUnit);
  538. setAudioSessionActive (false);
  539. }
  540. }
  541. }
  542. private:
  543. //==============================================================================
  544. iOSAudioIODevice& owner;
  545. SharedResourcePointer<AudioSessionHolder> sessionHolder;
  546. CriticalSection callbackLock;
  547. AudioStreamBasicDescription format;
  548. AudioUnit audioUnit {};
  549. AudioSampleBuffer floatData;
  550. float* inputChannels[3];
  551. float* outputChannels[3];
  552. bool monoInputChannelNumber, monoOutputChannelNumber;
  553. void prepareFloatBuffers (int bufferSize)
  554. {
  555. if (owner.numInputChannels + owner.numOutputChannels > 0)
  556. {
  557. floatData.setSize (owner.numInputChannels + owner.numOutputChannels, bufferSize);
  558. zeromem (inputChannels, sizeof (inputChannels));
  559. zeromem (outputChannels, sizeof (outputChannels));
  560. for (int i = 0; i < owner.numInputChannels; ++i)
  561. inputChannels[i] = floatData.getWritePointer (i);
  562. for (int i = 0; i < owner.numOutputChannels; ++i)
  563. outputChannels[i] = floatData.getWritePointer (i + owner.numInputChannels);
  564. }
  565. }
  566. //==============================================================================
  567. OSStatus process (AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time,
  568. const UInt32 numFrames, AudioBufferList* data)
  569. {
  570. OSStatus err = noErr;
  571. if (owner.audioInputIsAvailable && owner.numInputChannels > 0)
  572. err = AudioUnitRender (audioUnit, flags, time, 1, numFrames, data);
  573. const ScopedTryLock stl (callbackLock);
  574. if (stl.isLocked() && owner.callback != nullptr)
  575. {
  576. if ((int) numFrames > floatData.getNumSamples())
  577. prepareFloatBuffers ((int) numFrames);
  578. if (owner.audioInputIsAvailable && owner.numInputChannels > 0)
  579. {
  580. short* shortData = (short*) data->mBuffers[0].mData;
  581. if (owner.numInputChannels >= 2)
  582. {
  583. for (UInt32 i = 0; i < numFrames; ++i)
  584. {
  585. inputChannels[0][i] = *shortData++ * (1.0f / 32768.0f);
  586. inputChannels[1][i] = *shortData++ * (1.0f / 32768.0f);
  587. }
  588. }
  589. else
  590. {
  591. if (monoInputChannelNumber > 0)
  592. ++shortData;
  593. for (UInt32 i = 0; i < numFrames; ++i)
  594. {
  595. inputChannels[0][i] = *shortData++ * (1.0f / 32768.0f);
  596. ++shortData;
  597. }
  598. }
  599. }
  600. else
  601. {
  602. for (int i = owner.numInputChannels; --i >= 0;)
  603. zeromem (inputChannels[i], sizeof (float) * numFrames);
  604. }
  605. owner.callback->audioDeviceIOCallback ((const float**) inputChannels, owner.numInputChannels,
  606. outputChannels, owner.numOutputChannels, (int) numFrames);
  607. short* const shortData = (short*) data->mBuffers[0].mData;
  608. int n = 0;
  609. if (owner.numOutputChannels >= 2)
  610. {
  611. for (UInt32 i = 0; i < numFrames; ++i)
  612. {
  613. shortData [n++] = (short) (outputChannels[0][i] * 32767.0f);
  614. shortData [n++] = (short) (outputChannels[1][i] * 32767.0f);
  615. }
  616. }
  617. else if (owner.numOutputChannels == 1)
  618. {
  619. for (UInt32 i = 0; i < numFrames; ++i)
  620. {
  621. const short s = (short) (outputChannels[monoOutputChannelNumber][i] * 32767.0f);
  622. shortData [n++] = s;
  623. shortData [n++] = s;
  624. }
  625. }
  626. else
  627. {
  628. zeromem (data->mBuffers[0].mData, 2 * sizeof (short) * numFrames);
  629. }
  630. }
  631. else
  632. {
  633. zeromem (data->mBuffers[0].mData, 2 * sizeof (short) * numFrames);
  634. }
  635. return err;
  636. }
  637. void updateCurrentBufferSize()
  638. {
  639. NSTimeInterval bufferDuration = owner.sampleRate > 0 ? (NSTimeInterval) ((owner.preferredBufferSize + 1) / owner.sampleRate) : 0.0;
  640. JUCE_NSERROR_CHECK ([[AVAudioSession sharedInstance] setPreferredIOBufferDuration: bufferDuration
  641. error: &error]);
  642. owner.updateSampleRateAndAudioInput();
  643. }
  644. //==============================================================================
  645. static OSStatus processStatic (void* client, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time,
  646. UInt32 /*busNumber*/, UInt32 numFrames, AudioBufferList* data)
  647. {
  648. return static_cast<Pimpl*> (client)->process (flags, time, numFrames, data);
  649. }
  650. //==============================================================================
  651. void resetFormat (const int numChannels) noexcept
  652. {
  653. zerostruct (format);
  654. format.mFormatID = kAudioFormatLinearPCM;
  655. format.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked | kAudioFormatFlagsNativeEndian;
  656. format.mBitsPerChannel = 8 * sizeof (short);
  657. format.mChannelsPerFrame = (UInt32) numChannels;
  658. format.mFramesPerPacket = 1;
  659. format.mBytesPerFrame = format.mBytesPerPacket = (UInt32) numChannels * sizeof (short);
  660. }
  661. bool createAudioUnit()
  662. {
  663. if (audioUnit != 0)
  664. {
  665. AudioComponentInstanceDispose (audioUnit);
  666. audioUnit = 0;
  667. }
  668. resetFormat (2);
  669. AudioComponentDescription desc;
  670. desc.componentType = kAudioUnitType_Output;
  671. desc.componentSubType = kAudioUnitSubType_RemoteIO;
  672. desc.componentManufacturer = kAudioUnitManufacturer_Apple;
  673. desc.componentFlags = 0;
  674. desc.componentFlagsMask = 0;
  675. AudioComponent comp = AudioComponentFindNext (0, &desc);
  676. AudioComponentInstanceNew (comp, &audioUnit);
  677. if (audioUnit == 0)
  678. return false;
  679. #if JucePlugin_Enable_IAA
  680. AudioComponentDescription appDesc;
  681. appDesc.componentType = JucePlugin_IAAType;
  682. appDesc.componentSubType = JucePlugin_IAASubType;
  683. appDesc.componentManufacturer = JucePlugin_ManufacturerCode;
  684. appDesc.componentFlags = 0;
  685. appDesc.componentFlagsMask = 0;
  686. OSStatus err = AudioOutputUnitPublish (&appDesc,
  687. CFSTR(JucePlugin_IAAName),
  688. JucePlugin_VersionCode,
  689. audioUnit);
  690. // This assert will be hit if the Inter-App Audio entitlement has not
  691. // been enabled, or the description being published with
  692. // AudioOutputUnitPublish is different from any in the AudioComponents
  693. // array in this application's .plist file.
  694. jassert (err == noErr);
  695. err = AudioUnitAddPropertyListener(audioUnit,
  696. kAudioUnitProperty_IsInterAppConnected,
  697. audioUnitPropertyChangeDispatcher,
  698. this);
  699. jassert (err == noErr);
  700. #endif
  701. if (owner.numInputChannels > 0)
  702. {
  703. const UInt32 one = 1;
  704. AudioUnitSetProperty (audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &one, sizeof (one));
  705. }
  706. {
  707. AudioChannelLayout layout;
  708. layout.mChannelBitmap = 0;
  709. layout.mNumberChannelDescriptions = 0;
  710. layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
  711. AudioUnitSetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Input, 0, &layout, sizeof (layout));
  712. AudioUnitSetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Output, 0, &layout, sizeof (layout));
  713. }
  714. {
  715. AURenderCallbackStruct inputProc;
  716. inputProc.inputProc = processStatic;
  717. inputProc.inputProcRefCon = this;
  718. AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &inputProc, sizeof (inputProc));
  719. }
  720. AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof (format));
  721. AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, sizeof (format));
  722. UInt32 framesPerSlice;
  723. UInt32 dataSize = sizeof (framesPerSlice);
  724. AudioUnitInitialize (audioUnit);
  725. updateCurrentBufferSize();
  726. if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_MaximumFramesPerSlice,
  727. kAudioUnitScope_Global, 0, &framesPerSlice, &dataSize) == noErr
  728. && dataSize == sizeof (framesPerSlice) && static_cast<int> (framesPerSlice) != owner.actualBufferSize)
  729. {
  730. prepareFloatBuffers (static_cast<int> (framesPerSlice));
  731. }
  732. AudioUnitAddPropertyListener (audioUnit, kAudioUnitProperty_StreamFormat, handleStreamFormatChangeCallback, this);
  733. return true;
  734. }
  735. void fillHostCallbackInfo (HostCallbackInfo& callbackInfo)
  736. {
  737. zerostruct (callbackInfo);
  738. UInt32 dataSize = sizeof (HostCallbackInfo);
  739. OSStatus err = AudioUnitGetProperty (audioUnit,
  740. kAudioUnitProperty_HostCallbacks,
  741. kAudioUnitScope_Global,
  742. 0,
  743. &callbackInfo,
  744. &dataSize);
  745. ignoreUnused (err);
  746. jassert (err == noErr);
  747. }
  748. void handleAudioTransportEvent (AudioUnitRemoteControlEvent event)
  749. {
  750. OSStatus err = AudioUnitSetProperty (audioUnit, kAudioOutputUnitProperty_RemoteControlToHost,
  751. kAudioUnitScope_Global, 0, &event, sizeof (event));
  752. ignoreUnused (err);
  753. jassert (err == noErr);
  754. }
  755. // If the routing is set to go through the receiver (i.e. the speaker, but quiet), this re-routes it
  756. // to make it loud. Needed because by default when using an input + output, the output is kept quiet.
  757. static void fixAudioRouteIfSetToReceiver()
  758. {
  759. auto session = [AVAudioSession sharedInstance];
  760. auto route = session.currentRoute;
  761. for (AVAudioSessionPortDescription* port in route.inputs)
  762. {
  763. ignoreUnused (port);
  764. JUCE_IOS_AUDIO_LOG ("AVAudioSession: input: " << [port.description UTF8String]);
  765. }
  766. for (AVAudioSessionPortDescription* port in route.outputs)
  767. {
  768. JUCE_IOS_AUDIO_LOG ("AVAudioSession: output: " << [port.description UTF8String]);
  769. if ([port.portName isEqualToString: @"Receiver"])
  770. {
  771. JUCE_NSERROR_CHECK ([session overrideOutputAudioPort: AVAudioSessionPortOverrideSpeaker
  772. error: &error]);
  773. setAudioSessionActive (true);
  774. }
  775. }
  776. }
  777. void handleAsyncUpdate() override
  778. {
  779. owner.handleRouteChange ("Stream format change");
  780. }
  781. void handleStreamFormatChange()
  782. {
  783. AudioStreamBasicDescription desc;
  784. zerostruct (desc);
  785. UInt32 dataSize = sizeof (desc);
  786. AudioUnitGetProperty(audioUnit,
  787. kAudioUnitProperty_StreamFormat,
  788. kAudioUnitScope_Output,
  789. 0,
  790. &desc,
  791. &dataSize);
  792. if (desc.mSampleRate != owner.getCurrentSampleRate())
  793. triggerAsyncUpdate();
  794. }
  795. static void handleStreamFormatChangeCallback (void* device,
  796. AudioUnit,
  797. AudioUnitPropertyID,
  798. AudioUnitScope scope,
  799. AudioUnitElement element)
  800. {
  801. if (scope == kAudioUnitScope_Output && element == 0)
  802. static_cast<Pimpl*> (device)->handleStreamFormatChange();
  803. }
  804. static void audioUnitPropertyChangeDispatcher (void* data, AudioUnit unit, AudioUnitPropertyID propertyID,
  805. AudioUnitScope scope, AudioUnitElement element)
  806. {
  807. Pimpl* device = (Pimpl*)data;
  808. device->handleAudioUnitPropertyChange (unit, propertyID, scope, element);
  809. }
  810. void handleMidiMessage (MidiMessage msg)
  811. {
  812. if (owner.messageCollector != nullptr)
  813. owner.messageCollector->addMessageToQueue (msg);
  814. }
  815. static void midiEventCallback (void *client, UInt32 status, UInt32 data1, UInt32 data2, UInt32)
  816. {
  817. return static_cast<Pimpl*> (client)->handleMidiMessage (MidiMessage ((int) status,
  818. (int) data1,
  819. (int) data2,
  820. Time::getMillisecondCounter() / 1000.0));
  821. }
  822. JUCE_DECLARE_NON_COPYABLE (Pimpl)
  823. };
  824. //==============================================================================
  825. iOSAudioIODevice::iOSAudioIODevice (const String& deviceName)
  826. : AudioIODevice (deviceName, iOSAudioDeviceName),
  827. #if TARGET_IPHONE_SIMULATOR
  828. defaultBufferSize (512),
  829. #else
  830. defaultBufferSize (256),
  831. #endif
  832. sampleRate (0), numInputChannels (2), numOutputChannels (2),
  833. preferredBufferSize (0), actualBufferSize (0), isRunning (false),
  834. audioInputIsAvailable (false), interAppAudioConnected (false),
  835. callback (nullptr), messageCollector (nullptr),
  836. pimpl (new Pimpl (*this))
  837. {
  838. updateSampleRateAndAudioInput();
  839. }
  840. //==============================================================================
  841. int iOSAudioIODevice::getOutputLatencyInSamples() { return roundToInt (sampleRate * [AVAudioSession sharedInstance].outputLatency); }
  842. int iOSAudioIODevice::getInputLatencyInSamples() { return roundToInt (sampleRate * [AVAudioSession sharedInstance].inputLatency); }
  843. //==============================================================================
  844. AudioPlayHead* iOSAudioIODevice::getAudioPlayHead() const { return pimpl; }
  845. void iOSAudioIODevice::close() { pimpl->close(); }
  846. void iOSAudioIODevice::start (AudioIODeviceCallback* callbackToUse) { pimpl->start (callbackToUse); }
  847. void iOSAudioIODevice::stop() { pimpl->stop(); }
  848. Array<double> iOSAudioIODevice::getAvailableSampleRates() { return pimpl->getAvailableSampleRates(); }
  849. Array<int> iOSAudioIODevice::getAvailableBufferSizes() { return pimpl->getAvailableBufferSizes(); }
  850. bool iOSAudioIODevice::setAudioPreprocessingEnabled (bool enabled) { return pimpl->setAudioPreprocessingEnabled (enabled); }
  851. void iOSAudioIODevice::switchApplication() { return pimpl->switchApplication(); }
  852. //==============================================================================
  853. void iOSAudioIODevice::handleStatusChange (bool enabled, const char* reason) { pimpl->handleStatusChange (enabled, reason); }
  854. void iOSAudioIODevice::handleRouteChange (const char* reason) { pimpl->handleRouteChange (reason); }
  855. #if JUCE_MODULE_AVAILABLE_juce_graphics
  856. Image iOSAudioIODevice::getIcon (int size) { return pimpl->getIcon (size); }
  857. #endif
  858. //==============================================================================
  859. String iOSAudioIODevice::open (const BigInteger& inChans, const BigInteger& outChans, double requestedSampleRate, int requestedBufferSize)
  860. {
  861. return pimpl->open (inChans, outChans, requestedSampleRate, requestedBufferSize);
  862. }
  863. void iOSAudioIODevice::updateSampleRateAndAudioInput()
  864. {
  865. auto session = [AVAudioSession sharedInstance];
  866. sampleRate = session.sampleRate;
  867. audioInputIsAvailable = session.isInputAvailable;
  868. actualBufferSize = roundToInt (sampleRate * session.IOBufferDuration);
  869. JUCE_IOS_AUDIO_LOG ("AVAudioSession: sampleRate: " << sampleRate
  870. << " Hz, audioInputAvailable: " << (int) audioInputIsAvailable
  871. << ", buffer size: " << actualBufferSize);
  872. }
  873. //==============================================================================
  874. class iOSAudioIODeviceType : public AudioIODeviceType
  875. {
  876. public:
  877. iOSAudioIODeviceType() : AudioIODeviceType (iOSAudioDeviceName) {}
  878. void scanForDevices() {}
  879. StringArray getDeviceNames (bool /*wantInputNames*/) const { return StringArray (iOSAudioDeviceName); }
  880. int getDefaultDeviceIndex (bool /*forInput*/) const { return 0; }
  881. int getIndexOfDevice (AudioIODevice* d, bool /*asInput*/) const { return d != nullptr ? 0 : -1; }
  882. bool hasSeparateInputsAndOutputs() const { return false; }
  883. AudioIODevice* createDevice (const String& outputDeviceName, const String& inputDeviceName)
  884. {
  885. if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty())
  886. return new iOSAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName : inputDeviceName);
  887. return nullptr;
  888. }
  889. private:
  890. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSAudioIODeviceType)
  891. };
  892. //==============================================================================
  893. AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio()
  894. {
  895. return new iOSAudioIODeviceType();
  896. }
  897. //==============================================================================
  898. AudioSessionHolder::AudioSessionHolder() { nativeSession = [[iOSAudioSessionNative alloc] init: this]; }
  899. AudioSessionHolder::~AudioSessionHolder() { [nativeSession release]; }
  900. void AudioSessionHolder::handleAsyncUpdate()
  901. {
  902. const ScopedLock sl (routeChangeLock);
  903. for (auto device: activeDevices)
  904. device->handleRouteChange (lastRouteChangeReason.toRawUTF8());
  905. }
  906. void AudioSessionHolder::handleStatusChange (bool enabled, const char* reason) const
  907. {
  908. for (auto device: activeDevices)
  909. device->handleStatusChange (enabled, reason);
  910. }
  911. void AudioSessionHolder::handleRouteChange (const char* reason)
  912. {
  913. const ScopedLock sl (routeChangeLock);
  914. lastRouteChangeReason = reason;
  915. triggerAsyncUpdate();
  916. }
  917. #undef JUCE_NSERROR_CHECK