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