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