27#if JUCE_MAC || JUCE_IOS
29#include "../../juce_audio_basics/native/juce_mac_CoreAudioLayouts.h"
37 const char*
const coreAudioFormatName =
"CoreAudio supported file";
39 StringArray findFileExtensionsForCoreAudioCodecs()
41 StringArray extensionsArray;
42 CFArrayRef extensions =
nullptr;
43 UInt32 sizeOfArray =
sizeof (extensions);
45 if (AudioFileGetGlobalInfo (kAudioFileGlobalInfo_AllExtensions, 0,
nullptr, &sizeOfArray, &extensions) == noErr)
47 auto numValues = CFArrayGetCount (extensions);
49 for (CFIndex i = 0; i < numValues; ++i)
50 extensionsArray.add (
"." +
String::fromCFString ((CFStringRef) CFArrayGetValueAtIndex (extensions, i)));
52 CFRelease (extensions);
55 return extensionsArray;
128 input.
read (uuid,
sizeof (uuid));
130 if (
memcmp (uuid,
"\x29\x81\x92\x73\xB5\xBF\x4A\xEF\xB7\x8D\x62\xD1\xEF\x90\xBB\x2C", 16) == 0)
146 static StringPairArray parseMidiChunk (InputStream& input, int64 size)
148 auto originalPosition = input.getPosition();
150 MemoryBlock midiBlock;
151 input.readIntoMemoryBlock (midiBlock, (ssize_t) size);
152 MemoryInputStream midiInputStream (midiBlock,
false);
154 StringPairArray midiMetadata;
157 if (midiFile.readFrom (midiInputStream))
161 findTempoEvents (midiFile, midiMetadata);
162 findTimeSigEvents (midiFile, midiMetadata);
163 findKeySigEvents (midiFile, midiMetadata);
166 input.setPosition (originalPosition + size);
170 static void findTempoEvents (MidiFile& midiFile, StringPairArray& midiMetadata)
172 MidiMessageSequence tempoEvents;
173 midiFile.findAllTempoEvents (tempoEvents);
175 auto numTempoEvents = tempoEvents.getNumEvents();
176 MemoryOutputStream tempoSequence;
178 for (
int i = 0; i < numTempoEvents; ++i)
180 auto tempo = getTempoFromTempoMetaEvent (tempoEvents.getEventPointer (i));
187 if (numTempoEvents > 1)
188 tempoSequence << String (tempo) <<
',' << tempoEvents.getEventTime (i) <<
';';
192 if (tempoSequence.getDataSize() > 0)
193 midiMetadata.set (
"tempo sequence", tempoSequence.toUTF8());
196 static double getTempoFromTempoMetaEvent (MidiMessageSequence::MidiEventHolder* holder)
198 if (holder !=
nullptr)
200 auto& midiMessage = holder->message;
202 if (midiMessage.isTempoMetaEvent())
204 auto tempoSecondsPerQuarterNote = midiMessage.getTempoSecondsPerQuarterNote();
206 if (tempoSecondsPerQuarterNote > 0.0)
207 return 60.0 / tempoSecondsPerQuarterNote;
214 static void findTimeSigEvents (MidiFile& midiFile, StringPairArray& midiMetadata)
216 MidiMessageSequence timeSigEvents;
217 midiFile.findAllTimeSigEvents (timeSigEvents);
218 auto numTimeSigEvents = timeSigEvents.getNumEvents();
220 MemoryOutputStream timeSigSequence;
222 for (
int i = 0; i < numTimeSigEvents; ++i)
224 int numerator, denominator;
225 timeSigEvents.getEventPointer(i)->message.getTimeSignatureInfo (numerator, denominator);
227 String timeSigString;
228 timeSigString << numerator <<
'/' << denominator;
233 if (numTimeSigEvents > 1)
234 timeSigSequence << timeSigString <<
',' << timeSigEvents.getEventTime (i) <<
';';
237 if (timeSigSequence.getDataSize() > 0)
238 midiMetadata.set (
"time signature sequence", timeSigSequence.toUTF8());
241 static void findKeySigEvents (MidiFile& midiFile, StringPairArray& midiMetadata)
243 MidiMessageSequence keySigEvents;
244 midiFile.findAllKeySigEvents (keySigEvents);
245 auto numKeySigEvents = keySigEvents.getNumEvents();
247 MemoryOutputStream keySigSequence;
249 for (
int i = 0; i < numKeySigEvents; ++i)
251 auto& message (keySigEvents.getEventPointer (i)->message);
252 auto key = jlimit (0, 14, message.getKeySignatureNumberOfSharpsOrFlats() + 7);
253 bool isMajor = message.isKeySignatureMajorKey();
255 static const char* majorKeys[] = {
"Cb",
"Gb",
"Db",
"Ab",
"Eb",
"Bb",
"F",
"C",
"G",
"D",
"A",
"E",
"B",
"F#",
"C#" };
256 static const char* minorKeys[] = {
"Ab",
"Eb",
"Bb",
"F",
"C",
"G",
"D",
"A",
"E",
"B",
"F#",
"C#",
"G#",
"D#",
"A#" };
258 String keySigString (isMajor ? majorKeys[key]
267 if (numKeySigEvents > 1)
268 keySigSequence << keySigString <<
',' << keySigEvents.getEventTime (i) <<
';';
271 if (keySigSequence.getDataSize() > 0)
272 midiMetadata.set (
"key signature sequence", keySigSequence.toUTF8());
276 static StringPairArray parseInformationChunk (InputStream& input)
278 StringPairArray infoStrings;
279 auto numEntries = (uint32) input.readIntBigEndian();
281 for (uint32 i = 0; i < numEntries; ++i)
282 infoStrings.
set (input.readString(), input.readString());
288 static bool read (InputStream& input, StringPairArray& metadataValues)
290 auto originalPos = input.getPosition();
292 const FileHeader cafFileHeader (input);
293 const bool isCafFile = cafFileHeader.fileType == chunkName (
"caff");
297 while (! input.isExhausted())
299 const ChunkHeader chunkHeader (input);
301 if (chunkHeader.chunkType == chunkName (
"desc"))
303 AudioDescriptionChunk audioDescriptionChunk (input);
305 else if (chunkHeader.chunkType == chunkName (
"uuid"))
307 metadataValues.addArray (parseUserDefinedChunk (input, chunkHeader.chunkSize));
309 else if (chunkHeader.chunkType == chunkName (
"data"))
314 if (chunkHeader.chunkSize == -1)
317 input.setPosition (input.getPosition() + chunkHeader.chunkSize);
319 else if (chunkHeader.chunkType == chunkName (
"midi"))
321 metadataValues.addArray (parseMidiChunk (input, chunkHeader.chunkSize));
323 else if (chunkHeader.chunkType == chunkName (
"info"))
325 metadataValues.addArray (parseInformationChunk (input));
330 input.setPosition (input.getPosition() + chunkHeader.chunkSize);
335 input.setPosition (originalPos);
350 if (
input !=
nullptr)
407 destinationAudioFormat.mSampleRate =
sampleRate;
410 destinationAudioFormat.mBitsPerChannel =
sizeof (
float) * 8;
411 destinationAudioFormat.mChannelsPerFrame =
numChannels;
412 destinationAudioFormat.mBytesPerFrame =
sizeof (
float);
413 destinationAudioFormat.mFramesPerPacket = 1;
414 destinationAudioFormat.mBytesPerPacket = destinationAudioFormat.mFramesPerPacket * destinationAudioFormat.mBytesPerFrame;
419 &destinationAudioFormat);
428 auto caOrder = CoreAudioLayouts::getCoreAudioLayoutChannels (*
caLayout);
433 jassert (isPositiveAndBelow (
idx,
static_cast<int> (
numChannels)));
475 while (numSamples > 0)
481 auto* data =
static_cast<float*
> (audioDataBlock.
getData());
485 bufferList->mBuffers[
j].mNumberChannels = 1;
486 bufferList->mBuffers[
j].mDataByteSize = (
UInt32) numBytes;
487 bufferList->mBuffers[
j].mData = data;
535 int64 lastReadPosition = 0;
543 static OSStatus readCallback (
void* inClientData, SInt64 inPosition, UInt32 requestCount,
544 void* buffer, UInt32* actualCount)
546 auto* reader =
static_cast<CoreAudioReader*
> (inClientData);
547 reader->input->setPosition (inPosition);
548 *actualCount = (UInt32) reader->input->read (buffer, (
int) requestCount);
552 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreAudioReader)
573 std::unique_ptr<CoreAudioReader> r (
new CoreAudioReader (sourceStream));
600#define DEFINE_CHANNEL_LAYOUT_DFL_ENTRY(x) CoreAudioChannelLayoutTag { x, #x, AudioChannelSet() }
601#define DEFINE_CHANNEL_LAYOUT_TAG_ENTRY(x, y) CoreAudioChannelLayoutTag { x, #x, y }
619 void runTest()
override
627 beginTest (
"All CA tags handled");
633 expect (!
labels.isDiscreteLayout(),
"Tag \"" + String (
tagEntry.name) +
"\" is not handled by JUCE");
638 beginTest (
"Number of speakers");
642 auto labels = CoreAudioLayouts::getSpeakerLayoutForCoreAudioTag (
tagEntry.tag);
644 expect (
labels.size() == (
tagEntry.tag & 0xffff),
"Tag \"" + String (
tagEntry.name) +
"\" has incorrect channel count");
649 beginTest (
"No duplicate speaker");
653 auto labels = CoreAudioLayouts::getSpeakerLayoutForCoreAudioTag (
tagEntry.tag);
656 for (
int i = 0; i < (
labels.size() - 1); ++i)
657 expect (
labels.getReference (i) !=
labels.getReference (i + 1),
658 "Tag \"" + String (
tagEntry.name) +
"\" has the same speaker twice");
663 beginTest (
"CA speaker list and juce layouts are consistent");
668 "Tag \"" + String (
tagEntry.name) +
"\" is not converted consistently by JUCE");
672 beginTest (
"AudioChannelSet documentation is correct");
676 if (
tagEntry.equivalentChannelSet.isDisabled())
679 expect (CoreAudioLayouts::fromCoreAudio (
tagEntry.tag) ==
tagEntry.equivalentChannelSet,
680 "Documentation for tag \"" + String (
tagEntry.name) +
"\" is incorrect");
685 beginTest (
"CA tag reverse conversion");
689 if (
tagEntry.equivalentChannelSet.isDisabled())
692 expect (CoreAudioLayouts::toCoreAudio (
tagEntry.equivalentChannelSet) ==
tagEntry.tag,
693 "Incorrect reverse conversion for tag \"" + String (
tagEntry.name) +
"\"");
701 AudioChannelLayoutTag tag;
703 AudioChannelSet equivalentChannelSet;
847static CoreAudioLayoutsUnitTest coreAudioLayoutsUnitTest;
Holds a resizable array of primitive or copy-by-value objects.
int size() const noexcept
Returns the current number of elements in the array.
Array()=default
Creates an empty array.
ElementType & getReference(int index) const noexcept
Returns a direct reference to one of the elements in the array, without checking the index passed in.
void set(int indexToChange, ParameterType newValue)
Replaces an element with a new value.
Represents a set of audio channel types.
static AudioChannelSet JUCE_CALLTYPE create7point1()
Creates a set for a DTS 7.1 surround setup (left, right, centre, leftSurroundSide,...
static AudioChannelSet JUCE_CALLTYPE quadraphonic()
Creates a set for quadraphonic surround setup (left, right, leftSurround, rightSurround)
static AudioChannelSet JUCE_CALLTYPE create5point0()
Creates a set for a 5.0 surround setup (left, right, centre, leftSurround, rightSurround).
int size() const noexcept
Returns the number of channels in the set.
static AudioChannelSet JUCE_CALLTYPE create6point0()
Creates a set for a 6.0 Cine surround setup (left, right, centre, leftSurround, rightSurround,...
static AudioChannelSet JUCE_CALLTYPE create6point1Music()
Creates a set for a 6.0 Music surround setup (left, right, leftSurround, rightSurround,...
static AudioChannelSet JUCE_CALLTYPE hexagonal()
Creates a set for hexagonal surround setup (left, right, leftSurroundRear, rightSurroundRear,...
static AudioChannelSet JUCE_CALLTYPE mono()
Creates a one-channel mono set (centre).
static AudioChannelSet JUCE_CALLTYPE createLRS()
Creates a set containing an LRS set (left, right, surround).
static AudioChannelSet JUCE_CALLTYPE stereo()
Creates a set containing a stereo set (left, right).
static AudioChannelSet JUCE_CALLTYPE octagonal()
Creates a set for octagonal surround setup (left, right, leftSurround, rightSurround,...
static AudioChannelSet JUCE_CALLTYPE create6point1()
Creates a set for a 6.1 Cine surround setup (left, right, centre, leftSurround, rightSurround,...
static AudioChannelSet JUCE_CALLTYPE create5point1()
Creates a set for a 5.1 surround setup (left, right, centre, leftSurround, rightSurround,...
static AudioChannelSet JUCE_CALLTYPE create7point0SDDS()
Creates a set for a SDDS 7.0 surround setup (left, right, centre, leftSurround, rightSurround,...
static AudioChannelSet JUCE_CALLTYPE create6point0Music()
Creates a set for a 6.0 Music surround setup (left, right, leftSurround, rightSurround,...
static AudioChannelSet JUCE_CALLTYPE create7point1SDDS()
Creates a set for a 7.1 surround setup (left, right, centre, leftSurround, rightSurround,...
static AudioChannelSet JUCE_CALLTYPE create7point0()
Creates a set for a DTS 7.0 surround setup (left, right, centre, leftSurroundSide,...
static AudioChannelSet JUCE_CALLTYPE ambisonic(int order=1)
Creates a set for ACN, SN3D normalised ambisonic surround setups with a given order.
static AudioChannelSet JUCE_CALLTYPE createLCRS()
Creates a set containing an LCRS set (left, right, centre, surround).
static AudioChannelSet JUCE_CALLTYPE createLCR()
Creates a set containing an LCR set (left, right, centre).
static AudioChannelSet JUCE_CALLTYPE pentagonal()
Creates a set for pentagonal surround setup (left, right, centre, leftSurroundRear,...
int getChannelIndexForType(ChannelType type) const noexcept
Returns the index for a particular channel-type.
static JUCE_CONSTEXPR uint32 bigEndianInt(const void *bytes) noexcept
Turns 4 bytes into a big-endian integer.
AudioChannelSet getChannelLayout() override
Get the channel layout of the audio stream.
bool readSamples(int **destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) override
Subclasses must implement this method to perform the low-level read operation.
void malloc(SizeType newNumElements, size_t elementSize=sizeof(ElementType))
Allocates a specified amount of memory.
A class to hold a resizable block of raw data.
void ensureSize(const size_t minimumSize, bool initialiseNewSpaceToZero=false)
Increases the block's size only if it's smaller than a given size.
void * getData() const noexcept
Returns a void pointer to the data.
The base class for streams that write data to some kind of destination.
A container for holding a set of strings which are keyed by another string.
void set(const String &key, const String &value)
Adds or amends a key/value pair.
static String fromCFString(CFStringRef cfString)
OSX ONLY - Creates a String from an OSX CFString.
This is a base class for classes that perform a unit test.