OpenShot Audio Library | OpenShotAudio  0.6.0
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 
31 namespace juce
32 {
33 
34 //==============================================================================
35 namespace
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 //==============================================================================
105 const char* const CoreAudioFormat::midiDataBase64 = "midiDataBase64";
106 const char* const CoreAudioFormat::tempo = "tempo";
107 const char* const CoreAudioFormat::timeSig = "time signature";
108 const char* const CoreAudioFormat::keySig = "key signature";
109 
110 //==============================================================================
111 struct 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 //==============================================================================
387 class CoreAudioReader final : public AudioFormatReader
388 {
389 public:
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 
581  }
582 
583  bool ok = false;
584 
585 private:
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 //==============================================================================
613 CoreAudioFormat::CoreAudioFormat()
614  : AudioFormat (coreAudioFormatName, findFileExtensionsForCoreAudioCodecs()),
615  streamKind (StreamKind::kNone)
616 {
617 }
618 
619 CoreAudioFormat::CoreAudioFormat (StreamKind kind)
620  : AudioFormat (coreAudioFormatName, findFileExtensionsForCoreAudioCodec (toAudioFileTypeID (kind))),
621  streamKind (kind)
622 {
623 }
624 
625 CoreAudioFormat::~CoreAudioFormat() = default;
626 
627 Array<int> CoreAudioFormat::getPossibleSampleRates() { return {}; }
628 Array<int> CoreAudioFormat::getPossibleBitDepths() { return {}; }
629 
630 bool CoreAudioFormat::canDoStereo() { return true; }
631 bool CoreAudioFormat::canDoMono() { return true; }
632 
633 //==============================================================================
634 AudioFormatReader* 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 
648 AudioFormatWriter* 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 
667 class CoreAudioLayoutsUnitTest final : public UnitTest
668 {
669 public:
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 
764 private:
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 
913 static CoreAudioLayoutsUnitTest coreAudioLayoutsUnitTest;
914 
915 #endif
916 
917 } // namespace juce
918 
919 #endif
virtual AudioChannelSet getChannelLayout()
constexpr static uint32 bigEndianInt(const void *bytes) noexcept