OpenShot Library | libopenshot-audio 0.2.0
juce_AiffAudioFormat.cpp
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
27namespace juce
28{
29
30static const char* const aiffFormatName = "AIFF file";
31
32//==============================================================================
33const char* const AiffAudioFormat::appleOneShot = "apple one shot";
34const char* const AiffAudioFormat::appleRootSet = "apple root set";
35const char* const AiffAudioFormat::appleRootNote = "apple root note";
36const char* const AiffAudioFormat::appleBeats = "apple beats";
37const char* const AiffAudioFormat::appleDenominator = "apple denominator";
38const char* const AiffAudioFormat::appleNumerator = "apple numerator";
39const char* const AiffAudioFormat::appleTag = "apple tag";
40const char* const AiffAudioFormat::appleKey = "apple key";
41
42//==============================================================================
43namespace AiffFileHelpers
44{
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
71 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
92 static uint16 getValue16 (const StringPairArray& values, const char* name, const char* def)
93 {
95 }
96
97 static int8 getValue8 (const StringPairArray& values, const char* name, const char* def)
98 {
99 return (int8) values.getValue (name, def).getIntValue();
100 }
101
102 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 //==============================================================================
130 {
131 enum Key
132 {
133 minor = 1,
134 major = 2,
135 neither = 3,
136 both = 4
137 };
138
139 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
153 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)
179 }
180
181 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 {
203 static bool isValidTag (const char* d) noexcept
204 {
208 }
209
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
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 {
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
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 {
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//==============================================================================
384{
385public:
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();
424 bytesPerFrame = (int) ((numChannels * bitsPerSample) >> 3);
425
426 unsigned char sampleRateBytes[10];
428 const int byte0 = sampleRateBytes[0];
429
430 if ((byte0 & 0x80) != 0
432 || (byte0 == 0x40 && sampleRateBytes[1] > 0x1C))
433 break;
434
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;
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();
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 {
511 metadataValues.set ("NumCueNotes", String (numNotes));
512
513 for (uint16 i = 0; i < numNotes; ++i)
514 {
516 auto identifier = (uint16) input->readShortBigEndian(); // may be zero in this case
518
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 {
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 {
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 //==============================================================================
562 int64 startSampleInFile, int numSamples) override
563 {
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)
589 tempBuffer, (int) numChannels, numThisTime);
590 else
593 tempBuffer, (int) numChannels, numThisTime);
594
596 numSamples -= numThisTime;
597 }
598
599 return true;
600 }
601
602 template <typename Endianness>
603 static void copySampleData (unsigned int bitsPerSample, bool usesFloatingPointData,
605 const void* sourceData, int numChannels, int numSamples) noexcept
606 {
607 switch (bitsPerSample)
608 {
609 case 8: ReadHelper<AudioData::Int32, AudioData::Int8, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break;
610 case 16: ReadHelper<AudioData::Int32, AudioData::Int16, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break;
611 case 24: ReadHelper<AudioData::Int32, AudioData::Int24, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break;
612 case 32: if (usesFloatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples);
613 else ReadHelper<AudioData::Int32, AudioData::Int32, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples);
614 break;
615 default: jassertfalse; break;
616 }
617 }
618
619 int bytesPerFrame;
620 int64 dataChunkStart;
621 bool littleEndian;
622
623private:
624 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AiffAudioFormatReader)
625};
626
627//==============================================================================
629{
630public:
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
654 ~AiffAudioFormatWriter() override
655 {
656 if ((bytesWritten & 1) != 0)
657 output->writeByte (0);
658
659 writeHeader();
660 }
661
662 //==============================================================================
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
699private:
700 MemoryBlock tempBlock, markChunk, comtChunk, instChunk;
701 uint64 lengthInSamples = 0, bytesWritten = 0;
702 int64 headerPosition = 0;
703 bool writeFailed = false;
704
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"));
724 output->writeInt (chunkName ("AIFF"));
725 output->writeInt (chunkName ("COMM"));
728 output->writeIntBigEndian ((int) lengthInSamples);
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//==============================================================================
808{
809public:
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
818 int64 startSampleInFile, int numSamples) override
819 {
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>
833 else
834 AiffAudioFormatReader::copySampleData<AudioData::BigEndian>
837
838 return true;
839 }
840
841 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, sizeof (float) * (size_t) num);
850 return;
851 }
852
853 float** dest = &result;
854 const void* source = sampleToPointer (sample);
855
856 if (littleEndian)
857 {
858 switch (bitsPerSample)
859 {
865 break;
866 default: jassertfalse; break;
867 }
868 }
869 else
870 {
871 switch (bitsPerSample)
872 {
878 break;
879 default: jassertfalse; break;
880 }
881 }
882 }
883
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;
905 break;
906 default: jassertfalse; break;
907 }
908 }
909
910private:
911 const bool littleEndian;
912
913 template <typename SampleType>
914 void scanMinAndMax (int64 startSampleInFile, int64 numSamples, Range<float>* results, int numChannelsToRead) const noexcept
915 {
916 for (int i = 0; i < numChannelsToRead; ++i)
917 results[i] = scanMinAndMaxForChannel<SampleType> (i, startSampleInFile, numSamples);
918 }
919
920 template <typename SampleType>
921 Range<float> scanMinAndMaxForChannel (int channel, int64 startSampleInFile, int64 numSamples) const noexcept
922 {
923 return littleEndian ? scanMinAndMaxInterleaved<SampleType, AudioData::LittleEndian> (channel, startSampleInFile, numSamples)
924 : scanMinAndMaxInterleaved<SampleType, AudioData::BigEndian> (channel, startSampleInFile, numSamples);
925 }
926
927 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedAiffReader)
928};
929
930//==============================================================================
931AiffAudioFormat::AiffAudioFormat() : AudioFormat (aiffFormatName, ".aiff .aif") {}
933
935{
936 return { 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000 };
937}
938
940{
941 return { 8, 16, 24 };
942}
943
944bool AiffAudioFormat::canDoStereo() { return true; }
945bool AiffAudioFormat::canDoMono() { return true; }
946
947#if JUCE_MAC
949{
951 return true;
952
953 auto type = f.getMacOSType();
954
955 // (NB: written as hex to avoid four-char-constant warnings)
956 return type == 0x41494646 /* AIFF */ || type == 0x41494643 /* AIFC */
957 || type == 0x61696666 /* aiff */ || type == 0x61696663 /* aifc */;
958}
959#endif
960
962{
963 std::unique_ptr<AiffAudioFormatReader> w (new AiffAudioFormatReader (sourceStream));
964
965 if (w->sampleRate > 0 && w->numChannels > 0)
966 return w.release();
967
969 w->input = nullptr;
970
971 return nullptr;
972}
973
978
980{
981 if (fin != nullptr)
982 {
983 AiffAudioFormatReader reader (fin);
984
985 if (reader.lengthInSamples > 0)
986 return new MemoryMappedAiffReader (fin->getFile(), reader);
987 }
988
989 return nullptr;
990}
991
993 double sampleRate,
994 unsigned int numberOfChannels,
995 int bitsPerSample,
996 const StringPairArray& metadataValues,
997 int /*qualityOptionIndex*/)
998{
999 if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample))
1000 return new AiffAudioFormatWriter (out, sampleRate, numberOfChannels,
1001 (unsigned int) bitsPerSample, metadataValues);
1002
1003 return nullptr;
1004}
1005
1006} // namespace juce
bool readSamples(int **destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) override
Subclasses must implement this method to perform the low-level read operation.
bool write(const int **data, int numSamples) override
Writes a set of samples to the audio stream.
static const char *const appleDenominator
Metadata property name used when reading a aiff file with a basc chunk.
AiffAudioFormat()
Creates an format object.
bool canDoStereo() override
Returns true if the format can do 2-channel audio.
static const char *const appleOneShot
Metadata property name used when reading a aiff file with a basc chunk.
AudioFormatReader * createReaderFor(InputStream *sourceStream, bool deleteStreamIfOpeningFails) override
Tries to create an object that can read from a stream containing audio data in this format.
Array< int > getPossibleBitDepths() override
Returns a set of bit depths that the format can read and write.
static const char *const appleNumerator
Metadata property name used when reading a aiff file with a basc chunk.
bool canHandleFile(const File &fileToTest) override
Returns true if this the given file can be read by this format.
static const char *const appleTag
Metadata property name used when reading a aiff file with a basc chunk.
static const char *const appleBeats
Metadata property name used when reading a aiff file with a basc chunk.
bool canDoMono() override
Returns true if the format can do 1-channel audio.
Array< int > getPossibleSampleRates() override
Returns a set of sample rates that the format can read and write.
static const char *const appleRootSet
Metadata property name used when reading a aiff file with a basc chunk.
MemoryMappedAudioFormatReader * createMemoryMappedReader(const File &) override
Attempts to create a MemoryMappedAudioFormatReader, if possible for this format.
AudioFormatWriter * createWriterFor(OutputStream *streamToWriteTo, double sampleRateToUse, unsigned int numberOfChannels, int bitsPerSample, const StringPairArray &metadataValues, int qualityOptionIndex) override
Tries to create an object that can write to a stream with this audio format.
static const char *const appleKey
Metadata property name used when reading a aiff file with a basc chunk.
static const char *const appleRootNote
Metadata property name used when reading a aiff file with a basc chunk.
~AiffAudioFormat() override
Destructor.
Holds a resizable array of primitive or copy-by-value objects.
Definition juce_Array.h:60
Reads samples from an audio file stream.
InputStream * input
The input stream, for use by subclasses.
bool usesFloatingPointData
Indicates whether the data is floating-point or fixed.
StringPairArray metadataValues
A set of metadata values that the reader has pulled out of the stream.
static void clearSamplesBeyondAvailableLength(int **destChannels, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int &numSamples, int64 fileLengthInSamples)
Used by AudioFormatReader subclasses to clear any parts of the data blocks that lie beyond the end of...
int64 lengthInSamples
The total number of samples in the audio stream.
double sampleRate
The sample-rate of the stream.
unsigned int bitsPerSample
The number of bits per sample, e.g.
unsigned int numChannels
The total number of channels in the audio stream.
Writes samples to an audio file stream.
unsigned int numChannels
The number of channels being written to the stream.
double sampleRate
The sample rate of the stream.
unsigned int bitsPerSample
The bit depth of the file.
OutputStream * output
The output stream for use by subclasses.
Subclasses of AudioFormat are used to read and write different audio file formats.
virtual bool canHandleFile(const File &fileToTest)
Returns true if this the given file can be read by this format.
static JUCE_CONSTEXPR uint16 bigEndianShort(const void *bytes) noexcept
Turns 2 bytes into a big-endian integer.
static JUCE_CONSTEXPR uint32 bigEndianInt(const void *bytes) noexcept
Turns 4 bytes into a big-endian integer.
static Type swapIfLittleEndian(Type value) noexcept
Swaps the byte order of a signed or unsigned integer if the CPU is little-endian.
static JUCE_CONSTEXPR uint32 littleEndianInt(const void *bytes) noexcept
Turns 4 bytes into a little-endian integer.
static bool isLowerCase(juce_wchar character) noexcept
Checks whether a unicode character is lower-case.
static bool isLetterOrDigit(char character) noexcept
Checks whether a character is alphabetic or numeric.
static bool isUpperCase(juce_wchar character) noexcept
Checks whether a unicode character is upper-case.
An input stream that reads from a local file.
Represents a local file or directory.
Definition juce_File.h:45
OSType getMacOSType() const
OSX ONLY - Finds the OSType of a file from the its resources.
FileInputStream * createInputStream() const
Creates a stream to read from this file.
The base class for streams that read data.
virtual int64 getPosition()=0
Returns the offset of the next byte that will be read from the stream.
virtual int readIntBigEndian()
Reads four bytes from the stream as a big-endian 32-bit value.
virtual bool setPosition(int64 newPosition)=0
Tries to move the current read position of the stream.
virtual bool isExhausted()=0
Returns true if the stream has no more data to read.
virtual short readShortBigEndian()
Reads two bytes from the stream as a little-endian 16-bit value.
virtual size_t readIntoMemoryBlock(MemoryBlock &destBlock, ssize_t maxNumBytesToRead=-1)
Reads from the stream and appends the data to a MemoryBlock.
virtual int read(void *destBuffer, int maxBytesToRead)=0
Reads some data from the stream into a memory buffer.
virtual int readInt()
Reads four bytes from the stream as a little-endian 32-bit value.
virtual char readByte()
Reads a byte from the stream.
A class to hold a resizable block of raw data.
void ensureSize(const size_t minimumSize, bool initialiseNewSpaceToZero=false)
Increases the block's size only if it's smaller than a given size.
size_t getSize() const noexcept
Returns the block's current allocated size, in bytes.
void * getData() const noexcept
Returns a void pointer to the data.
void readMaxLevels(int64 startSampleInFile, int64 numSamples, Range< float > *results, int numChannelsToRead) override
Finds the highest and lowest sample levels from a section of the audio stream.
bool readSamples(int **destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) override
Subclasses must implement this method to perform the low-level read operation.
void getSample(int64 sample, float *result) const noexcept override
Returns the samples for all channels at a given sample position.
A specialised type of AudioFormatReader that uses a MemoryMappedFile to read directly from an audio f...
const void * sampleToPointer(int64 sample) const noexcept
Converts a sample index to a pointer to the mapped file memory.
Range< float > scanMinAndMaxInterleaved(int channel, int64 startSampleInFile, int64 numSamples) const noexcept
Used by AudioFormatReader subclasses to scan for min/max ranges in interleaved data.
The base class for streams that write data to some kind of destination.
virtual bool write(const void *dataToWrite, size_t numberOfBytes)=0
Writes a block of data to the stream.
virtual int64 getPosition()=0
Returns the stream's current position.
virtual bool writeByte(char byte)
Writes a single byte to the stream.
virtual bool writeIntBigEndian(int value)
Writes a 32-bit integer to the stream in a big-endian byte order.
virtual bool setPosition(int64 newPosition)=0
Tries to move the stream's output position.
virtual bool writeShortBigEndian(short value)
Writes a 16-bit integer to the stream in a big-endian byte order.
virtual bool writeInt(int value)
Writes a 32-bit integer to the stream in a little-endian byte order.
JUCE_CONSTEXPR bool contains(const ValueType position) const noexcept
Returns true if the given position lies inside this range.
Definition juce_Range.h:213
A container for holding a set of strings which are keyed by another string.
String getValue(StringRef, const String &defaultReturnValue) const
Finds the value corresponding to a key string.
void set(const String &key, const String &value)
Adds or amends a key/value pair.
int size() const noexcept
Returns the number of strings in the array.
The JUCE String class!
Definition juce_String.h:43
int getIntValue() const noexcept
Reads the value of the string as a decimal number (up to 32 bits in size).
Used by AudioFormatReader subclasses to copy data to different formats.
Used by AudioFormatWriter subclasses to copy data to different formats.