30 static const char*
const aiffFormatName =
"AIFF file";
43 namespace AiffFileHelpers
48 #pragma pack (push, 1) 57 uint16 startIdentifier;
73 values.
set (
"MidiUnityNote",
String (baseNote));
77 values.
set (
"HighNote",
String (highNote));
78 values.
set (
"LowVelocity",
String (lowVelocity));
79 values.
set (
"HighVelocity",
String (highVelocity));
83 values.
set (
"NumSampleLoops",
String (2));
92 static uint16 getValue16 (
const StringPairArray& values,
const char* name,
const char* def)
97 static int8 getValue8 (
const StringPairArray& values,
const char* name,
const char* def)
109 inst.baseNote = getValue8 (values,
"MidiUnityNote",
"60");
110 inst.detune = getValue8 (values,
"Detune",
"0");
111 inst.lowNote = getValue8 (values,
"LowNote",
"0");
112 inst.highNote = getValue8 (values,
"HighNote",
"127");
113 inst.lowVelocity = getValue8 (values,
"LowVelocity",
"1");
114 inst.highVelocity = getValue8 (values,
"HighVelocity",
"127");
115 inst.gain = (int16) getValue16 (values,
"Gain",
"0");
117 inst.sustainLoop.type = getValue16 (values,
"Loop0Type",
"0");
118 inst.sustainLoop.startIdentifier = getValue16 (values,
"Loop0StartIdentifier",
"0");
119 inst.sustainLoop.endIdentifier = getValue16 (values,
"Loop0EndIdentifier",
"0");
120 inst.releaseLoop.type = getValue16 (values,
"Loop1Type",
"0");
121 inst.releaseLoop.startIdentifier = getValue16 (values,
"Loop1StartIdentifier",
"0");
122 inst.releaseLoop.endIdentifier = getValue16 (values,
"Loop1EndIdentifier",
"0");
150 input.
read (unknown,
sizeof (unknown));
155 const bool rootNoteSet = rootNote != 0;
157 setBoolFlag (metadata, AiffAudioFormat::appleOneShot, oneShot == 2);
158 setBoolFlag (metadata, AiffAudioFormat::appleRootSet, rootNoteSet);
161 metadata.
set (AiffAudioFormat::appleRootNote,
String (rootNote));
163 metadata.
set (AiffAudioFormat::appleBeats,
String (numBeats));
164 metadata.
set (AiffAudioFormat::appleDenominator,
String (timeSigDen));
165 metadata.
set (AiffAudioFormat::appleNumerator,
String (timeSigNum));
167 const char* keyString =
nullptr;
171 case minor: keyString =
"minor";
break;
172 case major: keyString =
"major";
break;
173 case neither: keyString =
"neither";
break;
174 case both: keyString =
"both";
break;
177 if (keyString !=
nullptr)
178 metadata.
set (AiffAudioFormat::appleKey, keyString);
181 void setBoolFlag (
StringPairArray& values,
const char* name,
bool shouldBeSet)
const 183 values.
set (name, shouldBeSet ?
"1" :
"0");
203 static bool isValidTag (
const char* d) noexcept
210 static bool isAppleGenre (
const String& tag) noexcept
212 static const char* appleGenres[] =
226 for (
int i = 0; i < numElementsInArray (appleGenres); ++i)
227 if (tag == appleGenres[i])
241 auto* data =
static_cast<const char*
> (mb.
getData());
242 auto* dataEnd = data + mb.
getSize();
244 while (data < dataEnd)
246 bool isGenre =
false;
248 if (isValidTag (data))
251 isGenre = isAppleGenre (tag);
255 data += isGenre ? 118 : 50;
257 if (data < dataEnd && data[0] == 0)
259 if (data + 52 < dataEnd && isValidTag (data + 50)) data += 50;
260 else if (data + 120 < dataEnd && isValidTag (data + 118)) data += 118;
261 else if (data + 170 < dataEnd && isValidTag (data + 168)) data += 168;
272 static bool metaDataContainsZeroIdentifiers (
const StringPairArray& values)
275 const String cueString (
"Cue");
276 const String noteString (
"CueNote");
277 const String identifierString (
"Identifier");
281 if (key.startsWith (noteString))
284 if (key.startsWith (cueString) && key.contains (identifierString))
302 auto idOffset = metaDataContainsZeroIdentifiers (values) ? 1 : 0;
308 for (
int i = 0; i < numCues; ++i)
310 auto prefixCue =
"Cue" +
String (i);
311 auto identifier = idOffset + values.
getValue (prefixCue +
"Identifier",
"1").
getIntValue();
314 jassert (! identifiers.
contains (identifier));
315 identifiers.
add (identifier);
319 auto label =
"CueLabel" +
String (i);
321 for (
int labelIndex = 0; labelIndex < numCueLabels; ++labelIndex)
323 auto prefixLabel =
"CueLabel" +
String (labelIndex);
324 auto labelIdentifier = idOffset + values.
getValue (prefixLabel +
"Identifier",
"1").
getIntValue();
326 if (labelIdentifier == identifier)
328 label = values.
getValue (prefixLabel +
"Text", label);
336 auto labelLength = jmin ((
size_t) 254, label.getNumBytesAsUTF8());
338 out.
write (label.toUTF8(), labelLength);
360 for (
int i = 0; i < numNotes; ++i)
362 auto prefix =
"CueNote" +
String (i);
368 auto commentLength = jmin (comment.getNumBytesAsUTF8(), (size_t) 65534);
371 out.
write (comment.toUTF8(), commentLength);
389 using namespace AiffFileHelpers;
391 if (input->readInt() == chunkName (
"FORM"))
393 auto len = input->readIntBigEndian();
394 auto end = input->getPosition() + len;
395 auto nextType = input->readInt();
397 if (nextType == chunkName (
"AIFF") || nextType == chunkName (
"AIFC"))
399 bool hasGotVer =
false;
400 bool hasGotData =
false;
401 bool hasGotType =
false;
403 while (input->getPosition() < end)
405 auto type = input->readInt();
406 auto length = (uint32) input->readIntBigEndian();
407 auto chunkEnd = input->getPosition() + length;
409 if (type == chunkName (
"FVER"))
412 auto ver = input->readIntBigEndian();
414 if (ver != 0 && ver != (
int) 0xa2805140)
417 else if (type == chunkName (
"COMM"))
421 numChannels = (
unsigned int) input->readShortBigEndian();
422 lengthInSamples = input->readIntBigEndian();
423 bitsPerSample = (
unsigned int) input->readShortBigEndian();
424 bytesPerFrame = (int) ((numChannels * bitsPerSample) >> 3);
426 unsigned char sampleRateBytes[10];
427 input->
read (sampleRateBytes, 10);
428 const int byte0 = sampleRateBytes[0];
430 if ((byte0 & 0x80) != 0
431 || byte0 <= 0x3F || byte0 > 0x40
432 || (byte0 == 0x40 && sampleRateBytes[1] > 0x1C))
437 sampleRate = (int) sampRate;
443 littleEndian =
false;
447 auto compType = input->readInt();
449 if (compType == chunkName (
"NONE") || compType == chunkName (
"twos"))
451 littleEndian =
false;
453 else if (compType == chunkName (
"sowt"))
457 else if (compType == chunkName (
"fl32") || compType == chunkName (
"FL32"))
459 littleEndian =
false;
460 usesFloatingPointData =
true;
469 else if (type == chunkName (
"SSND"))
473 auto offset = input->readIntBigEndian();
474 dataChunkStart = input->getPosition() + 4 + offset;
475 lengthInSamples = (bytesPerFrame > 0) ? jmin (lengthInSamples, ((int64) length) / (int64) bytesPerFrame) : 0;
477 else if (type == chunkName (
"MARK"))
479 auto numCues = (uint16) input->readShortBigEndian();
482 metadataValues.set (
"NumCuePoints",
String (numCues));
483 metadataValues.set (
"NumCueLabels",
String (numCues));
485 for (uint16 i = 0; i < numCues; ++i)
487 auto identifier = (uint16) input->readShortBigEndian();
488 auto offset = (uint32) input->readIntBigEndian();
489 auto stringLength = (uint8) input->readByte();
491 input->readIntoMemoryBlock (textBlock, stringLength);
496 if ((stringLength & 1) == 0)
499 auto prefixCue =
"Cue" +
String (i);
500 metadataValues.set (prefixCue +
"Identifier",
String (identifier));
501 metadataValues.set (prefixCue +
"Offset",
String (offset));
503 auto prefixLabel =
"CueLabel" +
String (i);
504 metadataValues.set (prefixLabel +
"Identifier",
String (identifier));
505 metadataValues.set (prefixLabel +
"Text", textBlock.
toString());
508 else if (type == chunkName (
"COMT"))
510 auto numNotes = (uint16) input->readShortBigEndian();
511 metadataValues.set (
"NumCueNotes",
String (numNotes));
513 for (uint16 i = 0; i < numNotes; ++i)
515 auto timestamp = (uint32) input->readIntBigEndian();
516 auto identifier = (uint16) input->readShortBigEndian();
517 auto stringLength = (uint16) input->readShortBigEndian();
520 input->readIntoMemoryBlock (textBlock, stringLength + (stringLength & 1));
522 auto prefix =
"CueNote" +
String (i);
523 metadataValues.set (prefix +
"TimeStamp",
String (timestamp));
524 metadataValues.set (prefix +
"Identifier",
String (identifier));
525 metadataValues.set (prefix +
"Text", textBlock.
toString());
528 else if (type == chunkName (
"INST"))
531 inst.
calloc (jmax ((
size_t) length + 1,
sizeof (InstChunk)), 1);
532 input->read (inst, (
int) length);
533 inst->copyTo (metadataValues);
535 else if (type == chunkName (
"basc"))
539 else if (type == chunkName (
"cate"))
541 metadataValues.set (AiffAudioFormat::appleTag,
542 AiffFileHelpers::CATEChunk::read (*input, length));
544 else if ((hasGotVer && hasGotData && hasGotType)
545 || chunkEnd < input->getPosition()
546 || input->isExhausted())
551 input->setPosition (chunkEnd + (chunkEnd & 1));
556 if (metadataValues.size() > 0)
557 metadataValues.set (
"MetaDataSource",
"AIFF");
561 bool readSamples (
int** destSamples,
int numDestChannels,
int startOffsetInDestBuffer,
562 int64 startSampleInFile,
int numSamples)
override 564 clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
565 startSampleInFile, numSamples, lengthInSamples);
570 input->setPosition (dataChunkStart + startSampleInFile * bytesPerFrame);
572 while (numSamples > 0)
574 const int tempBufSize = 480 * 3 * 4;
575 char tempBuffer [tempBufSize];
577 const int numThisTime = jmin (tempBufSize / bytesPerFrame, numSamples);
578 const int bytesRead = input->read (tempBuffer, numThisTime * bytesPerFrame);
580 if (bytesRead < numThisTime * bytesPerFrame)
582 jassert (bytesRead >= 0);
583 zeromem (tempBuffer + bytesRead, (
size_t) (numThisTime * bytesPerFrame - bytesRead));
587 copySampleData<AudioData::LittleEndian> (bitsPerSample, usesFloatingPointData,
588 destSamples, startOffsetInDestBuffer, numDestChannels,
589 tempBuffer, (int) numChannels, numThisTime);
591 copySampleData<AudioData::BigEndian> (bitsPerSample, usesFloatingPointData,
592 destSamples, startOffsetInDestBuffer, numDestChannels,
593 tempBuffer, (int) numChannels, numThisTime);
595 startOffsetInDestBuffer += numThisTime;
596 numSamples -= numThisTime;
602 template <
typename Endianness>
603 static void copySampleData (
unsigned int numBitsPerSample,
bool floatingPointData,
604 int*
const* destSamples,
int startOffsetInDestBuffer,
int numDestChannels,
605 const void* sourceData,
int numberOfChannels,
int numSamples) noexcept
607 switch (numBitsPerSample)
615 default: jassertfalse;
break;
620 int64 dataChunkStart;
632 unsigned int numChans,
unsigned int bits,
636 using namespace AiffFileHelpers;
638 if (metadataValues.
size() > 0)
643 jassert (metadataValues.
getValue (
"MetaDataSource",
"None") !=
"WAV");
645 MarkChunk::create (markChunk, metadataValues);
646 COMTChunk::create (comtChunk, metadataValues);
647 InstChunk::create (instChunk, metadataValues);
656 if ((bytesWritten & 1) != 0)
657 output->writeByte (0);
663 bool write (
const int** data,
int numSamples)
override 665 jassert (numSamples >= 0);
666 jassert (data !=
nullptr && *data !=
nullptr);
671 auto bytes = numChannels * (size_t) numSamples * bitsPerSample / 8;
672 tempBlock.ensureSize (bytes,
false);
674 switch (bitsPerSample)
680 default: jassertfalse;
break;
683 if (bytesWritten + bytes >= (
size_t) 0xfff00000
684 || ! output->write (tempBlock.getData(), bytes))
694 bytesWritten += bytes;
695 lengthInSamples += (uint64) numSamples;
700 MemoryBlock tempBlock, markChunk, comtChunk, instChunk;
701 uint64 lengthInSamples = 0, bytesWritten = 0;
702 int64 headerPosition = 0;
703 bool writeFailed =
false;
707 using namespace AiffFileHelpers;
709 const bool couldSeekOk = output->setPosition (headerPosition);
710 ignoreUnused (couldSeekOk);
714 jassert (couldSeekOk);
716 auto headerLen = (int) (54 + (markChunk.
getSize() > 0 ? markChunk.
getSize() + 8 : 0)
719 auto audioBytes = (int) (lengthInSamples * ((bitsPerSample * numChannels) / 8));
720 audioBytes += (audioBytes & 1);
722 output->writeInt (chunkName (
"FORM"));
723 output->writeIntBigEndian (headerLen + audioBytes - 8);
724 output->writeInt (chunkName (
"AIFF"));
725 output->writeInt (chunkName (
"COMM"));
726 output->writeIntBigEndian (18);
727 output->writeShortBigEndian ((
short) numChannels);
728 output->writeIntBigEndian ((
int) lengthInSamples);
729 output->writeShortBigEndian ((
short) bitsPerSample);
731 uint8 sampleRateBytes[10] = {};
735 sampleRateBytes[0] = 0x3f;
736 sampleRateBytes[1] = 0xff;
737 sampleRateBytes[2] = 0x80;
741 int mask = 0x40000000;
742 sampleRateBytes[0] = 0x40;
744 if (sampleRate >= mask)
747 sampleRateBytes[1] = 0x1d;
751 int n = (int) sampleRate;
754 for (i = 0; i <= 32 ; ++i)
764 sampleRateBytes[1] = (uint8) (29 - i);
765 sampleRateBytes[2] = (uint8) ((n >> 24) & 0xff);
766 sampleRateBytes[3] = (uint8) ((n >> 16) & 0xff);
767 sampleRateBytes[4] = (uint8) ((n >> 8) & 0xff);
768 sampleRateBytes[5] = (uint8) (n & 0xff);
772 output->write (sampleRateBytes, 10);
776 output->writeInt (chunkName (
"MARK"));
777 output->writeIntBigEndian ((
int) markChunk.
getSize());
778 *output << markChunk;
783 output->writeInt (chunkName (
"COMT"));
784 output->writeIntBigEndian ((
int) comtChunk.
getSize());
785 *output << comtChunk;
790 output->writeInt (chunkName (
"INST"));
791 output->writeIntBigEndian ((
int) instChunk.
getSize());
792 *output << instChunk;
795 output->writeInt (chunkName (
"SSND"));
796 output->writeIntBigEndian (audioBytes + 8);
797 output->writeInt (0);
798 output->writeInt (0);
800 jassert (output->getPosition() == headerLen);
813 littleEndian (reader.littleEndian)
817 bool readSamples (
int** destSamples,
int numDestChannels,
int startOffsetInDestBuffer,
818 int64 startSampleInFile,
int numSamples)
override 820 clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
821 startSampleInFile, numSamples, lengthInSamples);
823 if (map ==
nullptr || ! mappedSection.contains (
Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
830 AiffAudioFormatReader::copySampleData<AudioData::LittleEndian>
831 (bitsPerSample, usesFloatingPointData, destSamples, startOffsetInDestBuffer,
832 numDestChannels, sampleToPointer (startSampleInFile), (int) numChannels, numSamples);
834 AiffAudioFormatReader::copySampleData<AudioData::BigEndian>
835 (bitsPerSample, usesFloatingPointData, destSamples, startOffsetInDestBuffer,
836 numDestChannels, sampleToPointer (startSampleInFile), (int) numChannels, numSamples);
841 void getSample (int64 sample,
float* result)
const noexcept
override 843 auto num = (int) numChannels;
845 if (map ==
nullptr || ! mappedSection.contains (sample))
849 zeromem (result, (
size_t) num *
sizeof (
float));
853 float** dest = &result;
854 const void* source = sampleToPointer (sample);
858 switch (bitsPerSample)
866 default: jassertfalse;
break;
871 switch (bitsPerSample)
879 default: jassertfalse;
break;
886 numSamples = jmin (numSamples, lengthInSamples - startSampleInFile);
888 if (map ==
nullptr || numSamples <= 0 || ! mappedSection.contains (
Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
890 jassert (numSamples <= 0);
892 for (
int i = 0; i < numChannelsToRead; ++i)
898 switch (bitsPerSample)
900 case 8: scanMinAndMax<AudioData::UInt8> (startSampleInFile, numSamples, results, numChannelsToRead);
break;
901 case 16: scanMinAndMax<AudioData::Int16> (startSampleInFile, numSamples, results, numChannelsToRead);
break;
902 case 24: scanMinAndMax<AudioData::Int24> (startSampleInFile, numSamples, results, numChannelsToRead);
break;
903 case 32:
if (usesFloatingPointData) scanMinAndMax<AudioData::Float32> (startSampleInFile, numSamples, results, numChannelsToRead);
904 else scanMinAndMax<AudioData::Int32> (startSampleInFile, numSamples, results, numChannelsToRead);
906 default: jassertfalse;
break;
913 const bool littleEndian;
915 template <
typename SampleType>
916 void scanMinAndMax (int64 startSampleInFile, int64 numSamples,
Range<float>* results,
int numChannelsToRead)
const noexcept
918 for (
int i = 0; i < numChannelsToRead; ++i)
919 results[i] = scanMinAndMaxForChannel<SampleType> (i, startSampleInFile, numSamples);
922 template <
typename SampleType>
923 Range<float> scanMinAndMaxForChannel (
int channel, int64 startSampleInFile, int64 numSamples)
const noexcept
925 return littleEndian ? scanMinAndMaxInterleaved<SampleType, AudioData::LittleEndian> (channel, startSampleInFile, numSamples)
926 : scanMinAndMaxInterleaved<SampleType, AudioData::BigEndian> (channel, startSampleInFile, numSamples);
938 return { 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000 };
943 return { 8, 16, 24 };
958 return type == 0x41494646 || type == 0x41494643
959 || type == 0x61696666 || type == 0x61696663 ;
967 if (w->sampleRate > 0 && w->numChannels > 0)
970 if (! deleteStreamIfOpeningFails)
996 unsigned int numberOfChannels,
1003 (
unsigned int) bitsPerSample, metadataValues);
size_t getSize() const noexcept
Returns the block's current allocated size, in bytes.
virtual bool writeShortBigEndian(short value)
Writes a 16-bit integer to the stream in a big-endian byte order.
virtual bool writeByte(char byte)
Writes a single byte to the stream.
void getSample(int64 sample, float *result) const noexcept override
Returns the samples for all channels at a given sample position.
const StringArray & getAllKeys() const noexcept
Returns a list of all keys in the array.
void add(const ElementType &newElement)
Appends a new element at the end of the array.
Very simple container class to hold a pointer to some data on the heap.
static JUCE_CONSTEXPR uint32 bigEndianInt(const void *bytes) noexcept
Turns 4 bytes into a big-endian integer.
String getValue(StringRef, const String &defaultReturnValue) const
Finds the value corresponding to a key string.
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.
A special array for holding a list of strings.
FileInputStream * createInputStream() const
Creates a stream to read from this file.
int size() const noexcept
Returns the number of strings in the array.
void calloc(SizeType newNumElements, const size_t elementSize=sizeof(ElementType))
Allocates a specified amount of memory and clears it.
String toString() const
Attempts to parse the contents of the block as a zero-terminated UTF8 string.
static bool isUpperCase(juce_wchar character) noexcept
Checks whether a unicode character is upper-case.
void * getData() noexcept
Returns a void pointer to the data.
virtual bool writeIntBigEndian(int value)
Writes a 32-bit integer to the stream in a big-endian byte order.
bool write(const void *, size_t) override
Writes a block of data to the stream.
static Type swapIfLittleEndian(Type value) noexcept
Swaps the byte order of a signed or unsigned integer if the CPU is little-endian. ...
static JUCE_CONSTEXPR uint32 littleEndianInt(const void *bytes) noexcept
Turns 4 bytes into a little-endian integer.
Represents a local file or directory.
The base class for streams that write data to some kind of destination.
void readMaxLevels(int64 startSampleInFile, int64 numSamples, Range< float > *results, int numChannelsToRead) override
Finds the highest and lowest sample levels from a section of the audio stream.
static JUCE_CONSTEXPR uint16 bigEndianShort(const void *bytes) noexcept
Turns 2 bytes into a big-endian integer.
OSType getMacOSType() const
OSX ONLY - Finds the OSType of a file from the its resources.
size_t getDataSize() const noexcept
Returns the number of bytes of data that have been written to the stream.
void set(const String &key, const String &value)
Adds or amends a key/value pair.
bool contains(StringRef stringToLookFor, bool ignoreCase=false) const
Searches for a string in the array.
String joinIntoString(StringRef separatorString, int startIndex=0, int numberOfElements=-1) const
Joins the strings in the array together into one string.
int getIntValue() const noexcept
Reads the value of the string as a decimal number (up to 32 bits in size).
A container for holding a set of strings which are keyed by another string.
Writes data to an internal memory buffer, which grows as required.
static bool isLowerCase(juce_wchar character) noexcept
Checks whether a unicode character is lower-case.
A class to hold a resizable block of raw data.
virtual int64 getPosition()=0
Returns the stream's current position.
static bool isLetterOrDigit(char character) noexcept
Checks whether a character is alphabetic or numeric.
bool contains(ParameterType elementToLookFor) const
Returns true if the array contains at least one occurrence of an object.
void add(String stringToAdd)
Appends a string at the end of the array.
void setSize(const size_t newSize, bool initialiseNewSpaceToZero=false)
Resizes the memory block.
Wraps a pointer to a null-terminated UTF-8 character string, and provides various methods to operate ...