OpenShot Audio Library | OpenShotAudio  0.6.0
juce_AiffAudioFormat.cpp
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 
26 namespace juce
27 {
28 
29 static const char* const aiffFormatName = "AIFF file";
30 
31 //==============================================================================
32 const char* const AiffAudioFormat::appleOneShot = "apple one shot";
33 const char* const AiffAudioFormat::appleRootSet = "apple root set";
34 const char* const AiffAudioFormat::appleRootNote = "apple root note";
35 const char* const AiffAudioFormat::appleBeats = "apple beats";
36 const char* const AiffAudioFormat::appleDenominator = "apple denominator";
37 const char* const AiffAudioFormat::appleNumerator = "apple numerator";
38 const char* const AiffAudioFormat::appleTag = "apple tag";
39 const char* const AiffAudioFormat::appleKey = "apple key";
40 
41 //==============================================================================
42 namespace AiffFileHelpers
43 {
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  {
93  return ByteOrder::swapIfLittleEndian ((uint16) values.getValue (name, def).getIntValue());
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  //==============================================================================
128  struct BASCChunk
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);
157  setBoolFlag (metadata, AiffAudioFormat::appleRootSet, rootNoteSet);
158 
159  if (rootNoteSet)
160  metadata.emplace (AiffAudioFormat::appleRootNote, String (rootNote));
161 
162  metadata.emplace (AiffAudioFormat::appleBeats, String (numBeats));
163  metadata.emplace (AiffAudioFormat::appleDenominator, String (timeSigDen));
164  metadata.emplace (AiffAudioFormat::appleNumerator, String (timeSigNum));
165 
166  const char* keyString = nullptr;
167 
168  switch (key)
169  {
170  case minor: keyString = "minor"; break;
171  case major: keyString = "major"; break;
172  case neither: keyString = "neither"; break;
173  case both: keyString = "both"; break;
174  default: break;
175  }
176 
177  if (keyString != nullptr)
178  metadata.emplace (AiffAudioFormat::appleKey, keyString);
179  }
180 
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  {
207  return CharacterFunctions::isLetterOrDigit (d[0]) && CharacterFunctions::isUpperCase (static_cast<juce_wchar> (d[0]))
208  && CharacterFunctions::isLetterOrDigit (d[1]) && CharacterFunctions::isLowerCase (static_cast<juce_wchar> (d[1]))
209  && CharacterFunctions::isLetterOrDigit (d[2]) && CharacterFunctions::isLowerCase (static_cast<juce_wchar> (d[2]));
210  }
211 
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 //==============================================================================
385 class AiffAudioFormatReader final : public AudioFormatReader
386 {
387 public:
388  AiffAudioFormatReader (InputStream* in)
389  : AudioFormatReader (in, aiffFormatName)
390  {
391  using namespace AiffFileHelpers;
392 
393  std::map<String, String> metadataValuesMap;
394 
395  for (int i = 0; i != metadataValues.size(); ++i)
396  {
397  metadataValuesMap.emplace (metadataValues.getAllKeys().getReference (i),
398  metadataValues.getAllValues().getReference (i));
399  }
400 
401  // If this fails, there were duplicate keys in the metadata
402  jassert ((size_t) metadataValuesMap.size() == (size_t) metadataValues.size());
403 
404  if (input->readInt() == chunkName ("FORM"))
405  {
406  auto len = input->readIntBigEndian();
407  auto end = input->getPosition() + len;
408  auto nextType = input->readInt();
409 
410  if (nextType == chunkName ("AIFF") || nextType == chunkName ("AIFC"))
411  {
412  bool hasGotVer = false;
413  bool hasGotData = false;
414  bool hasGotType = false;
415 
416  while (input->getPosition() < end)
417  {
418  auto type = input->readInt();
419  auto length = (uint32) input->readIntBigEndian();
420  auto chunkEnd = input->getPosition() + length;
421 
422  if (type == chunkName ("FVER"))
423  {
424  hasGotVer = true;
425  auto ver = input->readIntBigEndian();
426 
427  if (ver != 0 && ver != (int) 0xa2805140)
428  break;
429  }
430  else if (type == chunkName ("COMM"))
431  {
432  hasGotType = true;
433 
434  numChannels = (unsigned int) input->readShortBigEndian();
435  lengthInSamples = input->readIntBigEndian();
436  bitsPerSample = (unsigned int) input->readShortBigEndian();
437  bytesPerFrame = (int) ((numChannels * bitsPerSample) >> 3);
438 
439  unsigned char sampleRateBytes[10];
440  input->read (sampleRateBytes, 10);
441  const int byte0 = sampleRateBytes[0];
442 
443  if ((byte0 & 0x80) != 0
444  || byte0 <= 0x3F || byte0 > 0x40
445  || (byte0 == 0x40 && sampleRateBytes[1] > 0x1C))
446  break;
447 
448  auto sampRate = ByteOrder::bigEndianInt (sampleRateBytes + 2);
449  sampRate >>= (16414 - ByteOrder::bigEndianShort (sampleRateBytes));
450  sampleRate = (int) sampRate;
451 
452  if (length <= 18)
453  {
454  // some types don't have a chunk large enough to include a compression
455  // type, so assume it's just big-endian pcm
456  littleEndian = false;
457  }
458  else
459  {
460  auto compType = input->readInt();
461 
462  if (compType == chunkName ("NONE") || compType == chunkName ("twos"))
463  {
464  littleEndian = false;
465  }
466  else if (compType == chunkName ("sowt"))
467  {
468  littleEndian = true;
469  }
470  else if (compType == chunkName ("fl32") || compType == chunkName ("FL32"))
471  {
472  littleEndian = false;
473  usesFloatingPointData = true;
474  }
475  else
476  {
477  sampleRate = 0;
478  break;
479  }
480  }
481  }
482  else if (type == chunkName ("SSND"))
483  {
484  hasGotData = true;
485 
486  auto offset = input->readIntBigEndian();
487  dataChunkStart = input->getPosition() + 4 + offset;
488  lengthInSamples = (bytesPerFrame > 0) ? jmin (lengthInSamples, ((int64) length) / (int64) bytesPerFrame) : 0;
489  }
490  else if (type == chunkName ("MARK"))
491  {
492  auto numCues = (uint16) input->readShortBigEndian();
493 
494  // these two are always the same for AIFF-read files
495  metadataValuesMap.emplace ("NumCuePoints", String (numCues));
496  metadataValuesMap.emplace ("NumCueLabels", String (numCues));
497 
498  for (uint16 i = 0; i < numCues; ++i)
499  {
500  auto identifier = (uint16) input->readShortBigEndian();
501  auto offset = (uint32) input->readIntBigEndian();
502  auto stringLength = (uint8) input->readByte();
503  MemoryBlock textBlock;
504  input->readIntoMemoryBlock (textBlock, stringLength);
505 
506  // if the stringLength is even then read one more byte as the
507  // string needs to be an even number of bytes INCLUDING the
508  // leading length character in the pascal string
509  if ((stringLength & 1) == 0)
510  input->readByte();
511 
512  auto prefixCue = "Cue" + String (i);
513  metadataValuesMap.emplace (prefixCue + "Identifier", String (identifier));
514  metadataValuesMap.emplace (prefixCue + "Offset", String (offset));
515 
516  auto prefixLabel = "CueLabel" + String (i);
517  metadataValuesMap.emplace (prefixLabel + "Identifier", String (identifier));
518  metadataValuesMap.emplace (prefixLabel + "Text", textBlock.toString());
519  }
520  }
521  else if (type == chunkName ("COMT"))
522  {
523  auto numNotes = (uint16) input->readShortBigEndian();
524  metadataValuesMap.emplace ("NumCueNotes", String (numNotes));
525 
526  for (uint16 i = 0; i < numNotes; ++i)
527  {
528  auto timestamp = (uint32) input->readIntBigEndian();
529  auto identifier = (uint16) input->readShortBigEndian(); // may be zero in this case
530  auto stringLength = (uint16) input->readShortBigEndian();
531 
532  MemoryBlock textBlock;
533  input->readIntoMemoryBlock (textBlock, stringLength + (stringLength & 1));
534 
535  auto prefix = "CueNote" + String (i);
536  metadataValuesMap.emplace (prefix + "TimeStamp", String (timestamp));
537  metadataValuesMap.emplace (prefix + "Identifier", String (identifier));
538  metadataValuesMap.emplace (prefix + "Text", textBlock.toString());
539  }
540  }
541  else if (type == chunkName ("INST"))
542  {
543  HeapBlock<InstChunk> inst;
544  inst.calloc (jmax ((size_t) length + 1, sizeof (InstChunk)), 1);
545  input->read (inst, (int) length);
546  inst->copyTo (metadataValuesMap);
547  }
548  else if (type == chunkName ("basc"))
549  {
550  AiffFileHelpers::BASCChunk (*input).addToMetadata (metadataValuesMap);
551  }
552  else if (type == chunkName ("cate"))
553  {
554  metadataValuesMap.emplace (AiffAudioFormat::appleTag,
555  AiffFileHelpers::CATEChunk::read (*input, length));
556  }
557  else if ((hasGotVer && hasGotData && hasGotType)
558  || chunkEnd < input->getPosition()
559  || input->isExhausted())
560  {
561  break;
562  }
563 
564  input->setPosition (chunkEnd + (chunkEnd & 1)); // (chunks should be aligned to an even byte address)
565  }
566  }
567  }
568 
569  if (metadataValuesMap.size() > 0)
570  metadataValuesMap.emplace ("MetaDataSource", "AIFF");
571 
572  metadataValues.addMap (metadataValuesMap);
573  }
574 
575  //==============================================================================
576  bool readSamples (int* const* destSamples, int numDestChannels, int startOffsetInDestBuffer,
577  int64 startSampleInFile, int numSamples) override
578  {
579  clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
580  startSampleInFile, numSamples, lengthInSamples);
581 
582  if (numSamples <= 0)
583  return true;
584 
585  input->setPosition (dataChunkStart + startSampleInFile * bytesPerFrame);
586 
587  while (numSamples > 0)
588  {
589  const int tempBufSize = 480 * 3 * 4; // (keep this a multiple of 3)
590  char tempBuffer [tempBufSize];
591 
592  const int numThisTime = jmin (tempBufSize / bytesPerFrame, numSamples);
593  const int bytesRead = input->read (tempBuffer, numThisTime * bytesPerFrame);
594 
595  if (bytesRead < numThisTime * bytesPerFrame)
596  {
597  jassert (bytesRead >= 0);
598  zeromem (tempBuffer + bytesRead, (size_t) (numThisTime * bytesPerFrame - bytesRead));
599  }
600 
601  if (littleEndian)
602  copySampleData<AudioData::LittleEndian> (bitsPerSample, usesFloatingPointData,
603  destSamples, startOffsetInDestBuffer, numDestChannels,
604  tempBuffer, (int) numChannels, numThisTime);
605  else
606  copySampleData<AudioData::BigEndian> (bitsPerSample, usesFloatingPointData,
607  destSamples, startOffsetInDestBuffer, numDestChannels,
608  tempBuffer, (int) numChannels, numThisTime);
609 
610  startOffsetInDestBuffer += numThisTime;
611  numSamples -= numThisTime;
612  }
613 
614  return true;
615  }
616 
617  template <typename Endianness>
618  static void copySampleData (unsigned int numBitsPerSample, bool floatingPointData,
619  int* const* destSamples, int startOffsetInDestBuffer, int numDestChannels,
620  const void* sourceData, int numberOfChannels, int numSamples) noexcept
621  {
622  switch (numBitsPerSample)
623  {
624  case 8: ReadHelper<AudioData::Int32, AudioData::Int8, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); break;
625  case 16: ReadHelper<AudioData::Int32, AudioData::Int16, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); break;
626  case 24: ReadHelper<AudioData::Int32, AudioData::Int24, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); break;
627  case 32: if (floatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
628  else ReadHelper<AudioData::Int32, AudioData::Int32, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
629  break;
630  default: jassertfalse; break;
631  }
632  }
633 
634  int bytesPerFrame;
635  int64 dataChunkStart;
636  bool littleEndian;
637 
638 private:
639  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AiffAudioFormatReader)
640 };
641 
642 //==============================================================================
643 class AiffAudioFormatWriter final : public AudioFormatWriter
644 {
645 public:
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 
714 private:
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
728  jassert (couldSeekOk);
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"));
737  output->writeIntBigEndian (headerLen + audioBytes - 8);
738  output->writeInt (chunkName ("AIFF"));
739  output->writeInt (chunkName ("COMM"));
740  output->writeIntBigEndian (18);
741  output->writeShortBigEndian ((short) numChannels);
742  output->writeIntBigEndian ((int) lengthInSamples);
743  output->writeShortBigEndian ((short) bitsPerSample);
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  {
760  jassertfalse;
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 
786  output->write (sampleRateBytes, 10);
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"));
810  output->writeIntBigEndian (audioBytes + 8);
811  output->writeInt (0);
812  output->writeInt (0);
813 
814  jassert (output->getPosition() == headerLen);
815  }
816 
817  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AiffAudioFormatWriter)
818 };
819 
820 //==============================================================================
821 class MemoryMappedAiffReader final : public MemoryMappedAudioFormatReader
822 {
823 public:
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 
831  bool readSamples (int* const* destSamples, int numDestChannels, int startOffsetInDestBuffer,
832  int64 startSampleInFile, int numSamples) override
833  {
834  clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
835  startSampleInFile, numSamples, lengthInSamples);
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>
848  (bitsPerSample, usesFloatingPointData, destSamples, startOffsetInDestBuffer,
849  numDestChannels, sampleToPointer (startSampleInFile), (int) numChannels, numSamples);
850  else
851  AiffAudioFormatReader::copySampleData<AudioData::BigEndian>
852  (bitsPerSample, usesFloatingPointData, destSamples, startOffsetInDestBuffer,
853  numDestChannels, sampleToPointer (startSampleInFile), (int) numChannels, numSamples);
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  {
877  case 8: ReadHelper<AudioData::Float32, AudioData::UInt8, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num); break;
878  case 16: ReadHelper<AudioData::Float32, AudioData::Int16, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num); break;
879  case 24: ReadHelper<AudioData::Float32, AudioData::Int24, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num); break;
880  case 32: if (usesFloatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
881  else ReadHelper<AudioData::Float32, AudioData::Int32, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
882  break;
883  default: jassertfalse; break;
884  }
885  }
886  else
887  {
888  switch (bitsPerSample)
889  {
890  case 8: ReadHelper<AudioData::Float32, AudioData::UInt8, AudioData::BigEndian>::read (dest, 0, 1, source, 1, num); break;
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;
893  case 32: if (usesFloatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::BigEndian>::read (dest, 0, 1, source, 1, num);
894  else ReadHelper<AudioData::Float32, AudioData::Int32, AudioData::BigEndian>::read (dest, 0, 1, source, 1, num);
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;
920  case 32: if (usesFloatingPointData) scanMinAndMax<AudioData::Float32> (startSampleInFile, numSamples, results, numChannelsToRead);
921  else scanMinAndMax<AudioData::Int32> (startSampleInFile, numSamples, results, numChannelsToRead);
922  break;
923  default: jassertfalse; break;
924  }
925  }
926 
928 
929 private:
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  {
942  return littleEndian ? scanMinAndMaxInterleaved<SampleType, AudioData::LittleEndian> (channel, startSampleInFile, numSamples)
943  : scanMinAndMaxInterleaved<SampleType, AudioData::BigEndian> (channel, startSampleInFile, numSamples);
944  }
945 
946  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedAiffReader)
947 };
948 
949 //==============================================================================
950 AiffAudioFormat::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 
963 bool AiffAudioFormat::canDoStereo() { return true; }
964 bool AiffAudioFormat::canDoMono() { return true; }
965 
966 #if JUCE_MAC
967 bool AiffAudioFormat::canHandleFile (const File& f)
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 
980 AudioFormatReader* AiffAudioFormat::createReaderFor (InputStream* sourceStream, bool deleteStreamIfOpeningFails)
981 {
982  std::unique_ptr<AiffAudioFormatReader> w (new AiffAudioFormatReader (sourceStream));
983 
984  if (w->sampleRate > 0 && w->numChannels > 0)
985  return w.release();
986 
987  if (! deleteStreamIfOpeningFails)
988  w->input = nullptr;
989 
990  return nullptr;
991 }
992 
994 {
995  return createMemoryMappedReader (file.createInputStream().release());
996 }
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
static const char *const appleDenominator
static const char *const appleOneShot
AudioFormatReader * createReaderFor(InputStream *sourceStream, bool deleteStreamIfOpeningFails) override
Array< int > getPossibleBitDepths() override
static const char *const appleNumerator
static const char *const appleTag
static const char *const appleBeats
virtual AudioFormatWriter * createWriterFor(OutputStream *streamToWriteTo, double sampleRateToUse, unsigned int numberOfChannels, int bitsPerSample, const StringPairArray &metadataValues, int qualityOptionIndex)=0
Array< int > getPossibleSampleRates() override
static const char *const appleRootSet
MemoryMappedAudioFormatReader * createMemoryMappedReader(const File &) override
static const char *const appleKey
static const char *const appleRootNote
static void clearSamplesBeyondAvailableLength(int *const *destChannels, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int &numSamples, int64 fileLengthInSamples)
AudioFormatReader(InputStream *sourceStream, const String &formatName)
virtual void readMaxLevels(int64 startSample, int64 numSamples, Range< float > *results, int numChannelsToRead)
virtual bool canHandleFile(const File &fileToTest)
constexpr static uint32 bigEndianInt(const void *bytes) noexcept
constexpr static uint32 littleEndianInt(const void *bytes) noexcept
static Type swapIfLittleEndian(Type value) noexcept
constexpr static uint16 bigEndianShort(const void *bytes) noexcept
static bool isLowerCase(juce_wchar character) noexcept
static bool isLetterOrDigit(char character) noexcept
static bool isUpperCase(juce_wchar character) noexcept
const File & getFile() const noexcept
std::unique_ptr< FileInputStream > createInputStream() const
Definition: juce_File.cpp:732