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