OpenShot Audio Library | OpenShotAudio: juce_CoreAudioFormat.cpp Source File

OpenShot Audio Library | OpenShotAudio 0.4.0

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