30static const char*
const aiffFormatName =
"AIFF file";
43namespace AiffFileHelpers
48 #pragma pack (push, 1)
57 uint16 startIdentifier;
71 void copyTo (StringPairArray& values)
const
73 values.
set (
"MidiUnityNote", String (baseNote));
74 values.
set (
"Detune", String (detune));
76 values.
set (
"LowNote", String (lowNote));
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)
99 return (int8) values.getValue (name, def).getIntValue();
102 static void create (MemoryBlock& block,
const StringPairArray& values)
104 if (values.getAllKeys().contains (
"MidiUnityNote",
true))
106 block.setSize ((
sizeof (InstChunk) + 3) & ~(
size_t) 3,
true);
107 auto& inst = *
static_cast<InstChunk*
> (block.getData());
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");
139 BASCChunk (InputStream& input)
143 flags = (uint32) input.readIntBigEndian();
144 numBeats = (uint32) input.readIntBigEndian();
145 rootNote = (uint16) input.readShortBigEndian();
146 key = (uint16) input.readShortBigEndian();
147 timeSigNum = (uint16) input.readShortBigEndian();
148 timeSigDen = (uint16) input.readShortBigEndian();
149 oneShot = (uint16) input.readShortBigEndian();
150 input.read (unknown,
sizeof (unknown));
153 void addToMetadata (StringPairArray& metadata)
const
155 const bool rootNoteSet = rootNote != 0;
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)
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])
233 static String read (InputStream& input,
const uint32 length)
236 input.skipNextBytes (4);
237 input.readIntoMemoryBlock (mb, (ssize_t) length - 4);
239 StringArray tagsArray;
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))
250 auto tag = String (CharPointer_UTF8 (data), CharPointer_UTF8 (dataEnd));
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;
265 return tagsArray.joinIntoString (
";");
272 static bool metaDataContainsZeroIdentifiers (
const StringPairArray& values)
275 const String cueString (
"Cue");
276 const String noteString (
"CueNote");
277 const String identifierString (
"Identifier");
279 for (
auto& key : values.getAllKeys())
281 if (key.startsWith (noteString))
284 if (key.startsWith (cueString) && key.contains (identifierString))
285 if (values.getValue (key,
"-1").getIntValue() == 0)
292 static void create (MemoryBlock& block,
const StringPairArray& values)
294 auto numCues = values.getValue (
"NumCuePoints",
"0").getIntValue();
298 MemoryOutputStream out (block,
false);
299 out.writeShortBigEndian ((
short) numCues);
301 auto numCueLabels = values.getValue (
"NumCueLabels",
"0").getIntValue();
302 auto idOffset = metaDataContainsZeroIdentifiers (values) ? 1 : 0;
305 Array<int> identifiers;
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);
318 auto offset = values.getValue (prefixCue +
"Offset",
"0").getIntValue();
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);
333 out.writeShortBigEndian ((
short) identifier);
334 out.writeIntBigEndian (offset);
336 auto labelLength = jmin ((
size_t) 254, label.getNumBytesAsUTF8());
337 out.writeByte ((
char) labelLength + 1);
338 out.write (label.toUTF8(), labelLength);
341 if ((out.getDataSize() & 1) != 0)
351 static void create (MemoryBlock& block,
const StringPairArray& values)
353 auto numNotes = values.getValue (
"NumCueNotes",
"0").getIntValue();
357 MemoryOutputStream out (block,
false);
358 out.writeShortBigEndian ((
short) numNotes);
360 for (
int i = 0; i < numNotes; ++i)
362 auto prefix =
"CueNote" + String (i);
364 out.writeIntBigEndian (values.getValue (prefix +
"TimeStamp",
"0").getIntValue());
365 out.writeShortBigEndian ((
short) values.getValue (prefix +
"Identifier",
"0").getIntValue());
367 auto comment = values.getValue (prefix +
"Text", String());
368 auto commentLength = jmin (comment.getNumBytesAsUTF8(), (
size_t) 65534);
370 out.writeShortBigEndian ((
short) commentLength + 1);
371 out.write (comment.toUTF8(), commentLength);
374 if ((out.getDataSize() & 1) != 0)
383class AiffAudioFormatReader :
public AudioFormatReader
386 AiffAudioFormatReader (InputStream* in)
389 using namespace AiffFileHelpers;
397 if (nextType == chunkName (
"AIFF") || nextType == chunkName (
"AIFC"))
399 bool hasGotVer =
false;
400 bool hasGotData =
false;
401 bool hasGotType =
false;
409 if (type == chunkName (
"FVER"))
414 if (ver != 0 && ver != (
int) 0xa2805140)
417 else if (type == chunkName (
"COMM"))
426 unsigned char sampleRateBytes[10];
428 const int byte0 = sampleRateBytes[0];
430 if ((byte0 & 0x80) != 0
431 || byte0 <= 0x3F || byte0 > 0x40
432 || (byte0 == 0x40 && sampleRateBytes[1] > 0x1C))
443 littleEndian =
false;
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;
469 else if (type == chunkName (
"SSND"))
477 else if (type == chunkName (
"MARK"))
485 for (uint16 i = 0; i < numCues; ++i)
490 MemoryBlock textBlock;
496 if ((stringLength & 1) == 0)
499 auto prefixCue =
"Cue" + String (i);
503 auto prefixLabel =
"CueLabel" + String (i);
508 else if (type == chunkName (
"COMT"))
513 for (uint16 i = 0; i < numNotes; ++i)
519 MemoryBlock textBlock;
522 auto prefix =
"CueNote" + String (i);
528 else if (type == chunkName (
"INST"))
530 HeapBlock<InstChunk> inst;
531 inst.calloc (jmax ((
size_t) length + 1,
sizeof (InstChunk)), 1);
535 else if (type == chunkName (
"basc"))
537 AiffFileHelpers::BASCChunk (*input).addToMetadata (
metadataValues);
539 else if (type == chunkName (
"cate"))
542 AiffFileHelpers::CATEChunk::read (*
input, length));
544 else if ((hasGotVer && hasGotData && hasGotType)
545 || chunkEnd < input->getPosition()
561 bool readSamples (
int** destSamples,
int numDestChannels,
int startOffsetInDestBuffer,
562 int64 startSampleInFile,
int numSamples)
override
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));
588 destSamples, startOffsetInDestBuffer, numDestChannels,
592 destSamples, startOffsetInDestBuffer, numDestChannels,
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)
609 case 8: ReadHelper<AudioData::Int32, AudioData::Int8, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
break;
610 case 16: ReadHelper<AudioData::Int32, AudioData::Int16, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
break;
611 case 24: ReadHelper<AudioData::Int32, AudioData::Int24, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
break;
612 case 32:
if (floatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
613 else ReadHelper<AudioData::Int32, AudioData::Int32, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
615 default: jassertfalse;
break;
620 int64 dataChunkStart;
624 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AiffAudioFormatReader)
628class AiffAudioFormatWriter :
public AudioFormatWriter
631 AiffAudioFormatWriter (OutputStream* out,
double rate,
632 unsigned int numChans,
unsigned int bits,
633 const StringPairArray& metadataValues)
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);
650 headerPosition = out->getPosition();
654 ~AiffAudioFormatWriter()
override
656 if ((bytesWritten & 1) != 0)
663 bool write (
const int** data,
int numSamples)
override
665 jassert (numSamples >= 0);
666 jassert (data !=
nullptr && *data !=
nullptr);
672 tempBlock.ensureSize (bytes,
false);
676 case 8: WriteHelper<AudioData::Int8, AudioData::Int32, AudioData::BigEndian>::write (tempBlock.getData(), (
int)
numChannels, data, numSamples);
break;
677 case 16: WriteHelper<AudioData::Int16, AudioData::Int32, AudioData::BigEndian>::write (tempBlock.getData(), (
int)
numChannels, data, numSamples);
break;
678 case 24: WriteHelper<AudioData::Int24, AudioData::Int32, AudioData::BigEndian>::write (tempBlock.getData(), (
int)
numChannels, data, numSamples);
break;
679 case 32: WriteHelper<AudioData::Int32, AudioData::Int32, AudioData::BigEndian>::write (tempBlock.getData(), (
int)
numChannels, data, numSamples);
break;
680 default: jassertfalse;
break;
683 if (bytesWritten + bytes >= (
size_t) 0xfff00000
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;
710 ignoreUnused (couldSeekOk);
714 jassert (couldSeekOk);
716 auto headerLen = (int) (54 + (markChunk.getSize() > 0 ? markChunk.getSize() + 8 : 0)
717 + (comtChunk.getSize() > 0 ? comtChunk.getSize() + 8 : 0)
718 + (instChunk.getSize() > 0 ? instChunk.getSize() + 8 : 0));
720 audioBytes += (audioBytes & 1);
731 uint8 sampleRateBytes[10] = {};
735 sampleRateBytes[0] = 0x3f;
736 sampleRateBytes[1] = 0xff;
737 sampleRateBytes[2] = 0x80;
741 int mask = 0x40000000;
742 sampleRateBytes[0] = 0x40;
747 sampleRateBytes[1] = 0x1d;
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);
774 if (markChunk.getSize() > 0)
781 if (comtChunk.getSize() > 0)
788 if (instChunk.getSize() > 0)
803 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AiffAudioFormatWriter)
807class MemoryMappedAiffReader :
public MemoryMappedAudioFormatReader
810 MemoryMappedAiffReader (
const File& f,
const AiffAudioFormatReader& reader)
813 littleEndian (reader.littleEndian)
817 bool readSamples (
int** destSamples,
int numDestChannels,
int startOffsetInDestBuffer,
818 int64 startSampleInFile,
int numSamples)
override
823 if (map ==
nullptr || ! mappedSection.
contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
830 AiffAudioFormatReader::copySampleData<AudioData::LittleEndian>
834 AiffAudioFormatReader::copySampleData<AudioData::BigEndian>
841 void getSample (int64 sample,
float* result)
const noexcept override
845 if (map ==
nullptr || ! mappedSection.
contains (sample))
849 zeromem (result, (
size_t) num *
sizeof (
float));
853 float** dest = &result;
860 case 8: ReadHelper<AudioData::Float32, AudioData::UInt8, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
break;
861 case 16: ReadHelper<AudioData::Float32, AudioData::Int16, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
break;
862 case 24: ReadHelper<AudioData::Float32, AudioData::Int24, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
break;
863 case 32:
if (
usesFloatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
864 else ReadHelper<AudioData::Float32, AudioData::Int32, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
866 default: jassertfalse;
break;
873 case 8: ReadHelper<AudioData::Float32, AudioData::UInt8, AudioData::BigEndian>::read (dest, 0, 1, source, 1, num);
break;
874 case 16: ReadHelper<AudioData::Float32, AudioData::Int16, AudioData::BigEndian>::read (dest, 0, 1, source, 1, num);
break;
875 case 24: ReadHelper<AudioData::Float32, AudioData::Int24, AudioData::BigEndian>::read (dest, 0, 1, source, 1, num);
break;
876 case 32:
if (
usesFloatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::BigEndian>::read (dest, 0, 1, source, 1, num);
877 else ReadHelper<AudioData::Float32, AudioData::Int32, AudioData::BigEndian>::read (dest, 0, 1, source, 1, num);
879 default: jassertfalse;
break;
884 void readMaxLevels (int64 startSampleInFile, int64 numSamples, Range<float>* results,
int numChannelsToRead)
override
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)
893 results[i] = Range<float>();
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)
929 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedAiffReader)
938 return { 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000 };
943 return { 8, 16, 24 };
955 auto type = f.getMacOSType();
958 return type == 0x41494646 || type == 0x41494643
959 || type == 0x61696666 || type == 0x61696663 ;
965 std::unique_ptr<AiffAudioFormatReader>
w (
new AiffAudioFormatReader (sourceStream));
967 if (
w->sampleRate > 0 &&
w->numChannels > 0)
985 AiffAudioFormatReader reader (
fin);
987 if (reader.lengthInSamples > 0)
988 return new MemoryMappedAiffReader (
fin->getFile(), reader);
996 unsigned int numberOfChannels,
1002 return new AiffAudioFormatWriter (
out, sampleRate, numberOfChannels,
1003 (
unsigned int) bitsPerSample, metadataValues);
void set(int indexToChange, ParameterType newValue)
static JUCE_CONSTEXPR uint16 bigEndianShort(const void *bytes) noexcept
static JUCE_CONSTEXPR uint32 bigEndianInt(const void *bytes) noexcept
static Type swapIfLittleEndian(Type value) noexcept
static JUCE_CONSTEXPR uint32 littleEndianInt(const void *bytes) noexcept
static bool isLowerCase(juce_wchar character) noexcept
static bool isLetterOrDigit(char character) noexcept
static bool isUpperCase(juce_wchar character) noexcept
FileInputStream * createInputStream() const
virtual bool write(const void *dataToWrite, size_t numberOfBytes)=0
virtual int64 getPosition()=0
virtual bool writeByte(char byte)
virtual bool writeIntBigEndian(int value)
virtual bool setPosition(int64 newPosition)=0
virtual bool writeShortBigEndian(short value)
virtual bool writeInt(int value)
JUCE_CONSTEXPR bool contains(const ValueType position) const noexcept
void set(const String &key, const String &value)
int size() const noexcept