JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_AiffAudioFormat.cpp
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - 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 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-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
26namespace juce
27{
28
29static const char* const aiffFormatName = "AIFF file";
30
31//==============================================================================
32const char* const AiffAudioFormat::appleOneShot = "apple one shot";
33const char* const AiffAudioFormat::appleRootSet = "apple root set";
34const char* const AiffAudioFormat::appleRootNote = "apple root note";
35const char* const AiffAudioFormat::appleBeats = "apple beats";
36const char* const AiffAudioFormat::appleDenominator = "apple denominator";
37const char* const AiffAudioFormat::appleNumerator = "apple numerator";
38const char* const AiffAudioFormat::appleTag = "apple tag";
39const char* const AiffAudioFormat::appleKey = "apple key";
40
41//==============================================================================
42namespace AiffFileHelpers
43{
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
70 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
91 static uint16 getValue16 (const StringPairArray& values, const char* name, const char* def)
92 {
94 }
95
96 static int8 getValue8 (const StringPairArray& values, const char* name, const char* def)
97 {
98 return (int8) values.getValue (name, def).getIntValue();
99 }
100
101 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 //==============================================================================
129 {
130 enum Key
131 {
132 minor = 1,
133 major = 2,
134 neither = 3,
135 both = 4
136 };
137
138 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
152 void addToMetadata (std::map<String, String>& metadata) const
153 {
154 const bool rootNoteSet = rootNote != 0;
155
156 setBoolFlag (metadata, AiffAudioFormat::appleOneShot, oneShot == 2);
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)
179 }
180
181 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 {
205 static bool isValidTag (const char* d) noexcept
206 {
210 }
211
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
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 {
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
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 {
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//==============================================================================
386{
387public:
389 : AudioFormatReader (in, aiffFormatName)
390 {
391 using namespace AiffFileHelpers;
392
394
395 for (int i = 0; i != metadataValues.size(); ++i)
396 {
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();
437 bytesPerFrame = (int) ((numChannels * bitsPerSample) >> 3);
438
439 unsigned char sampleRateBytes[10];
441 const int byte0 = sampleRateBytes[0];
442
443 if ((byte0 & 0x80) != 0
445 || (byte0 == 0x40 && sampleRateBytes[1] > 0x1C))
446 break;
447
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;
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();
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 {
524 metadataValuesMap.emplace ("NumCueNotes", String (numNotes));
525
526 for (uint16 i = 0; i < numNotes; ++i)
527 {
529 auto identifier = (uint16) input->readShortBigEndian(); // may be zero in this case
531
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 {
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 {
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
573 }
574
575 //==============================================================================
577 int64 startSampleInFile, int numSamples) override
578 {
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)
604 tempBuffer, (int) numChannels, numThisTime);
605 else
608 tempBuffer, (int) numChannels, numThisTime);
609
611 numSamples -= numThisTime;
612 }
613
614 return true;
615 }
616
617 template <typename Endianness>
618 static void copySampleData (unsigned int numBitsPerSample, bool floatingPointData,
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
638private:
640};
641
642//==============================================================================
644{
645public:
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
669 ~AiffAudioFormatWriter() override
670 {
671 if ((bytesWritten & 1) != 0)
672 output->writeByte (0);
673
674 writeHeader();
675 }
676
677 //==============================================================================
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
714private:
715 MemoryBlock tempBlock, markChunk, comtChunk, instChunk;
716 uint64 lengthInSamples = 0, bytesWritten = 0;
717 int64 headerPosition = 0;
718 bool writeFailed = false;
719
720 void writeHeader()
721 {
722 using namespace AiffFileHelpers;
723
724 [[maybe_unused]] const bool couldSeekOk = output->setPosition (headerPosition);
725
726 // if this fails, you've given it an output stream that can't seek! It needs
727 // to be able to seek back to write the header
729
730 auto headerLen = (int) (54 + (markChunk.isEmpty() ? 0 : markChunk.getSize() + 8)
731 + (comtChunk.isEmpty() ? 0 : comtChunk.getSize() + 8)
732 + (instChunk.isEmpty() ? 0 : instChunk.getSize() + 8));
733 auto audioBytes = (int) (lengthInSamples * ((bitsPerSample * numChannels) / 8));
734 audioBytes += (audioBytes & 1);
735
736 output->writeInt (chunkName ("FORM"));
738 output->writeInt (chunkName ("AIFF"));
739 output->writeInt (chunkName ("COMM"));
742 output->writeIntBigEndian ((int) lengthInSamples);
744
745 uint8 sampleRateBytes[10] = {};
746
747 if (sampleRate <= 1)
748 {
749 sampleRateBytes[0] = 0x3f;
750 sampleRateBytes[1] = 0xff;
751 sampleRateBytes[2] = 0x80;
752 }
753 else
754 {
755 int mask = 0x40000000;
756 sampleRateBytes[0] = 0x40;
757
758 if (sampleRate >= mask)
759 {
761 sampleRateBytes[1] = 0x1d;
762 }
763 else
764 {
765 int n = (int) sampleRate;
766 int i;
767
768 for (i = 0; i <= 32 ; ++i)
769 {
770 if ((n & mask) != 0)
771 break;
772
773 mask >>= 1;
774 }
775
776 n = n << (i + 1);
777
778 sampleRateBytes[1] = (uint8) (29 - i);
779 sampleRateBytes[2] = (uint8) ((n >> 24) & 0xff);
780 sampleRateBytes[3] = (uint8) ((n >> 16) & 0xff);
781 sampleRateBytes[4] = (uint8) ((n >> 8) & 0xff);
782 sampleRateBytes[5] = (uint8) (n & 0xff);
783 }
784 }
785
787
788 if (! markChunk.isEmpty())
789 {
790 output->writeInt (chunkName ("MARK"));
791 output->writeIntBigEndian ((int) markChunk.getSize());
792 *output << markChunk;
793 }
794
795 if (! comtChunk.isEmpty())
796 {
797 output->writeInt (chunkName ("COMT"));
798 output->writeIntBigEndian ((int) comtChunk.getSize());
799 *output << comtChunk;
800 }
801
802 if (! instChunk.isEmpty())
803 {
804 output->writeInt (chunkName ("INST"));
805 output->writeIntBigEndian ((int) instChunk.getSize());
806 *output << instChunk;
807 }
808
809 output->writeInt (chunkName ("SSND"));
811 output->writeInt (0);
812 output->writeInt (0);
813
815 }
816
818};
819
820//==============================================================================
822{
823public:
824 MemoryMappedAiffReader (const File& f, const AiffAudioFormatReader& reader)
825 : MemoryMappedAudioFormatReader (f, reader, reader.dataChunkStart,
826 reader.bytesPerFrame * reader.lengthInSamples, reader.bytesPerFrame),
827 littleEndian (reader.littleEndian)
828 {
829 }
830
832 int64 startSampleInFile, int numSamples) override
833 {
836
837 if (numSamples <= 0)
838 return true;
839
840 if (map == nullptr || ! mappedSection.contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
841 {
842 jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read.
843 return false;
844 }
845
846 if (littleEndian)
847 AiffAudioFormatReader::copySampleData<AudioData::LittleEndian>
850 else
851 AiffAudioFormatReader::copySampleData<AudioData::BigEndian>
854
855 return true;
856 }
857
858 void getSample (int64 sample, float* result) const noexcept override
859 {
860 auto num = (int) numChannels;
861
862 if (map == nullptr || ! mappedSection.contains (sample))
863 {
864 jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read.
865
866 zeromem (result, (size_t) num * sizeof (float));
867 return;
868 }
869
870 float** dest = &result;
871 const void* source = sampleToPointer (sample);
872
873 if (littleEndian)
874 {
875 switch (bitsPerSample)
876 {
882 break;
883 default: jassertfalse; break;
884 }
885 }
886 else
887 {
888 switch (bitsPerSample)
889 {
891 case 16: ReadHelper<AudioData::Float32, AudioData::Int16, AudioData::BigEndian>::read (dest, 0, 1, source, 1, num); break;
892 case 24: ReadHelper<AudioData::Float32, AudioData::Int24, AudioData::BigEndian>::read (dest, 0, 1, source, 1, num); break;
895 break;
896 default: jassertfalse; break;
897 }
898 }
899 }
900
901 void readMaxLevels (int64 startSampleInFile, int64 numSamples, Range<float>* results, int numChannelsToRead) override
902 {
903 numSamples = jmin (numSamples, lengthInSamples - startSampleInFile);
904
905 if (map == nullptr || numSamples <= 0 || ! mappedSection.contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
906 {
907 jassert (numSamples <= 0); // you must make sure that the window contains all the samples you're going to attempt to read.
908
909 for (int i = 0; i < numChannelsToRead; ++i)
910 results[i] = Range<float>();
911
912 return;
913 }
914
915 switch (bitsPerSample)
916 {
917 case 8: scanMinAndMax<AudioData::UInt8> (startSampleInFile, numSamples, results, numChannelsToRead); break;
918 case 16: scanMinAndMax<AudioData::Int16> (startSampleInFile, numSamples, results, numChannelsToRead); break;
919 case 24: scanMinAndMax<AudioData::Int24> (startSampleInFile, numSamples, results, numChannelsToRead); break;
922 break;
923 default: jassertfalse; break;
924 }
925 }
926
928
929private:
930 const bool littleEndian;
931
932 template <typename SampleType>
933 void scanMinAndMax (int64 startSampleInFile, int64 numSamples, Range<float>* results, int numChannelsToRead) const noexcept
934 {
935 for (int i = 0; i < numChannelsToRead; ++i)
936 results[i] = scanMinAndMaxForChannel<SampleType> (i, startSampleInFile, numSamples);
937 }
938
939 template <typename SampleType>
940 Range<float> scanMinAndMaxForChannel (int channel, int64 startSampleInFile, int64 numSamples) const noexcept
941 {
943 : scanMinAndMaxInterleaved<SampleType, AudioData::BigEndian> (channel, startSampleInFile, numSamples);
944 }
945
946 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedAiffReader)
947};
948
949//==============================================================================
950AiffAudioFormat::AiffAudioFormat() : AudioFormat (aiffFormatName, ".aiff .aif") {}
952
954{
955 return { 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000 };
956}
957
959{
960 return { 8, 16, 24 };
961}
962
963bool AiffAudioFormat::canDoStereo() { return true; }
964bool AiffAudioFormat::canDoMono() { return true; }
965
966#if JUCE_MAC
968{
970 return true;
971
972 auto type = f.getMacOSType();
973
974 // (NB: written as hex to avoid four-char-constant warnings)
975 return type == 0x41494646 /* AIFF */ || type == 0x41494643 /* AIFC */
976 || type == 0x61696666 /* aiff */ || type == 0x61696663 /* aifc */;
977}
978#endif
979
981{
983
984 if (w->sampleRate > 0 && w->numChannels > 0)
985 return w.release();
986
988 w->input = nullptr;
989
990 return nullptr;
991}
992
997
999{
1000 if (fin != nullptr)
1001 {
1002 AiffAudioFormatReader reader (fin);
1003
1004 if (reader.lengthInSamples > 0)
1005 return new MemoryMappedAiffReader (fin->getFile(), reader);
1006 }
1007
1008 return nullptr;
1009}
1010
1012 double sampleRate,
1013 unsigned int numberOfChannels,
1014 int bitsPerSample,
1015 const StringPairArray& metadataValues,
1016 int /*qualityOptionIndex*/)
1017{
1018 if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample))
1019 return new AiffAudioFormatWriter (out, sampleRate, numberOfChannels,
1020 (unsigned int) bitsPerSample, metadataValues);
1021
1022 return nullptr;
1023}
1024
1025} // namespace juce
bool readSamples(int *const *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.
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:56
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 *const *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.
virtual void readMaxLevels(int64 startSample, int64 numSamples, Range< float > *results, int numChannelsToRead)
Finds the highest and lowest sample levels from a section of the audio stream.
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 constexpr uint32 bigEndianInt(const void *bytes) noexcept
Turns 4 bytes into a big-endian integer.
static constexpr uint32 littleEndianInt(const void *bytes) noexcept
Turns 4 bytes into a little-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 constexpr uint16 bigEndianShort(const void *bytes) noexcept
Turns 2 bytes into a big-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.
std::unique_ptr< FileInputStream > createInputStream() const
Creates a stream to read from this file.
Very simple container class to hold a pointer to some data on the heap.
void calloc(SizeType newNumElements, const size_t elementSize=sizeof(ElementType))
Allocates a specified amount of memory and clears it.
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.
bool isEmpty() const noexcept
Returns true if the memory block has zero size.
void * getData() noexcept
Returns a void pointer to the data.
size_t getSize() const noexcept
Returns the block's current allocated size, in bytes.
void ensureSize(size_t minimumSize, bool initialiseNewSpaceToZero=false)
Increases the block's size only if it's smaller than a given size.
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 *const *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.
A general-purpose range object, that simply represents any linear range with a start and end point.
Definition juce_Range.h:40
constexpr bool contains(const ValueType position) const noexcept
Returns true if the given position lies inside this range.
Definition juce_Range.h:214
String & getReference(int index) noexcept
Returns a reference to one of the strings in the array.
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.
const StringArray & getAllValues() const noexcept
Returns a list of all values in the array.
void addMap(const std::map< String, String > &mapToAdd)
Adds the contents of a map to this StringPairArray.
int size() const noexcept
Returns the number of strings in the array.
const StringArray & getAllKeys() const noexcept
Returns a list of all keys in the array.
The JUCE String class!
Definition juce_String.h:53
int getIntValue() const noexcept
Reads the value of the string as a decimal number (up to 32 bits in size).
T data(T... args)
T emplace(T... args)
#define jassert(expression)
Platform-independent assertion macro.
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
This is a shorthand way of writing both a JUCE_DECLARE_NON_COPYABLE and JUCE_LEAK_DETECTOR macro for ...
#define jassertfalse
This will always cause an assertion failure.
typedef int
JUCE Namespace.
void zerostruct(Type &structure) noexcept
Overwrites a structure or object with zeros.
Definition juce_Memory.h:32
unsigned short uint16
A platform-independent 16-bit unsigned integer type.
wchar_t juce_wchar
A platform-independent 32-bit unicode character type.
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
constexpr Type jmax(Type a, Type b)
Returns the larger of two values.
RangedDirectoryIterator end(const RangedDirectoryIterator &)
Returns a default-constructed sentinel value.
signed short int16
A platform-independent 16-bit signed integer type.
signed char int8
A platform-independent 8-bit signed integer type.
unsigned long long uint64
A platform-independent 64-bit unsigned integer type.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
unsigned int uint32
A platform-independent 32-bit unsigned integer type.
unsigned char uint8
A platform-independent 8-bit unsigned integer type.
constexpr int numElementsInArray(Type(&)[N]) noexcept
Handy function for getting the number of elements in a simple const C array.
long long int64
A platform-independent 64-bit integer type.
void zeromem(void *memory, size_t numBytes) noexcept
Fills a block of memory with zeros.
Definition juce_Memory.h:28
read
T release(T... args)
Used by AudioFormatReader subclasses to copy data to different formats.
Used by AudioFormatWriter subclasses to copy data to different formats.
typedef size_t