1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 #if JUCE_MAC || JUCE_IOS
27 
28 #include <juce_audio_basics/native/juce_mac_CoreAudioLayouts.h>
29 
30 namespace juce
31 {
32 
33 //==============================================================================
34 namespace
35 {
36     const char* const coreAudioFormatName = "CoreAudio supported file";
37 
findFileExtensionsForCoreAudioCodecs()38     StringArray findFileExtensionsForCoreAudioCodecs()
39     {
40         StringArray extensionsArray;
41         CFArrayRef extensions = nullptr;
42         UInt32 sizeOfArray = sizeof (extensions);
43 
44         if (AudioFileGetGlobalInfo (kAudioFileGlobalInfo_AllExtensions, 0, nullptr, &sizeOfArray, &extensions) == noErr)
45         {
46             auto numValues = CFArrayGetCount (extensions);
47 
48             for (CFIndex i = 0; i < numValues; ++i)
49                 extensionsArray.add ("." + String::fromCFString ((CFStringRef) CFArrayGetValueAtIndex (extensions, i)));
50 
51             CFRelease (extensions);
52         }
53 
54         return extensionsArray;
55     }
56 }
57 
58 //==============================================================================
59 const char* const CoreAudioFormat::midiDataBase64   = "midiDataBase64";
60 const char* const CoreAudioFormat::tempo            = "tempo";
61 const char* const CoreAudioFormat::timeSig          = "time signature";
62 const char* const CoreAudioFormat::keySig           = "key signature";
63 
64 //==============================================================================
65 struct CoreAudioFormatMetatdata
66 {
chunkNamejuce::CoreAudioFormatMetatdata67     static uint32 chunkName (const char* const name) noexcept   { return ByteOrder::bigEndianInt (name); }
68 
69     //==============================================================================
70     struct FileHeader
71     {
FileHeaderjuce::CoreAudioFormatMetatdata::FileHeader72         FileHeader (InputStream& input)
73         {
74             fileType    = (uint32) input.readIntBigEndian();
75             fileVersion = (uint16) input.readShortBigEndian();
76             fileFlags   = (uint16) input.readShortBigEndian();
77         }
78 
79         uint32 fileType;
80         uint16 fileVersion;
81         uint16 fileFlags;
82     };
83 
84     //==============================================================================
85     struct ChunkHeader
86     {
ChunkHeaderjuce::CoreAudioFormatMetatdata::ChunkHeader87         ChunkHeader (InputStream& input)
88         {
89             chunkType = (uint32) input.readIntBigEndian();
90             chunkSize = (int64)  input.readInt64BigEndian();
91         }
92 
93         uint32 chunkType;
94         int64 chunkSize;
95     };
96 
97     //==============================================================================
98     struct AudioDescriptionChunk
99     {
AudioDescriptionChunkjuce::CoreAudioFormatMetatdata::AudioDescriptionChunk100         AudioDescriptionChunk (InputStream& input)
101         {
102             sampleRate          = input.readDoubleBigEndian();
103             formatID            = (uint32) input.readIntBigEndian();
104             formatFlags         = (uint32) input.readIntBigEndian();
105             bytesPerPacket      = (uint32) input.readIntBigEndian();
106             framesPerPacket     = (uint32) input.readIntBigEndian();
107             channelsPerFrame    = (uint32) input.readIntBigEndian();
108             bitsPerChannel      = (uint32) input.readIntBigEndian();
109         }
110 
111         double sampleRate;
112         uint32 formatID;
113         uint32 formatFlags;
114         uint32 bytesPerPacket;
115         uint32 framesPerPacket;
116         uint32 channelsPerFrame;
117         uint32 bitsPerChannel;
118     };
119 
120     //==============================================================================
parseUserDefinedChunkjuce::CoreAudioFormatMetatdata121     static StringPairArray parseUserDefinedChunk (InputStream& input, int64 size)
122     {
123         StringPairArray infoStrings;
124         auto originalPosition = input.getPosition();
125 
126         uint8 uuid[16];
127         input.read (uuid, sizeof (uuid));
128 
129         if (memcmp (uuid, "\x29\x81\x92\x73\xB5\xBF\x4A\xEF\xB7\x8D\x62\xD1\xEF\x90\xBB\x2C", 16) == 0)
130         {
131             auto numEntries = (uint32) input.readIntBigEndian();
132 
133             for (uint32 i = 0; i < numEntries && input.getPosition() < originalPosition + size; ++i)
134             {
135                 String keyName = input.readString();
136                 infoStrings.set (keyName, input.readString());
137             }
138         }
139 
140         input.setPosition (originalPosition + size);
141         return infoStrings;
142     }
143 
144     //==============================================================================
parseMidiChunkjuce::CoreAudioFormatMetatdata145     static StringPairArray parseMidiChunk (InputStream& input, int64 size)
146     {
147         auto originalPosition = input.getPosition();
148 
149         MemoryBlock midiBlock;
150         input.readIntoMemoryBlock (midiBlock, (ssize_t) size);
151         MemoryInputStream midiInputStream (midiBlock, false);
152 
153         StringPairArray midiMetadata;
154         MidiFile midiFile;
155 
156         if (midiFile.readFrom (midiInputStream))
157         {
158             midiMetadata.set (CoreAudioFormat::midiDataBase64, midiBlock.toBase64Encoding());
159 
160             findTempoEvents (midiFile, midiMetadata);
161             findTimeSigEvents (midiFile, midiMetadata);
162             findKeySigEvents (midiFile, midiMetadata);
163         }
164 
165         input.setPosition (originalPosition + size);
166         return midiMetadata;
167     }
168 
findTempoEventsjuce::CoreAudioFormatMetatdata169     static void findTempoEvents (MidiFile& midiFile, StringPairArray& midiMetadata)
170     {
171         MidiMessageSequence tempoEvents;
172         midiFile.findAllTempoEvents (tempoEvents);
173 
174         auto numTempoEvents = tempoEvents.getNumEvents();
175         MemoryOutputStream tempoSequence;
176 
177         for (int i = 0; i < numTempoEvents; ++i)
178         {
179             auto tempo = getTempoFromTempoMetaEvent (tempoEvents.getEventPointer (i));
180 
181             if (tempo > 0.0)
182             {
183                 if (i == 0)
184                     midiMetadata.set (CoreAudioFormat::tempo, String (tempo));
185 
186                 if (numTempoEvents > 1)
187                     tempoSequence << String (tempo) << ',' << tempoEvents.getEventTime (i) << ';';
188             }
189         }
190 
191         if (tempoSequence.getDataSize() > 0)
192             midiMetadata.set ("tempo sequence", tempoSequence.toUTF8());
193     }
194 
getTempoFromTempoMetaEventjuce::CoreAudioFormatMetatdata195     static double getTempoFromTempoMetaEvent (MidiMessageSequence::MidiEventHolder* holder)
196     {
197         if (holder != nullptr)
198         {
199             auto& midiMessage = holder->message;
200 
201             if (midiMessage.isTempoMetaEvent())
202             {
203                 auto tempoSecondsPerQuarterNote = midiMessage.getTempoSecondsPerQuarterNote();
204 
205                 if (tempoSecondsPerQuarterNote > 0.0)
206                     return 60.0 / tempoSecondsPerQuarterNote;
207             }
208         }
209 
210         return 0.0;
211     }
212 
findTimeSigEventsjuce::CoreAudioFormatMetatdata213     static void findTimeSigEvents (MidiFile& midiFile, StringPairArray& midiMetadata)
214     {
215         MidiMessageSequence timeSigEvents;
216         midiFile.findAllTimeSigEvents (timeSigEvents);
217         auto numTimeSigEvents = timeSigEvents.getNumEvents();
218 
219         MemoryOutputStream timeSigSequence;
220 
221         for (int i = 0; i < numTimeSigEvents; ++i)
222         {
223             int numerator, denominator;
224             timeSigEvents.getEventPointer(i)->message.getTimeSignatureInfo (numerator, denominator);
225 
226             String timeSigString;
227             timeSigString << numerator << '/' << denominator;
228 
229             if (i == 0)
230                 midiMetadata.set (CoreAudioFormat::timeSig, timeSigString);
231 
232             if (numTimeSigEvents > 1)
233                 timeSigSequence << timeSigString << ',' << timeSigEvents.getEventTime (i) << ';';
234         }
235 
236         if (timeSigSequence.getDataSize() > 0)
237             midiMetadata.set ("time signature sequence", timeSigSequence.toUTF8());
238     }
239 
findKeySigEventsjuce::CoreAudioFormatMetatdata240     static void findKeySigEvents (MidiFile& midiFile, StringPairArray& midiMetadata)
241     {
242         MidiMessageSequence keySigEvents;
243         midiFile.findAllKeySigEvents (keySigEvents);
244         auto numKeySigEvents = keySigEvents.getNumEvents();
245 
246         MemoryOutputStream keySigSequence;
247 
248         for (int i = 0; i < numKeySigEvents; ++i)
249         {
250             auto& message (keySigEvents.getEventPointer (i)->message);
251             auto key = jlimit (0, 14, message.getKeySignatureNumberOfSharpsOrFlats() + 7);
252             bool isMajor = message.isKeySignatureMajorKey();
253 
254             static const char* majorKeys[] = { "Cb", "Gb", "Db", "Ab", "Eb", "Bb", "F", "C", "G", "D", "A", "E", "B", "F#", "C#" };
255             static const char* minorKeys[] = { "Ab", "Eb", "Bb", "F", "C", "G", "D", "A", "E", "B", "F#", "C#", "G#", "D#", "A#" };
256 
257             String keySigString (isMajor ? majorKeys[key]
258                                          : minorKeys[key]);
259 
260             if (! isMajor)
261                 keySigString << 'm';
262 
263             if (i == 0)
264                 midiMetadata.set (CoreAudioFormat::keySig, keySigString);
265 
266             if (numKeySigEvents > 1)
267                 keySigSequence << keySigString << ',' << keySigEvents.getEventTime (i) << ';';
268         }
269 
270         if (keySigSequence.getDataSize() > 0)
271             midiMetadata.set ("key signature sequence", keySigSequence.toUTF8());
272     }
273 
274     //==============================================================================
parseInformationChunkjuce::CoreAudioFormatMetatdata275     static StringPairArray parseInformationChunk (InputStream& input)
276     {
277         StringPairArray infoStrings;
278         auto numEntries = (uint32) input.readIntBigEndian();
279 
280         for (uint32 i = 0; i < numEntries; ++i)
281             infoStrings.set (input.readString(), input.readString());
282 
283         return infoStrings;
284     }
285 
286     //==============================================================================
readjuce::CoreAudioFormatMetatdata287     static bool read (InputStream& input, StringPairArray& metadataValues)
288     {
289         auto originalPos = input.getPosition();
290 
291         const FileHeader cafFileHeader (input);
292         const bool isCafFile = cafFileHeader.fileType == chunkName ("caff");
293 
294         if (isCafFile)
295         {
296             while (! input.isExhausted())
297             {
298                 const ChunkHeader chunkHeader (input);
299 
300                 if (chunkHeader.chunkType == chunkName ("desc"))
301                 {
302                     AudioDescriptionChunk audioDescriptionChunk (input);
303                 }
304                 else if (chunkHeader.chunkType == chunkName ("uuid"))
305                 {
306                     metadataValues.addArray (parseUserDefinedChunk (input, chunkHeader.chunkSize));
307                 }
308                 else if (chunkHeader.chunkType == chunkName ("data"))
309                 {
310                     // -1 signifies an unknown data size so the data has to be at the
311                     // end of the file so we must have finished the header
312 
313                     if (chunkHeader.chunkSize == -1)
314                         break;
315 
316                     input.setPosition (input.getPosition() + chunkHeader.chunkSize);
317                 }
318                 else if (chunkHeader.chunkType == chunkName ("midi"))
319                 {
320                     metadataValues.addArray (parseMidiChunk (input, chunkHeader.chunkSize));
321                 }
322                 else if (chunkHeader.chunkType == chunkName ("info"))
323                 {
324                     metadataValues.addArray (parseInformationChunk (input));
325                 }
326                 else
327                 {
328                     // we aren't decoding this chunk yet so just skip over it
329                     input.setPosition (input.getPosition() + chunkHeader.chunkSize);
330                 }
331             }
332         }
333 
334         input.setPosition (originalPos);
335 
336         return isCafFile;
337     }
338 };
339 
340 //==============================================================================
341 class CoreAudioReader : public AudioFormatReader
342 {
343 public:
CoreAudioReader(InputStream * inp)344     CoreAudioReader (InputStream* inp)  : AudioFormatReader (inp, coreAudioFormatName)
345     {
346         usesFloatingPointData = true;
347         bitsPerSample = 32;
348 
349         if (input != nullptr)
350             CoreAudioFormatMetatdata::read (*input, metadataValues);
351 
352         auto status = AudioFileOpenWithCallbacks (this,
353                                                   &readCallback,
354                                                   nullptr,  // write needs to be null to avoid permissions errors
355                                                   &getSizeCallback,
356                                                   nullptr,  // setSize needs to be null to avoid permissions errors
357                                                   0,        // AudioFileTypeID inFileTypeHint
358                                                   &audioFileID);
359         if (status == noErr)
360         {
361             status = ExtAudioFileWrapAudioFileID (audioFileID, false, &audioFileRef);
362 
363             if (status == noErr)
364             {
365                 AudioStreamBasicDescription sourceAudioFormat;
366                 UInt32 audioStreamBasicDescriptionSize = sizeof (AudioStreamBasicDescription);
367                 ExtAudioFileGetProperty (audioFileRef,
368                                          kExtAudioFileProperty_FileDataFormat,
369                                          &audioStreamBasicDescriptionSize,
370                                          &sourceAudioFormat);
371 
372                 numChannels = sourceAudioFormat.mChannelsPerFrame;
373                 sampleRate  = sourceAudioFormat.mSampleRate;
374 
375                 UInt32 sizeOfLengthProperty = sizeof (int64);
376                 ExtAudioFileGetProperty (audioFileRef,
377                                          kExtAudioFileProperty_FileLengthFrames,
378                                          &sizeOfLengthProperty,
379                                          &lengthInSamples);
380 
381                 HeapBlock<AudioChannelLayout> caLayout;
382                 bool hasLayout = false;
383                 UInt32 sizeOfLayout = 0, isWritable = 0;
384 
385                 status = AudioFileGetPropertyInfo (audioFileID, kAudioFilePropertyChannelLayout, &sizeOfLayout, &isWritable);
386 
387                 if (status == noErr && sizeOfLayout >= (sizeof (AudioChannelLayout) - sizeof (AudioChannelDescription)))
388                 {
389                     caLayout.malloc (1, static_cast<size_t> (sizeOfLayout));
390 
391                     status = AudioFileGetProperty (audioFileID, kAudioFilePropertyChannelLayout,
392                                                    &sizeOfLayout, caLayout.get());
393 
394                     if (status == noErr)
395                     {
396                         auto fileLayout = CoreAudioLayouts::fromCoreAudio (*caLayout.get());
397 
398                         if (fileLayout.size() == static_cast<int> (numChannels))
399                         {
400                             hasLayout = true;
401                             channelSet = fileLayout;
402                         }
403                     }
404                 }
405 
406                 destinationAudioFormat.mSampleRate       = sampleRate;
407                 destinationAudioFormat.mFormatID         = kAudioFormatLinearPCM;
408                 destinationAudioFormat.mFormatFlags      = kLinearPCMFormatFlagIsFloat | kLinearPCMFormatFlagIsNonInterleaved | kAudioFormatFlagsNativeEndian;
409                 destinationAudioFormat.mBitsPerChannel   = sizeof (float) * 8;
410                 destinationAudioFormat.mChannelsPerFrame = numChannels;
411                 destinationAudioFormat.mBytesPerFrame    = sizeof (float);
412                 destinationAudioFormat.mFramesPerPacket  = 1;
413                 destinationAudioFormat.mBytesPerPacket   = destinationAudioFormat.mFramesPerPacket * destinationAudioFormat.mBytesPerFrame;
414 
415                 status = ExtAudioFileSetProperty (audioFileRef,
416                                                   kExtAudioFileProperty_ClientDataFormat,
417                                                   sizeof (AudioStreamBasicDescription),
418                                                   &destinationAudioFormat);
419                 if (status == noErr)
420                 {
421                     bufferList.malloc (1, sizeof (AudioBufferList) + numChannels * sizeof (::AudioBuffer));
422                     bufferList->mNumberBuffers = numChannels;
423                     channelMap.malloc (numChannels);
424 
425                     if (hasLayout && caLayout != nullptr)
426                     {
427                         auto caOrder = CoreAudioLayouts::getCoreAudioLayoutChannels (*caLayout);
428 
429                         for (int i = 0; i < static_cast<int> (numChannels); ++i)
430                         {
431                             auto idx = channelSet.getChannelIndexForType (caOrder.getReference (i));
432                             jassert (isPositiveAndBelow (idx, static_cast<int> (numChannels)));
433 
434                             channelMap[i] = idx;
435                         }
436                     }
437                     else
438                     {
439                         for (int i = 0; i < static_cast<int> (numChannels); ++i)
440                             channelMap[i] = i;
441                     }
442 
443                     ok = true;
444                 }
445             }
446         }
447     }
448 
~CoreAudioReader()449     ~CoreAudioReader() override
450     {
451         ExtAudioFileDispose (audioFileRef);
452         AudioFileClose (audioFileID);
453     }
454 
455     //==============================================================================
readSamples(int ** destSamples,int numDestChannels,int startOffsetInDestBuffer,int64 startSampleInFile,int numSamples)456     bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
457                       int64 startSampleInFile, int numSamples) override
458     {
459         clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
460                                            startSampleInFile, numSamples, lengthInSamples);
461 
462         if (numSamples <= 0)
463             return true;
464 
465         if (lastReadPosition != startSampleInFile)
466         {
467             OSStatus status = ExtAudioFileSeek (audioFileRef, startSampleInFile);
468             if (status != noErr)
469                 return false;
470 
471             lastReadPosition = startSampleInFile;
472         }
473 
474         while (numSamples > 0)
475         {
476             auto numThisTime = jmin (8192, numSamples);
477             auto numBytes = (size_t) numThisTime * sizeof (float);
478 
479             audioDataBlock.ensureSize (numBytes * numChannels, false);
480             auto* data = static_cast<float*> (audioDataBlock.getData());
481 
482             for (int j = (int) numChannels; --j >= 0;)
483             {
484                 bufferList->mBuffers[j].mNumberChannels = 1;
485                 bufferList->mBuffers[j].mDataByteSize = (UInt32) numBytes;
486                 bufferList->mBuffers[j].mData = data;
487                 data += numThisTime;
488             }
489 
490             auto numFramesToRead = (UInt32) numThisTime;
491             auto status = ExtAudioFileRead (audioFileRef, &numFramesToRead, bufferList);
492 
493             if (status != noErr)
494                 return false;
495 
496             if (numFramesToRead == 0)
497                 break;
498 
499             if ((int) numFramesToRead < numThisTime)
500             {
501                 numThisTime = (int) numFramesToRead;
502                 numBytes    = (size_t) numThisTime * sizeof (float);
503             }
504 
505             for (int i = numDestChannels; --i >= 0;)
506             {
507                 auto* dest = destSamples[(i < (int) numChannels ? channelMap[i] : i)];
508 
509                 if (dest != nullptr)
510                 {
511                     if (i < (int) numChannels)
512                         memcpy (dest + startOffsetInDestBuffer, bufferList->mBuffers[i].mData, numBytes);
513                     else
514                         zeromem (dest + startOffsetInDestBuffer, numBytes);
515                 }
516             }
517 
518             startOffsetInDestBuffer += numThisTime;
519             numSamples -= numThisTime;
520             lastReadPosition += numThisTime;
521         }
522 
523         return true;
524     }
525 
getChannelLayout()526     AudioChannelSet getChannelLayout() override
527     {
528         if (channelSet.size() == static_cast<int> (numChannels))
529             return channelSet;
530 
531         return AudioFormatReader::getChannelLayout();
532     }
533 
534     bool ok = false;
535 
536 private:
537     AudioFileID audioFileID;
538     ExtAudioFileRef audioFileRef;
539     AudioChannelSet channelSet;
540     AudioStreamBasicDescription destinationAudioFormat;
541     MemoryBlock audioDataBlock;
542     HeapBlock<AudioBufferList> bufferList;
543     int64 lastReadPosition = 0;
544     HeapBlock<int> channelMap;
545 
getSizeCallback(void * inClientData)546     static SInt64 getSizeCallback (void* inClientData)
547     {
548         return static_cast<CoreAudioReader*> (inClientData)->input->getTotalLength();
549     }
550 
readCallback(void * inClientData,SInt64 inPosition,UInt32 requestCount,void * buffer,UInt32 * actualCount)551     static OSStatus readCallback (void* inClientData, SInt64 inPosition, UInt32 requestCount,
552                                   void* buffer, UInt32* actualCount)
553     {
554         auto* reader = static_cast<CoreAudioReader*> (inClientData);
555         reader->input->setPosition (inPosition);
556         *actualCount = (UInt32) reader->input->read (buffer, (int) requestCount);
557         return noErr;
558     }
559 
560     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreAudioReader)
561 };
562 
563 //==============================================================================
CoreAudioFormat()564 CoreAudioFormat::CoreAudioFormat()
565     : AudioFormat (coreAudioFormatName, findFileExtensionsForCoreAudioCodecs())
566 {
567 }
568 
~CoreAudioFormat()569 CoreAudioFormat::~CoreAudioFormat() {}
570 
getPossibleSampleRates()571 Array<int> CoreAudioFormat::getPossibleSampleRates()    { return {}; }
getPossibleBitDepths()572 Array<int> CoreAudioFormat::getPossibleBitDepths()      { return {}; }
573 
canDoStereo()574 bool CoreAudioFormat::canDoStereo()     { return true; }
canDoMono()575 bool CoreAudioFormat::canDoMono()       { return true; }
576 
577 //==============================================================================
createReaderFor(InputStream * sourceStream,bool deleteStreamIfOpeningFails)578 AudioFormatReader* CoreAudioFormat::createReaderFor (InputStream* sourceStream,
579                                                      bool deleteStreamIfOpeningFails)
580 {
581     std::unique_ptr<CoreAudioReader> r (new CoreAudioReader (sourceStream));
582 
583     if (r->ok)
584         return r.release();
585 
586     if (! deleteStreamIfOpeningFails)
587         r->input = nullptr;
588 
589     return nullptr;
590 }
591 
createWriterFor(OutputStream *,double,unsigned int,int,const StringPairArray &,int)592 AudioFormatWriter* CoreAudioFormat::createWriterFor (OutputStream*,
593                                                      double /*sampleRateToUse*/,
594                                                      unsigned int /*numberOfChannels*/,
595                                                      int /*bitsPerSample*/,
596                                                      const StringPairArray& /*metadataValues*/,
597                                                      int /*qualityOptionIndex*/)
598 {
599     jassertfalse; // not yet implemented!
600     return nullptr;
601 }
602 
603 
604 //==============================================================================
605 //==============================================================================
606 #if JUCE_UNIT_TESTS
607 
608 #define DEFINE_CHANNEL_LAYOUT_DFL_ENTRY(x) CoreAudioChannelLayoutTag { x, #x, AudioChannelSet() }
609 #define DEFINE_CHANNEL_LAYOUT_TAG_ENTRY(x, y) CoreAudioChannelLayoutTag { x, #x, y }
610 
611 class CoreAudioLayoutsUnitTest  : public UnitTest
612 {
613 public:
CoreAudioLayoutsUnitTest()614     CoreAudioLayoutsUnitTest()
615         : UnitTest ("Core Audio Layout <-> JUCE channel layout conversion", UnitTestCategories::audio)
616     {}
617 
618     // some ambisonic tags which are not explicitly defined
619     enum
620     {
621         kAudioChannelLayoutTag_HOA_ACN_SN3D_0Order = (190U<<16) | 1,
622         kAudioChannelLayoutTag_HOA_ACN_SN3D_1Order = (190U<<16) | 4,
623         kAudioChannelLayoutTag_HOA_ACN_SN3D_2Order = (190U<<16) | 9,
624         kAudioChannelLayoutTag_HOA_ACN_SN3D_3Order = (190U<<16) | 16,
625         kAudioChannelLayoutTag_HOA_ACN_SN3D_4Order = (190U<<16) | 25,
626         kAudioChannelLayoutTag_HOA_ACN_SN3D_5Order = (190U<<16) | 36
627     };
628 
runTest()629     void runTest() override
630     {
631         auto& knownTags = getAllKnownLayoutTags();
632 
633         {
634             // Check that all known tags defined in CoreAudio SDK version 10.12.4 are known to JUCE
635             // Include all defined tags even if there are duplicates as Apple will sometimes change
636             // definitions
637             beginTest ("All CA tags handled");
638 
639             for (auto tagEntry : knownTags)
640             {
641                 auto labels = CoreAudioLayouts::fromCoreAudio (tagEntry.tag);
642 
643                 expect (! labels.isDiscreteLayout(), "Tag \"" + String (tagEntry.name) + "\" is not handled by JUCE");
644             }
645         }
646 
647         {
648             beginTest ("Number of speakers");
649 
650             for (auto tagEntry : knownTags)
651             {
652                 auto labels = CoreAudioLayouts::getSpeakerLayoutForCoreAudioTag (tagEntry.tag);
653 
654                 expect (labels.size() == (tagEntry.tag & 0xffff), "Tag \"" + String (tagEntry.name) + "\" has incorrect channel count");
655             }
656         }
657 
658         {
659             beginTest ("No duplicate speaker");
660 
661             for (auto tagEntry : knownTags)
662             {
663                 auto labels = CoreAudioLayouts::getSpeakerLayoutForCoreAudioTag (tagEntry.tag);
664                 labels.sort();
665 
666                 for (int i = 0; i < (labels.size() - 1); ++i)
667                     expect (labels.getReference (i) != labels.getReference (i + 1),
668                             "Tag \"" + String (tagEntry.name) + "\" has the same speaker twice");
669             }
670         }
671 
672         {
673             beginTest ("CA speaker list and juce layouts are consistent");
674 
675             for (auto tagEntry : knownTags)
676                 expect (AudioChannelSet::channelSetWithChannels (CoreAudioLayouts::getSpeakerLayoutForCoreAudioTag (tagEntry.tag))
677                             == CoreAudioLayouts::fromCoreAudio (tagEntry.tag),
678                         "Tag \"" + String (tagEntry.name) + "\" is not converted consistently by JUCE");
679         }
680 
681         {
682             beginTest ("AudioChannelSet documentation is correct");
683 
684             for (auto tagEntry : knownTags)
685             {
686                 if (tagEntry.equivalentChannelSet.isDisabled())
687                     continue;
688 
689                 expect (CoreAudioLayouts::fromCoreAudio (tagEntry.tag) == tagEntry.equivalentChannelSet,
690                         "Documentation for tag \"" + String (tagEntry.name) + "\" is incorrect");
691             }
692         }
693 
694         {
695             beginTest ("CA tag reverse conversion");
696 
697             for (auto tagEntry : knownTags)
698             {
699                 if (tagEntry.equivalentChannelSet.isDisabled())
700                     continue;
701 
702                 expect (CoreAudioLayouts::toCoreAudio (tagEntry.equivalentChannelSet) == tagEntry.tag,
703                         "Incorrect reverse conversion for tag \"" + String (tagEntry.name) + "\"");
704             }
705         }
706     }
707 
708 private:
709     struct CoreAudioChannelLayoutTag
710     {
711         AudioChannelLayoutTag tag;
712         const char* name;
713         AudioChannelSet equivalentChannelSet; /* referred to this in the AudioChannelSet documentation */
714     };
715 
716     //==============================================================================
getAllKnownLayoutTags() const717     const Array<CoreAudioChannelLayoutTag>& getAllKnownLayoutTags() const
718     {
719         static CoreAudioChannelLayoutTag tags[] = {
720             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_Mono,   AudioChannelSet::mono()),
721             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_Stereo, AudioChannelSet::stereo()),
722             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_StereoHeadphones),
723             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_MatrixStereo),
724             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_MidSide),
725             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_XY),
726             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_Binaural),
727             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_Ambisonic_B_Format),
728             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_Quadraphonic, AudioChannelSet::quadraphonic()),
729             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_Pentagonal, AudioChannelSet::pentagonal()),
730             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_Hexagonal, AudioChannelSet::hexagonal()),
731             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_Octagonal, AudioChannelSet::octagonal()),
732             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_Cube),
733             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_MPEG_1_0),
734             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_MPEG_2_0),
735             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_MPEG_3_0_A, AudioChannelSet::createLCR()),
736             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_MPEG_3_0_B),
737             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_MPEG_4_0_A, AudioChannelSet::createLCRS()),
738             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_MPEG_4_0_B),
739             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_MPEG_5_0_A, AudioChannelSet::create5point0()),
740             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_MPEG_5_0_B),
741             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_MPEG_5_0_C),
742             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_MPEG_5_0_D),
743             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_MPEG_5_1_A, AudioChannelSet::create5point1()),
744             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_MPEG_5_1_B),
745             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_MPEG_5_1_C),
746             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_MPEG_5_1_D),
747             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_MPEG_6_1_A, AudioChannelSet::create6point1()),
748             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_MPEG_7_1_A, AudioChannelSet::create7point1SDDS()),
749             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_MPEG_7_1_B),
750             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_MPEG_7_1_C, AudioChannelSet::create7point1()),
751             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_Emagic_Default_7_1),
752             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_SMPTE_DTV),
753             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_ITU_1_0),
754             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_ITU_2_0),
755             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_ITU_2_1, AudioChannelSet::createLRS()),
756             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_ITU_2_2),
757             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_ITU_3_0),
758             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_ITU_3_1),
759             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_ITU_3_2),
760             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_ITU_3_2_1),
761             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_ITU_3_4_1),
762             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DVD_0),
763             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DVD_1),
764             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DVD_2),
765             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DVD_3),
766             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DVD_4),
767             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DVD_5),
768             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DVD_6),
769             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DVD_7),
770             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DVD_8),
771             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DVD_9),
772             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DVD_10),
773             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DVD_11),
774             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DVD_12),
775             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DVD_13),
776             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DVD_14),
777             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DVD_15),
778             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DVD_16),
779             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DVD_17),
780             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DVD_18),
781             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DVD_19),
782             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DVD_20),
783             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AudioUnit_4),
784             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AudioUnit_5),
785             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AudioUnit_6),
786             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AudioUnit_8),
787             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AudioUnit_5_0),
788             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_AudioUnit_6_0, AudioChannelSet::create6point0()),
789             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_AudioUnit_7_0, AudioChannelSet::create7point0()),
790             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_AudioUnit_7_0_Front, AudioChannelSet::create7point0SDDS()),
791             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AudioUnit_5_1),
792             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AudioUnit_6_1),
793             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AudioUnit_7_1),
794             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AudioUnit_7_1_Front),
795             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AAC_3_0),
796             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AAC_Quadraphonic),
797             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AAC_4_0),
798             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AAC_5_0),
799             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AAC_5_1),
800             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AAC_6_0),
801             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AAC_6_1),
802             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AAC_7_0),
803             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AAC_7_1),
804             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AAC_7_1_B),
805             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AAC_7_1_C),
806             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AAC_Octagonal),
807             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_TMH_10_2_std),
808             // DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_TMH_10_2_full), no indication on how to handle this tag
809             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AC3_1_0_1),
810             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AC3_3_0),
811             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AC3_3_1),
812             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AC3_3_0_1),
813             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AC3_2_1_1),
814             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_AC3_3_1_1),
815             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_EAC_6_0_A),
816             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_EAC_7_0_A),
817             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_EAC3_6_1_A),
818             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_EAC3_6_1_B),
819             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_EAC3_6_1_C),
820             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_EAC3_7_1_A),
821             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_EAC3_7_1_B),
822             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_EAC3_7_1_C),
823             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_EAC3_7_1_D),
824             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_EAC3_7_1_E),
825             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_EAC3_7_1_F),
826             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_EAC3_7_1_G),
827             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_EAC3_7_1_H),
828             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DTS_3_1),
829             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DTS_4_1),
830             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_DTS_6_0_A, AudioChannelSet::create6point0Music()),
831             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DTS_6_0_B),
832             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DTS_6_0_C),
833             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_DTS_6_1_A, AudioChannelSet::create6point1Music()),
834             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DTS_6_1_B),
835             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DTS_6_1_C),
836             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DTS_7_0),
837             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DTS_7_1),
838             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DTS_8_0_A),
839             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DTS_8_0_B),
840             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DTS_8_1_A),
841             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DTS_8_1_B),
842             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DTS_6_1_D),
843             DEFINE_CHANNEL_LAYOUT_DFL_ENTRY (kAudioChannelLayoutTag_DTS_6_1_D),
844             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_HOA_ACN_SN3D_0Order,  AudioChannelSet::ambisonic (0)),
845             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_HOA_ACN_SN3D_1Order,  AudioChannelSet::ambisonic (1)),
846             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_HOA_ACN_SN3D_2Order,  AudioChannelSet::ambisonic (2)),
847             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_HOA_ACN_SN3D_3Order, AudioChannelSet::ambisonic (3)),
848             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_HOA_ACN_SN3D_4Order, AudioChannelSet::ambisonic (4)),
849             DEFINE_CHANNEL_LAYOUT_TAG_ENTRY (kAudioChannelLayoutTag_HOA_ACN_SN3D_5Order, AudioChannelSet::ambisonic (5))
850         };
851         static Array<CoreAudioChannelLayoutTag> knownTags (tags, sizeof (tags) / sizeof (CoreAudioChannelLayoutTag));
852 
853         return knownTags;
854     }
855 };
856 
857 static CoreAudioLayoutsUnitTest coreAudioLayoutsUnitTest;
858 
859 #endif
860 
861 } // namespace juce
862 
863 #endif
864