1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2017 - ROLI Ltd.
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 5 End-User License
11    Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
12    27th April 2017).
13 
14    End User License Agreement: www.juce.com/juce-5-licence
15    Privacy Policy: www.juce.com/juce-5-privacy-policy
16 
17    Or: You may also use this code under the terms of the GPL v3 (see
18    www.gnu.org/licenses).
19 
20    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
21    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
22    DISCLAIMED.
23 
24   ==============================================================================
25 */
26 
27 namespace juce
28 {
29 
30 static const char* const aiffFormatName = "AIFF file";
31 
32 //==============================================================================
33 const char* const AiffAudioFormat::appleOneShot         = "apple one shot";
34 const char* const AiffAudioFormat::appleRootSet         = "apple root set";
35 const char* const AiffAudioFormat::appleRootNote        = "apple root note";
36 const char* const AiffAudioFormat::appleBeats           = "apple beats";
37 const char* const AiffAudioFormat::appleDenominator     = "apple denominator";
38 const char* const AiffAudioFormat::appleNumerator       = "apple numerator";
39 const char* const AiffAudioFormat::appleTag             = "apple tag";
40 const char* const AiffAudioFormat::appleKey             = "apple key";
41 
42 //==============================================================================
43 namespace AiffFileHelpers
44 {
chunkName(const char * name)45     inline int chunkName (const char* name) noexcept    { return (int) ByteOrder::littleEndianInt (name); }
46 
47    #if JUCE_MSVC
48     #pragma pack (push, 1)
49    #endif
50 
51     //==============================================================================
52     struct InstChunk
53     {
54         struct Loop
55         {
56             uint16 type; // these are different in AIFF and WAV
57             uint16 startIdentifier;
58             uint16 endIdentifier;
59         } JUCE_PACKED;
60 
61         int8 baseNote;
62         int8 detune;
63         int8 lowNote;
64         int8 highNote;
65         int8 lowVelocity;
66         int8 highVelocity;
67         int16 gain;
68         Loop sustainLoop;
69         Loop releaseLoop;
70 
copyTojuce::AiffFileHelpers::InstChunk71         void copyTo (StringPairArray& values) const
72         {
73             values.set ("MidiUnityNote",        String (baseNote));
74             values.set ("Detune",               String (detune));
75 
76             values.set ("LowNote",              String (lowNote));
77             values.set ("HighNote",             String (highNote));
78             values.set ("LowVelocity",          String (lowVelocity));
79             values.set ("HighVelocity",         String (highVelocity));
80 
81             values.set ("Gain",                 String ((int16) ByteOrder::swapIfLittleEndian ((uint16) gain)));
82 
83             values.set ("NumSampleLoops",       String (2));        // always 2 with AIFF, WAV can have more
84             values.set ("Loop0Type",            String (ByteOrder::swapIfLittleEndian (sustainLoop.type)));
85             values.set ("Loop0StartIdentifier", String (ByteOrder::swapIfLittleEndian (sustainLoop.startIdentifier)));
86             values.set ("Loop0EndIdentifier",   String (ByteOrder::swapIfLittleEndian (sustainLoop.endIdentifier)));
87             values.set ("Loop1Type",            String (ByteOrder::swapIfLittleEndian (releaseLoop.type)));
88             values.set ("Loop1StartIdentifier", String (ByteOrder::swapIfLittleEndian (releaseLoop.startIdentifier)));
89             values.set ("Loop1EndIdentifier",   String (ByteOrder::swapIfLittleEndian (releaseLoop.endIdentifier)));
90         }
91 
getValue16juce::AiffFileHelpers::InstChunk92         static uint16 getValue16 (const StringPairArray& values, const char* name, const char* def)
93         {
94             return ByteOrder::swapIfLittleEndian ((uint16) values.getValue (name, def).getIntValue());
95         }
96 
getValue8juce::AiffFileHelpers::InstChunk97         static int8 getValue8 (const StringPairArray& values, const char* name, const char* def)
98         {
99             return (int8) values.getValue (name, def).getIntValue();
100         }
101 
createjuce::AiffFileHelpers::InstChunk102         static void create (MemoryBlock& block, const StringPairArray& values)
103         {
104             if (values.getAllKeys().contains ("MidiUnityNote", true))
105             {
106                 block.setSize ((sizeof (InstChunk) + 3) & ~(size_t) 3, true);
107                 auto& inst = *static_cast<InstChunk*> (block.getData());
108 
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");
116 
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");
123             }
124         }
125 
126     } JUCE_PACKED;
127 
128     //==============================================================================
129     struct BASCChunk
130     {
131         enum Key
132         {
133             minor = 1,
134             major = 2,
135             neither = 3,
136             both = 4
137         };
138 
BASCChunkjuce::AiffFileHelpers::BASCChunk139         BASCChunk (InputStream& input)
140         {
141             zerostruct (*this);
142 
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));
151         }
152 
addToMetadatajuce::AiffFileHelpers::BASCChunk153         void addToMetadata (StringPairArray& metadata) const
154         {
155             const bool rootNoteSet = rootNote != 0;
156 
157             setBoolFlag (metadata, AiffAudioFormat::appleOneShot, oneShot == 2);
158             setBoolFlag (metadata, AiffAudioFormat::appleRootSet, rootNoteSet);
159 
160             if (rootNoteSet)
161                 metadata.set (AiffAudioFormat::appleRootNote,   String (rootNote));
162 
163             metadata.set (AiffAudioFormat::appleBeats,          String (numBeats));
164             metadata.set (AiffAudioFormat::appleDenominator,    String (timeSigDen));
165             metadata.set (AiffAudioFormat::appleNumerator,      String (timeSigNum));
166 
167             const char* keyString = nullptr;
168 
169             switch (key)
170             {
171                 case minor:     keyString = "minor";        break;
172                 case major:     keyString = "major";        break;
173                 case neither:   keyString = "neither";      break;
174                 case both:      keyString = "both";         break;
175             }
176 
177             if (keyString != nullptr)
178                 metadata.set (AiffAudioFormat::appleKey, keyString);
179         }
180 
setBoolFlagjuce::AiffFileHelpers::BASCChunk181         void setBoolFlag (StringPairArray& values, const char* name, bool shouldBeSet) const
182         {
183             values.set (name, shouldBeSet ? "1" : "0");
184         }
185 
186         uint32 flags;
187         uint32 numBeats;
188         uint16 rootNote;
189         uint16 key;
190         uint16 timeSigNum;
191         uint16 timeSigDen;
192         uint16 oneShot;
193         uint8 unknown[66];
194     } JUCE_PACKED;
195 
196    #if JUCE_MSVC
197     #pragma pack (pop)
198    #endif
199 
200     //==============================================================================
201     namespace CATEChunk
202     {
isValidTag(const char * d)203         static bool isValidTag (const char* d) noexcept
204         {
205             return CharacterFunctions::isLetterOrDigit (d[0]) && CharacterFunctions::isUpperCase (static_cast<juce_wchar> (d[0]))
206                 && CharacterFunctions::isLetterOrDigit (d[1]) && CharacterFunctions::isLowerCase (static_cast<juce_wchar> (d[1]))
207                 && CharacterFunctions::isLetterOrDigit (d[2]) && CharacterFunctions::isLowerCase (static_cast<juce_wchar> (d[2]));
208         }
209 
isAppleGenre(const String & tag)210         static bool isAppleGenre (const String& tag) noexcept
211         {
212             static const char* appleGenres[] =
213             {
214                 "Rock/Blues",
215                 "Electronic/Dance",
216                 "Jazz",
217                 "Urban",
218                 "World/Ethnic",
219                 "Cinematic/New Age",
220                 "Orchestral",
221                 "Country/Folk",
222                 "Experimental",
223                 "Other Genre"
224             };
225 
226             for (int i = 0; i < numElementsInArray (appleGenres); ++i)
227                 if (tag == appleGenres[i])
228                     return true;
229 
230             return false;
231         }
232 
read(InputStream & input,const uint32 length)233         static String read (InputStream& input, const uint32 length)
234         {
235             MemoryBlock mb;
236             input.skipNextBytes (4);
237             input.readIntoMemoryBlock (mb, (ssize_t) length - 4);
238 
239             StringArray tagsArray;
240 
241             auto* data = static_cast<const char*> (mb.getData());
242             auto* dataEnd = data + mb.getSize();
243 
244             while (data < dataEnd)
245             {
246                 bool isGenre = false;
247 
248                 if (isValidTag (data))
249                 {
250                     auto tag = String (CharPointer_UTF8 (data), CharPointer_UTF8 (dataEnd));
251                     isGenre = isAppleGenre (tag);
252                     tagsArray.add (tag);
253                 }
254 
255                 data += isGenre ? 118 : 50;
256 
257                 if (data < dataEnd && data[0] == 0)
258                 {
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;
262                 }
263             }
264 
265             return tagsArray.joinIntoString (";");
266         }
267     }
268 
269     //==============================================================================
270     namespace MarkChunk
271     {
metaDataContainsZeroIdentifiers(const StringPairArray & values)272         static bool metaDataContainsZeroIdentifiers (const StringPairArray& values)
273         {
274             // (zero cue identifiers are valid for WAV but not for AIFF)
275             const String cueString ("Cue");
276             const String noteString ("CueNote");
277             const String identifierString ("Identifier");
278 
279             for (auto& key : values.getAllKeys())
280             {
281                 if (key.startsWith (noteString))
282                     continue; // zero identifier IS valid in a COMT chunk
283 
284                 if (key.startsWith (cueString) && key.contains (identifierString))
285                     if (values.getValue (key, "-1").getIntValue() == 0)
286                         return true;
287             }
288 
289             return false;
290         }
291 
create(MemoryBlock & block,const StringPairArray & values)292         static void create (MemoryBlock& block, const StringPairArray& values)
293         {
294             auto numCues = values.getValue ("NumCuePoints", "0").getIntValue();
295 
296             if (numCues > 0)
297             {
298                 MemoryOutputStream out (block, false);
299                 out.writeShortBigEndian ((short) numCues);
300 
301                 auto numCueLabels = values.getValue ("NumCueLabels", "0").getIntValue();
302                 auto idOffset = metaDataContainsZeroIdentifiers (values) ? 1 : 0; // can't have zero IDs in AIFF
303 
304                #if JUCE_DEBUG
305                 Array<int> identifiers;
306                #endif
307 
308                 for (int i = 0; i < numCues; ++i)
309                 {
310                     auto prefixCue = "Cue" + String (i);
311                     auto identifier = idOffset + values.getValue (prefixCue + "Identifier", "1").getIntValue();
312 
313                    #if JUCE_DEBUG
314                     jassert (! identifiers.contains (identifier));
315                     identifiers.add (identifier);
316                    #endif
317 
318                     auto offset = values.getValue (prefixCue + "Offset", "0").getIntValue();
319                     auto label = "CueLabel" + String (i);
320 
321                     for (int labelIndex = 0; labelIndex < numCueLabels; ++labelIndex)
322                     {
323                         auto prefixLabel = "CueLabel" + String (labelIndex);
324                         auto labelIdentifier = idOffset + values.getValue (prefixLabel + "Identifier", "1").getIntValue();
325 
326                         if (labelIdentifier == identifier)
327                         {
328                             label = values.getValue (prefixLabel + "Text", label);
329                             break;
330                         }
331                     }
332 
333                     out.writeShortBigEndian ((short) identifier);
334                     out.writeIntBigEndian (offset);
335 
336                     auto labelLength = jmin ((size_t) 254, label.getNumBytesAsUTF8()); // seems to need null terminator even though it's a pstring
337                     out.writeByte ((char) labelLength + 1);
338                     out.write (label.toUTF8(), labelLength);
339                     out.writeByte (0);
340 
341                     if ((out.getDataSize() & 1) != 0)
342                         out.writeByte (0);
343                 }
344             }
345         }
346     }
347 
348     //==============================================================================
349     namespace COMTChunk
350     {
create(MemoryBlock & block,const StringPairArray & values)351         static void create (MemoryBlock& block, const StringPairArray& values)
352         {
353             auto numNotes = values.getValue ("NumCueNotes", "0").getIntValue();
354 
355             if (numNotes > 0)
356             {
357                 MemoryOutputStream out (block, false);
358                 out.writeShortBigEndian ((short) numNotes);
359 
360                 for (int i = 0; i < numNotes; ++i)
361                 {
362                     auto prefix = "CueNote" + String (i);
363 
364                     out.writeIntBigEndian (values.getValue (prefix + "TimeStamp", "0").getIntValue());
365                     out.writeShortBigEndian ((short) values.getValue (prefix + "Identifier", "0").getIntValue());
366 
367                     auto comment = values.getValue (prefix + "Text", String());
368                     auto commentLength = jmin (comment.getNumBytesAsUTF8(), (size_t) 65534);
369 
370                     out.writeShortBigEndian ((short) commentLength + 1);
371                     out.write (comment.toUTF8(), commentLength);
372                     out.writeByte (0);
373 
374                     if ((out.getDataSize() & 1) != 0)
375                         out.writeByte (0);
376                 }
377             }
378         }
379     }
380 }
381 
382 //==============================================================================
383 class AiffAudioFormatReader  : public AudioFormatReader
384 {
385 public:
AiffAudioFormatReader(InputStream * in)386     AiffAudioFormatReader (InputStream* in)
387         : AudioFormatReader (in, aiffFormatName)
388     {
389         using namespace AiffFileHelpers;
390 
391         if (input->readInt() == chunkName ("FORM"))
392         {
393             auto len = input->readIntBigEndian();
394             auto end = input->getPosition() + len;
395             auto nextType = input->readInt();
396 
397             if (nextType == chunkName ("AIFF") || nextType == chunkName ("AIFC"))
398             {
399                 bool hasGotVer = false;
400                 bool hasGotData = false;
401                 bool hasGotType = false;
402 
403                 while (input->getPosition() < end)
404                 {
405                     auto type = input->readInt();
406                     auto length = (uint32) input->readIntBigEndian();
407                     auto chunkEnd = input->getPosition() + length;
408 
409                     if (type == chunkName ("FVER"))
410                     {
411                         hasGotVer = true;
412                         auto ver = input->readIntBigEndian();
413 
414                         if (ver != 0 && ver != (int) 0xa2805140)
415                             break;
416                     }
417                     else if (type == chunkName ("COMM"))
418                     {
419                         hasGotType = true;
420 
421                         numChannels = (unsigned int) input->readShortBigEndian();
422                         lengthInSamples = input->readIntBigEndian();
423                         bitsPerSample = (unsigned int) input->readShortBigEndian();
424                         bytesPerFrame = (int) ((numChannels * bitsPerSample) >> 3);
425 
426                         unsigned char sampleRateBytes[10];
427                         input->read (sampleRateBytes, 10);
428                         const int byte0 = sampleRateBytes[0];
429 
430                         if ((byte0 & 0x80) != 0
431                              || byte0 <= 0x3F || byte0 > 0x40
432                              || (byte0 == 0x40 && sampleRateBytes[1] > 0x1C))
433                             break;
434 
435                         auto sampRate = ByteOrder::bigEndianInt (sampleRateBytes + 2);
436                         sampRate >>= (16414 - ByteOrder::bigEndianShort (sampleRateBytes));
437                         sampleRate = (int) sampRate;
438 
439                         if (length <= 18)
440                         {
441                             // some types don't have a chunk large enough to include a compression
442                             // type, so assume it's just big-endian pcm
443                             littleEndian = false;
444                         }
445                         else
446                         {
447                             auto compType = input->readInt();
448 
449                             if (compType == chunkName ("NONE") || compType == chunkName ("twos"))
450                             {
451                                 littleEndian = false;
452                             }
453                             else if (compType == chunkName ("sowt"))
454                             {
455                                 littleEndian = true;
456                             }
457                             else if (compType == chunkName ("fl32") || compType == chunkName ("FL32"))
458                             {
459                                 littleEndian = false;
460                                 usesFloatingPointData = true;
461                             }
462                             else
463                             {
464                                 sampleRate = 0;
465                                 break;
466                             }
467                         }
468                     }
469                     else if (type == chunkName ("SSND"))
470                     {
471                         hasGotData = true;
472 
473                         auto offset = input->readIntBigEndian();
474                         dataChunkStart = input->getPosition() + 4 + offset;
475                         lengthInSamples = (bytesPerFrame > 0) ? jmin (lengthInSamples, ((int64) length) / (int64) bytesPerFrame) : 0;
476                     }
477                     else if (type == chunkName ("MARK"))
478                     {
479                         auto numCues = (uint16) input->readShortBigEndian();
480 
481                         // these two are always the same for AIFF-read files
482                         metadataValues.set ("NumCuePoints", String (numCues));
483                         metadataValues.set ("NumCueLabels", String (numCues));
484 
485                         for (uint16 i = 0; i < numCues; ++i)
486                         {
487                             auto identifier = (uint16) input->readShortBigEndian();
488                             auto offset = (uint32) input->readIntBigEndian();
489                             auto stringLength = (uint8) input->readByte();
490                             MemoryBlock textBlock;
491                             input->readIntoMemoryBlock (textBlock, stringLength);
492 
493                             // if the stringLength is even then read one more byte as the
494                             // string needs to be an even number of bytes INCLUDING the
495                             // leading length character in the pascal string
496                             if ((stringLength & 1) == 0)
497                                 input->readByte();
498 
499                             auto prefixCue = "Cue" + String (i);
500                             metadataValues.set (prefixCue + "Identifier", String (identifier));
501                             metadataValues.set (prefixCue + "Offset", String (offset));
502 
503                             auto prefixLabel = "CueLabel" + String (i);
504                             metadataValues.set (prefixLabel + "Identifier", String (identifier));
505                             metadataValues.set (prefixLabel + "Text", textBlock.toString());
506                         }
507                     }
508                     else if (type == chunkName ("COMT"))
509                     {
510                         auto numNotes = (uint16) input->readShortBigEndian();
511                         metadataValues.set ("NumCueNotes", String (numNotes));
512 
513                         for (uint16 i = 0; i < numNotes; ++i)
514                         {
515                             auto timestamp = (uint32) input->readIntBigEndian();
516                             auto identifier = (uint16) input->readShortBigEndian(); // may be zero in this case
517                             auto stringLength = (uint16) input->readShortBigEndian();
518 
519                             MemoryBlock textBlock;
520                             input->readIntoMemoryBlock (textBlock, stringLength + (stringLength & 1));
521 
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());
526                         }
527                     }
528                     else if (type == chunkName ("INST"))
529                     {
530                         HeapBlock<InstChunk> inst;
531                         inst.calloc (jmax ((size_t) length + 1, sizeof (InstChunk)), 1);
532                         input->read (inst, (int) length);
533                         inst->copyTo (metadataValues);
534                     }
535                     else if (type == chunkName ("basc"))
536                     {
537                         AiffFileHelpers::BASCChunk (*input).addToMetadata (metadataValues);
538                     }
539                     else if (type == chunkName ("cate"))
540                     {
541                         metadataValues.set (AiffAudioFormat::appleTag,
542                                             AiffFileHelpers::CATEChunk::read (*input, length));
543                     }
544                     else if ((hasGotVer && hasGotData && hasGotType)
545                               || chunkEnd < input->getPosition()
546                               || input->isExhausted())
547                     {
548                         break;
549                     }
550 
551                     input->setPosition (chunkEnd + (chunkEnd & 1)); // (chunks should be aligned to an even byte address)
552                 }
553             }
554         }
555 
556         if (metadataValues.size() > 0)
557             metadataValues.set ("MetaDataSource", "AIFF");
558     }
559 
560     //==============================================================================
readSamples(int ** destSamples,int numDestChannels,int startOffsetInDestBuffer,int64 startSampleInFile,int numSamples)561     bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
562                       int64 startSampleInFile, int numSamples) override
563     {
564         clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
565                                            startSampleInFile, numSamples, lengthInSamples);
566 
567         if (numSamples <= 0)
568             return true;
569 
570         input->setPosition (dataChunkStart + startSampleInFile * bytesPerFrame);
571 
572         while (numSamples > 0)
573         {
574             const int tempBufSize = 480 * 3 * 4; // (keep this a multiple of 3)
575             char tempBuffer [tempBufSize];
576 
577             const int numThisTime = jmin (tempBufSize / bytesPerFrame, numSamples);
578             const int bytesRead = input->read (tempBuffer, numThisTime * bytesPerFrame);
579 
580             if (bytesRead < numThisTime * bytesPerFrame)
581             {
582                 jassert (bytesRead >= 0);
583                 zeromem (tempBuffer + bytesRead, (size_t) (numThisTime * bytesPerFrame - bytesRead));
584             }
585 
586             if (littleEndian)
587                 copySampleData<AudioData::LittleEndian> (bitsPerSample, usesFloatingPointData,
588                                                          destSamples, startOffsetInDestBuffer, numDestChannels,
589                                                          tempBuffer, (int) numChannels, numThisTime);
590             else
591                 copySampleData<AudioData::BigEndian> (bitsPerSample, usesFloatingPointData,
592                                                       destSamples, startOffsetInDestBuffer, numDestChannels,
593                                                       tempBuffer, (int) numChannels, numThisTime);
594 
595             startOffsetInDestBuffer += numThisTime;
596             numSamples -= numThisTime;
597         }
598 
599         return true;
600     }
601 
602     template <typename Endianness>
copySampleData(unsigned int numBitsPerSample,bool floatingPointData,int * const * destSamples,int startOffsetInDestBuffer,int numDestChannels,const void * sourceData,int numberOfChannels,int numSamples)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
606     {
607         switch (numBitsPerSample)
608         {
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);
614                         break;
615             default:    jassertfalse; break;
616         }
617     }
618 
619     int bytesPerFrame;
620     int64 dataChunkStart;
621     bool littleEndian;
622 
623 private:
624     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AiffAudioFormatReader)
625 };
626 
627 //==============================================================================
628 class AiffAudioFormatWriter  : public AudioFormatWriter
629 {
630 public:
AiffAudioFormatWriter(OutputStream * out,double rate,unsigned int numChans,unsigned int bits,const StringPairArray & metadataValues)631     AiffAudioFormatWriter (OutputStream* out, double rate,
632                            unsigned int numChans, unsigned int bits,
633                            const StringPairArray& metadataValues)
634         : AudioFormatWriter (out, aiffFormatName, rate, numChans, bits)
635     {
636         using namespace AiffFileHelpers;
637 
638         if (metadataValues.size() > 0)
639         {
640             // The meta data should have been sanitised for the AIFF format.
641             // If it was originally sourced from a WAV file the MetaDataSource
642             // key should be removed (or set to "AIFF") once this has been done
643             jassert (metadataValues.getValue ("MetaDataSource", "None") != "WAV");
644 
645             MarkChunk::create (markChunk, metadataValues);
646             COMTChunk::create (comtChunk, metadataValues);
647             InstChunk::create (instChunk, metadataValues);
648         }
649 
650         headerPosition = out->getPosition();
651         writeHeader();
652     }
653 
~AiffAudioFormatWriter()654     ~AiffAudioFormatWriter() override
655     {
656         if ((bytesWritten & 1) != 0)
657             output->writeByte (0);
658 
659         writeHeader();
660     }
661 
662     //==============================================================================
write(const int ** data,int numSamples)663     bool write (const int** data, int numSamples) override
664     {
665         jassert (numSamples >= 0);
666         jassert (data != nullptr && *data != nullptr); // the input must contain at least one channel!
667 
668         if (writeFailed)
669             return false;
670 
671         auto bytes = numChannels * (size_t) numSamples * bitsPerSample / 8;
672         tempBlock.ensureSize (bytes, false);
673 
674         switch (bitsPerSample)
675         {
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;
681         }
682 
683         if (bytesWritten + bytes >= (size_t) 0xfff00000
684              || ! output->write (tempBlock.getData(), bytes))
685         {
686             // failed to write to disk, so let's try writing the header.
687             // If it's just run out of disk space, then if it does manage
688             // to write the header, we'll still have a useable file..
689             writeHeader();
690             writeFailed = true;
691             return false;
692         }
693 
694         bytesWritten += bytes;
695         lengthInSamples += (uint64) numSamples;
696         return true;
697     }
698 
699 private:
700     MemoryBlock tempBlock, markChunk, comtChunk, instChunk;
701     uint64 lengthInSamples = 0, bytesWritten = 0;
702     int64 headerPosition = 0;
703     bool writeFailed = false;
704 
writeHeader()705     void writeHeader()
706     {
707         using namespace AiffFileHelpers;
708 
709         const bool couldSeekOk = output->setPosition (headerPosition);
710         ignoreUnused (couldSeekOk);
711 
712         // if this fails, you've given it an output stream that can't seek! It needs
713         // to be able to seek back to write the header
714         jassert (couldSeekOk);
715 
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));
719         auto audioBytes = (int) (lengthInSamples * ((bitsPerSample * numChannels) / 8));
720         audioBytes += (audioBytes & 1);
721 
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);
730 
731         uint8 sampleRateBytes[10] = {};
732 
733         if (sampleRate <= 1)
734         {
735             sampleRateBytes[0] = 0x3f;
736             sampleRateBytes[1] = 0xff;
737             sampleRateBytes[2] = 0x80;
738         }
739         else
740         {
741             int mask = 0x40000000;
742             sampleRateBytes[0] = 0x40;
743 
744             if (sampleRate >= mask)
745             {
746                 jassertfalse;
747                 sampleRateBytes[1] = 0x1d;
748             }
749             else
750             {
751                 int n = (int) sampleRate;
752                 int i;
753 
754                 for (i = 0; i <= 32 ; ++i)
755                 {
756                     if ((n & mask) != 0)
757                         break;
758 
759                     mask >>= 1;
760                 }
761 
762                 n = n << (i + 1);
763 
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);
769             }
770         }
771 
772         output->write (sampleRateBytes, 10);
773 
774         if (markChunk.getSize() > 0)
775         {
776             output->writeInt (chunkName ("MARK"));
777             output->writeIntBigEndian ((int) markChunk.getSize());
778             *output << markChunk;
779         }
780 
781         if (comtChunk.getSize() > 0)
782         {
783             output->writeInt (chunkName ("COMT"));
784             output->writeIntBigEndian ((int) comtChunk.getSize());
785             *output << comtChunk;
786         }
787 
788         if (instChunk.getSize() > 0)
789         {
790             output->writeInt (chunkName ("INST"));
791             output->writeIntBigEndian ((int) instChunk.getSize());
792             *output << instChunk;
793         }
794 
795         output->writeInt (chunkName ("SSND"));
796         output->writeIntBigEndian (audioBytes + 8);
797         output->writeInt (0);
798         output->writeInt (0);
799 
800         jassert (output->getPosition() == headerLen);
801     }
802 
803     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AiffAudioFormatWriter)
804 };
805 
806 //==============================================================================
807 class MemoryMappedAiffReader   : public MemoryMappedAudioFormatReader
808 {
809 public:
MemoryMappedAiffReader(const File & f,const AiffAudioFormatReader & reader)810     MemoryMappedAiffReader (const File& f, const AiffAudioFormatReader& reader)
811         : MemoryMappedAudioFormatReader (f, reader, reader.dataChunkStart,
812                                          reader.bytesPerFrame * reader.lengthInSamples, reader.bytesPerFrame),
813           littleEndian (reader.littleEndian)
814     {
815     }
816 
readSamples(int ** destSamples,int numDestChannels,int startOffsetInDestBuffer,int64 startSampleInFile,int numSamples)817     bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
818                       int64 startSampleInFile, int numSamples) override
819     {
820         clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
821                                            startSampleInFile, numSamples, lengthInSamples);
822 
823         if (map == nullptr || ! mappedSection.contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
824         {
825             jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read.
826             return false;
827         }
828 
829         if (littleEndian)
830             AiffAudioFormatReader::copySampleData<AudioData::LittleEndian>
831                     (bitsPerSample, usesFloatingPointData, destSamples, startOffsetInDestBuffer,
832                      numDestChannels, sampleToPointer (startSampleInFile), (int) numChannels, numSamples);
833         else
834             AiffAudioFormatReader::copySampleData<AudioData::BigEndian>
835                     (bitsPerSample, usesFloatingPointData, destSamples, startOffsetInDestBuffer,
836                      numDestChannels, sampleToPointer (startSampleInFile), (int) numChannels, numSamples);
837 
838         return true;
839     }
840 
getSample(int64 sample,float * result) const841     void getSample (int64 sample, float* result) const noexcept override
842     {
843         auto num = (int) numChannels;
844 
845         if (map == nullptr || ! mappedSection.contains (sample))
846         {
847             jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read.
848 
849             zeromem (result, (size_t) num * sizeof (float));
850             return;
851         }
852 
853         float** dest = &result;
854         const void* source = sampleToPointer (sample);
855 
856         if (littleEndian)
857         {
858             switch (bitsPerSample)
859             {
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);
865                             break;
866                 default:    jassertfalse; break;
867             }
868         }
869         else
870         {
871             switch (bitsPerSample)
872             {
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);
878                             break;
879                 default:    jassertfalse; break;
880             }
881         }
882     }
883 
readMaxLevels(int64 startSampleInFile,int64 numSamples,Range<float> * results,int numChannelsToRead)884     void readMaxLevels (int64 startSampleInFile, int64 numSamples, Range<float>* results, int numChannelsToRead) override
885     {
886         numSamples = jmin (numSamples, lengthInSamples - startSampleInFile);
887 
888         if (map == nullptr || numSamples <= 0 || ! mappedSection.contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
889         {
890             jassert (numSamples <= 0); // you must make sure that the window contains all the samples you're going to attempt to read.
891 
892             for (int i = 0; i < numChannelsToRead; ++i)
893                 results[i] = Range<float>();
894 
895             return;
896         }
897 
898         switch (bitsPerSample)
899         {
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);
905                         break;
906             default:    jassertfalse; break;
907         }
908     }
909 
910     using AudioFormatReader::readMaxLevels;
911 
912 private:
913     const bool littleEndian;
914 
915     template <typename SampleType>
scanMinAndMax(int64 startSampleInFile,int64 numSamples,Range<float> * results,int numChannelsToRead) const916     void scanMinAndMax (int64 startSampleInFile, int64 numSamples, Range<float>* results, int numChannelsToRead) const noexcept
917     {
918         for (int i = 0; i < numChannelsToRead; ++i)
919             results[i] = scanMinAndMaxForChannel<SampleType> (i, startSampleInFile, numSamples);
920     }
921 
922     template <typename SampleType>
scanMinAndMaxForChannel(int channel,int64 startSampleInFile,int64 numSamples) const923     Range<float> scanMinAndMaxForChannel (int channel, int64 startSampleInFile, int64 numSamples) const noexcept
924     {
925         return littleEndian ? scanMinAndMaxInterleaved<SampleType, AudioData::LittleEndian> (channel, startSampleInFile, numSamples)
926                             : scanMinAndMaxInterleaved<SampleType, AudioData::BigEndian>    (channel, startSampleInFile, numSamples);
927     }
928 
929     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedAiffReader)
930 };
931 
932 //==============================================================================
AiffAudioFormat()933 AiffAudioFormat::AiffAudioFormat()  : AudioFormat (aiffFormatName, ".aiff .aif") {}
~AiffAudioFormat()934 AiffAudioFormat::~AiffAudioFormat() {}
935 
getPossibleSampleRates()936 Array<int> AiffAudioFormat::getPossibleSampleRates()
937 {
938     return { 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000 };
939 }
940 
getPossibleBitDepths()941 Array<int> AiffAudioFormat::getPossibleBitDepths()
942 {
943      return { 8, 16, 24 };
944 }
945 
canDoStereo()946 bool AiffAudioFormat::canDoStereo() { return true; }
canDoMono()947 bool AiffAudioFormat::canDoMono()   { return true; }
948 
949 #if JUCE_MAC
canHandleFile(const File & f)950 bool AiffAudioFormat::canHandleFile (const File& f)
951 {
952     if (AudioFormat::canHandleFile (f))
953         return true;
954 
955     auto type = f.getMacOSType();
956 
957     // (NB: written as hex to avoid four-char-constant warnings)
958     return type == 0x41494646 /* AIFF */ || type == 0x41494643 /* AIFC */
959         || type == 0x61696666 /* aiff */ || type == 0x61696663 /* aifc */;
960 }
961 #endif
962 
createReaderFor(InputStream * sourceStream,bool deleteStreamIfOpeningFails)963 AudioFormatReader* AiffAudioFormat::createReaderFor (InputStream* sourceStream, bool deleteStreamIfOpeningFails)
964 {
965     std::unique_ptr<AiffAudioFormatReader> w (new AiffAudioFormatReader (sourceStream));
966 
967     if (w->sampleRate > 0 && w->numChannels > 0)
968         return w.release();
969 
970     if (! deleteStreamIfOpeningFails)
971         w->input = nullptr;
972 
973     return nullptr;
974 }
975 
createMemoryMappedReader(const File & file)976 MemoryMappedAudioFormatReader* AiffAudioFormat::createMemoryMappedReader (const File& file)
977 {
978     return createMemoryMappedReader (file.createInputStream());
979 }
980 
createMemoryMappedReader(FileInputStream * fin)981 MemoryMappedAudioFormatReader* AiffAudioFormat::createMemoryMappedReader (FileInputStream* fin)
982 {
983     if (fin != nullptr)
984     {
985         AiffAudioFormatReader reader (fin);
986 
987         if (reader.lengthInSamples > 0)
988             return new MemoryMappedAiffReader (fin->getFile(), reader);
989     }
990 
991     return nullptr;
992 }
993 
createWriterFor(OutputStream * out,double sampleRate,unsigned int numberOfChannels,int bitsPerSample,const StringPairArray & metadataValues,int)994 AudioFormatWriter* AiffAudioFormat::createWriterFor (OutputStream* out,
995                                                      double sampleRate,
996                                                      unsigned int numberOfChannels,
997                                                      int bitsPerSample,
998                                                      const StringPairArray& metadataValues,
999                                                      int /*qualityOptionIndex*/)
1000 {
1001     if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample))
1002         return new AiffAudioFormatWriter (out, sampleRate, numberOfChannels,
1003                                           (unsigned int) bitsPerSample, metadataValues);
1004 
1005     return nullptr;
1006 }
1007 
1008 } // namespace juce
1009