OpenShot Audio Library | OpenShotAudio  0.3.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 
27 namespace juce
28 {
29 
30 static const char* const aiffFormatName = "AIFF file";
31 
32 //==============================================================================
33 const char* const AiffAudioFormat::appleOneShot = "apple one shot";
34 const char* const AiffAudioFormat::appleRootSet = "apple root set";
35 const char* const AiffAudioFormat::appleRootNote = "apple root note";
36 const char* const AiffAudioFormat::appleBeats = "apple beats";
37 const char* const AiffAudioFormat::appleDenominator = "apple denominator";
38 const char* const AiffAudioFormat::appleNumerator = "apple numerator";
39 const char* const AiffAudioFormat::appleTag = "apple tag";
40 const char* const AiffAudioFormat::appleKey = "apple key";
41 
42 //==============================================================================
43 namespace AiffFileHelpers
44 {
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  {
94  return ByteOrder::swapIfLittleEndian ((uint16) values.getValue (name, def).getIntValue());
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  //==============================================================================
129  struct BASCChunk
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)
178  metadata.set (AiffAudioFormat::appleKey, keyString);
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  {
205  return CharacterFunctions::isLetterOrDigit (d[0]) && CharacterFunctions::isUpperCase (static_cast<juce_wchar> (d[0]))
206  && CharacterFunctions::isLetterOrDigit (d[1]) && CharacterFunctions::isLowerCase (static_cast<juce_wchar> (d[1]))
207  && CharacterFunctions::isLetterOrDigit (d[2]) && CharacterFunctions::isLowerCase (static_cast<juce_wchar> (d[2]));
208  }
209 
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 //==============================================================================
383 class AiffAudioFormatReader : public AudioFormatReader
384 {
385 public:
386  AiffAudioFormatReader (InputStream* in)
387  : AudioFormatReader (in, aiffFormatName)
388  {
389  using namespace AiffFileHelpers;
390 
391  if (input->readInt() == chunkName ("FORM"))
392  {
393  auto len = input->readIntBigEndian();
394  auto end = input->getPosition() + len;
395  auto nextType = input->readInt();
396 
397  if (nextType == chunkName ("AIFF") || nextType == chunkName ("AIFC"))
398  {
399  bool hasGotVer = false;
400  bool hasGotData = false;
401  bool hasGotType = false;
402 
403  while (input->getPosition() < end)
404  {
405  auto type = input->readInt();
406  auto length = (uint32) input->readIntBigEndian();
407  auto chunkEnd = input->getPosition() + length;
408 
409  if (type == chunkName ("FVER"))
410  {
411  hasGotVer = true;
412  auto ver = input->readIntBigEndian();
413 
414  if (ver != 0 && ver != (int) 0xa2805140)
415  break;
416  }
417  else if (type == chunkName ("COMM"))
418  {
419  hasGotType = true;
420 
421  numChannels = (unsigned int) input->readShortBigEndian();
422  lengthInSamples = input->readIntBigEndian();
423  bitsPerSample = (unsigned int) input->readShortBigEndian();
424  bytesPerFrame = (int) ((numChannels * bitsPerSample) >> 3);
425 
426  unsigned char sampleRateBytes[10];
427  input->read (sampleRateBytes, 10);
428  const int byte0 = sampleRateBytes[0];
429 
430  if ((byte0 & 0x80) != 0
431  || byte0 <= 0x3F || byte0 > 0x40
432  || (byte0 == 0x40 && sampleRateBytes[1] > 0x1C))
433  break;
434 
435  auto sampRate = ByteOrder::bigEndianInt (sampleRateBytes + 2);
436  sampRate >>= (16414 - ByteOrder::bigEndianShort (sampleRateBytes));
437  sampleRate = (int) sampRate;
438 
439  if (length <= 18)
440  {
441  // some types don't have a chunk large enough to include a compression
442  // type, so assume it's just big-endian pcm
443  littleEndian = false;
444  }
445  else
446  {
447  auto compType = input->readInt();
448 
449  if (compType == chunkName ("NONE") || compType == chunkName ("twos"))
450  {
451  littleEndian = false;
452  }
453  else if (compType == chunkName ("sowt"))
454  {
455  littleEndian = true;
456  }
457  else if (compType == chunkName ("fl32") || compType == chunkName ("FL32"))
458  {
459  littleEndian = false;
460  usesFloatingPointData = true;
461  }
462  else
463  {
464  sampleRate = 0;
465  break;
466  }
467  }
468  }
469  else if (type == chunkName ("SSND"))
470  {
471  hasGotData = true;
472 
473  auto offset = input->readIntBigEndian();
474  dataChunkStart = input->getPosition() + 4 + offset;
475  lengthInSamples = (bytesPerFrame > 0) ? jmin (lengthInSamples, ((int64) length) / (int64) bytesPerFrame) : 0;
476  }
477  else if (type == chunkName ("MARK"))
478  {
479  auto numCues = (uint16) input->readShortBigEndian();
480 
481  // these two are always the same for AIFF-read files
482  metadataValues.set ("NumCuePoints", String (numCues));
483  metadataValues.set ("NumCueLabels", String (numCues));
484 
485  for (uint16 i = 0; i < numCues; ++i)
486  {
487  auto identifier = (uint16) input->readShortBigEndian();
488  auto offset = (uint32) input->readIntBigEndian();
489  auto stringLength = (uint8) input->readByte();
490  MemoryBlock textBlock;
491  input->readIntoMemoryBlock (textBlock, stringLength);
492 
493  // if the stringLength is even then read one more byte as the
494  // string needs to be an even number of bytes INCLUDING the
495  // leading length character in the pascal string
496  if ((stringLength & 1) == 0)
497  input->readByte();
498 
499  auto prefixCue = "Cue" + String (i);
500  metadataValues.set (prefixCue + "Identifier", String (identifier));
501  metadataValues.set (prefixCue + "Offset", String (offset));
502 
503  auto prefixLabel = "CueLabel" + String (i);
504  metadataValues.set (prefixLabel + "Identifier", String (identifier));
505  metadataValues.set (prefixLabel + "Text", textBlock.toString());
506  }
507  }
508  else if (type == chunkName ("COMT"))
509  {
510  auto numNotes = (uint16) input->readShortBigEndian();
511  metadataValues.set ("NumCueNotes", String (numNotes));
512 
513  for (uint16 i = 0; i < numNotes; ++i)
514  {
515  auto timestamp = (uint32) input->readIntBigEndian();
516  auto identifier = (uint16) input->readShortBigEndian(); // may be zero in this case
517  auto stringLength = (uint16) input->readShortBigEndian();
518 
519  MemoryBlock textBlock;
520  input->readIntoMemoryBlock (textBlock, stringLength + (stringLength & 1));
521 
522  auto prefix = "CueNote" + String (i);
523  metadataValues.set (prefix + "TimeStamp", String (timestamp));
524  metadataValues.set (prefix + "Identifier", String (identifier));
525  metadataValues.set (prefix + "Text", textBlock.toString());
526  }
527  }
528  else if (type == chunkName ("INST"))
529  {
530  HeapBlock<InstChunk> inst;
531  inst.calloc (jmax ((size_t) length + 1, sizeof (InstChunk)), 1);
532  input->read (inst, (int) length);
533  inst->copyTo (metadataValues);
534  }
535  else if (type == chunkName ("basc"))
536  {
537  AiffFileHelpers::BASCChunk (*input).addToMetadata (metadataValues);
538  }
539  else if (type == chunkName ("cate"))
540  {
541  metadataValues.set (AiffAudioFormat::appleTag,
542  AiffFileHelpers::CATEChunk::read (*input, length));
543  }
544  else if ((hasGotVer && hasGotData && hasGotType)
545  || chunkEnd < input->getPosition()
546  || input->isExhausted())
547  {
548  break;
549  }
550 
551  input->setPosition (chunkEnd + (chunkEnd & 1)); // (chunks should be aligned to an even byte address)
552  }
553  }
554  }
555 
556  if (metadataValues.size() > 0)
557  metadataValues.set ("MetaDataSource", "AIFF");
558  }
559 
560  //==============================================================================
561  bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
562  int64 startSampleInFile, int numSamples) override
563  {
564  clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
565  startSampleInFile, numSamples, lengthInSamples);
566 
567  if (numSamples <= 0)
568  return true;
569 
570  input->setPosition (dataChunkStart + startSampleInFile * bytesPerFrame);
571 
572  while (numSamples > 0)
573  {
574  const int tempBufSize = 480 * 3 * 4; // (keep this a multiple of 3)
575  char tempBuffer [tempBufSize];
576 
577  const int numThisTime = jmin (tempBufSize / bytesPerFrame, numSamples);
578  const int bytesRead = input->read (tempBuffer, numThisTime * bytesPerFrame);
579 
580  if (bytesRead < numThisTime * bytesPerFrame)
581  {
582  jassert (bytesRead >= 0);
583  zeromem (tempBuffer + bytesRead, (size_t) (numThisTime * bytesPerFrame - bytesRead));
584  }
585 
586  if (littleEndian)
587  copySampleData<AudioData::LittleEndian> (bitsPerSample, usesFloatingPointData,
588  destSamples, startOffsetInDestBuffer, numDestChannels,
589  tempBuffer, (int) numChannels, numThisTime);
590  else
591  copySampleData<AudioData::BigEndian> (bitsPerSample, usesFloatingPointData,
592  destSamples, startOffsetInDestBuffer, numDestChannels,
593  tempBuffer, (int) numChannels, numThisTime);
594 
595  startOffsetInDestBuffer += numThisTime;
596  numSamples -= numThisTime;
597  }
598 
599  return true;
600  }
601 
602  template <typename Endianness>
603  static void copySampleData (unsigned int numBitsPerSample, bool floatingPointData,
604  int* const* destSamples, int startOffsetInDestBuffer, int numDestChannels,
605  const void* sourceData, int numberOfChannels, int numSamples) noexcept
606  {
607  switch (numBitsPerSample)
608  {
609  case 8: ReadHelper<AudioData::Int32, AudioData::Int8, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); break;
610  case 16: ReadHelper<AudioData::Int32, AudioData::Int16, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); break;
611  case 24: ReadHelper<AudioData::Int32, AudioData::Int24, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); break;
612  case 32: if (floatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
613  else ReadHelper<AudioData::Int32, AudioData::Int32, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
614  break;
615  default: jassertfalse; break;
616  }
617  }
618 
619  int bytesPerFrame;
620  int64 dataChunkStart;
621  bool littleEndian;
622 
623 private:
624  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AiffAudioFormatReader)
625 };
626 
627 //==============================================================================
628 class AiffAudioFormatWriter : public AudioFormatWriter
629 {
630 public:
631  AiffAudioFormatWriter (OutputStream* out, double rate,
632  unsigned int numChans, unsigned int bits,
633  const StringPairArray& metadataValues)
634  : AudioFormatWriter (out, aiffFormatName, rate, numChans, bits)
635  {
636  using namespace AiffFileHelpers;
637 
638  if (metadataValues.size() > 0)
639  {
640  // The meta data should have been sanitised for the AIFF format.
641  // If it was originally sourced from a WAV file the MetaDataSource
642  // key should be removed (or set to "AIFF") once this has been done
643  jassert (metadataValues.getValue ("MetaDataSource", "None") != "WAV");
644 
645  MarkChunk::create (markChunk, metadataValues);
646  COMTChunk::create (comtChunk, metadataValues);
647  InstChunk::create (instChunk, metadataValues);
648  }
649 
650  headerPosition = out->getPosition();
651  writeHeader();
652  }
653 
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 
699 private:
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"));
723  output->writeIntBigEndian (headerLen + audioBytes - 8);
724  output->writeInt (chunkName ("AIFF"));
725  output->writeInt (chunkName ("COMM"));
726  output->writeIntBigEndian (18);
727  output->writeShortBigEndian ((short) numChannels);
728  output->writeIntBigEndian ((int) lengthInSamples);
729  output->writeShortBigEndian ((short) bitsPerSample);
730 
731  uint8 sampleRateBytes[10] = {};
732 
733  if (sampleRate <= 1)
734  {
735  sampleRateBytes[0] = 0x3f;
736  sampleRateBytes[1] = 0xff;
737  sampleRateBytes[2] = 0x80;
738  }
739  else
740  {
741  int mask = 0x40000000;
742  sampleRateBytes[0] = 0x40;
743 
744  if (sampleRate >= mask)
745  {
746  jassertfalse;
747  sampleRateBytes[1] = 0x1d;
748  }
749  else
750  {
751  int n = (int) sampleRate;
752  int i;
753 
754  for (i = 0; i <= 32 ; ++i)
755  {
756  if ((n & mask) != 0)
757  break;
758 
759  mask >>= 1;
760  }
761 
762  n = n << (i + 1);
763 
764  sampleRateBytes[1] = (uint8) (29 - i);
765  sampleRateBytes[2] = (uint8) ((n >> 24) & 0xff);
766  sampleRateBytes[3] = (uint8) ((n >> 16) & 0xff);
767  sampleRateBytes[4] = (uint8) ((n >> 8) & 0xff);
768  sampleRateBytes[5] = (uint8) (n & 0xff);
769  }
770  }
771 
772  output->write (sampleRateBytes, 10);
773 
774  if (markChunk.getSize() > 0)
775  {
776  output->writeInt (chunkName ("MARK"));
777  output->writeIntBigEndian ((int) markChunk.getSize());
778  *output << markChunk;
779  }
780 
781  if (comtChunk.getSize() > 0)
782  {
783  output->writeInt (chunkName ("COMT"));
784  output->writeIntBigEndian ((int) comtChunk.getSize());
785  *output << comtChunk;
786  }
787 
788  if (instChunk.getSize() > 0)
789  {
790  output->writeInt (chunkName ("INST"));
791  output->writeIntBigEndian ((int) instChunk.getSize());
792  *output << instChunk;
793  }
794 
795  output->writeInt (chunkName ("SSND"));
796  output->writeIntBigEndian (audioBytes + 8);
797  output->writeInt (0);
798  output->writeInt (0);
799 
800  jassert (output->getPosition() == headerLen);
801  }
802 
803  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AiffAudioFormatWriter)
804 };
805 
806 //==============================================================================
807 class MemoryMappedAiffReader : public MemoryMappedAudioFormatReader
808 {
809 public:
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 
817  bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
818  int64 startSampleInFile, int numSamples) override
819  {
820  clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
821  startSampleInFile, numSamples, lengthInSamples);
822 
823  if (map == nullptr || ! mappedSection.contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
824  {
825  jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read.
826  return false;
827  }
828 
829  if (littleEndian)
830  AiffAudioFormatReader::copySampleData<AudioData::LittleEndian>
831  (bitsPerSample, usesFloatingPointData, destSamples, startOffsetInDestBuffer,
832  numDestChannels, sampleToPointer (startSampleInFile), (int) numChannels, numSamples);
833  else
834  AiffAudioFormatReader::copySampleData<AudioData::BigEndian>
835  (bitsPerSample, usesFloatingPointData, destSamples, startOffsetInDestBuffer,
836  numDestChannels, sampleToPointer (startSampleInFile), (int) numChannels, numSamples);
837 
838  return true;
839  }
840 
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, (size_t) num * sizeof (float));
850  return;
851  }
852 
853  float** dest = &result;
854  const void* source = sampleToPointer (sample);
855 
856  if (littleEndian)
857  {
858  switch (bitsPerSample)
859  {
860  case 8: ReadHelper<AudioData::Float32, AudioData::UInt8, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num); break;
861  case 16: ReadHelper<AudioData::Float32, AudioData::Int16, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num); break;
862  case 24: ReadHelper<AudioData::Float32, AudioData::Int24, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num); break;
863  case 32: if (usesFloatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
864  else ReadHelper<AudioData::Float32, AudioData::Int32, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
865  break;
866  default: jassertfalse; break;
867  }
868  }
869  else
870  {
871  switch (bitsPerSample)
872  {
873  case 8: ReadHelper<AudioData::Float32, AudioData::UInt8, AudioData::BigEndian>::read (dest, 0, 1, source, 1, num); break;
874  case 16: ReadHelper<AudioData::Float32, AudioData::Int16, AudioData::BigEndian>::read (dest, 0, 1, source, 1, num); break;
875  case 24: ReadHelper<AudioData::Float32, AudioData::Int24, AudioData::BigEndian>::read (dest, 0, 1, source, 1, num); break;
876  case 32: if (usesFloatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::BigEndian>::read (dest, 0, 1, source, 1, num);
877  else ReadHelper<AudioData::Float32, AudioData::Int32, AudioData::BigEndian>::read (dest, 0, 1, source, 1, num);
878  break;
879  default: jassertfalse; break;
880  }
881  }
882  }
883 
884  void readMaxLevels (int64 startSampleInFile, int64 numSamples, Range<float>* results, int numChannelsToRead) override
885  {
886  numSamples = jmin (numSamples, lengthInSamples - startSampleInFile);
887 
888  if (map == nullptr || numSamples <= 0 || ! mappedSection.contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
889  {
890  jassert (numSamples <= 0); // you must make sure that the window contains all the samples you're going to attempt to read.
891 
892  for (int i = 0; i < numChannelsToRead; ++i)
893  results[i] = Range<float>();
894 
895  return;
896  }
897 
898  switch (bitsPerSample)
899  {
900  case 8: scanMinAndMax<AudioData::UInt8> (startSampleInFile, numSamples, results, numChannelsToRead); break;
901  case 16: scanMinAndMax<AudioData::Int16> (startSampleInFile, numSamples, results, numChannelsToRead); break;
902  case 24: scanMinAndMax<AudioData::Int24> (startSampleInFile, numSamples, results, numChannelsToRead); break;
903  case 32: if (usesFloatingPointData) scanMinAndMax<AudioData::Float32> (startSampleInFile, numSamples, results, numChannelsToRead);
904  else scanMinAndMax<AudioData::Int32> (startSampleInFile, numSamples, results, numChannelsToRead);
905  break;
906  default: jassertfalse; break;
907  }
908  }
909 
911 
912 private:
913  const bool littleEndian;
914 
915  template <typename SampleType>
916  void scanMinAndMax (int64 startSampleInFile, int64 numSamples, Range<float>* results, int numChannelsToRead) const noexcept
917  {
918  for (int i = 0; i < numChannelsToRead; ++i)
919  results[i] = scanMinAndMaxForChannel<SampleType> (i, startSampleInFile, numSamples);
920  }
921 
922  template <typename SampleType>
923  Range<float> scanMinAndMaxForChannel (int channel, int64 startSampleInFile, int64 numSamples) const noexcept
924  {
925  return littleEndian ? scanMinAndMaxInterleaved<SampleType, AudioData::LittleEndian> (channel, startSampleInFile, numSamples)
926  : scanMinAndMaxInterleaved<SampleType, AudioData::BigEndian> (channel, startSampleInFile, numSamples);
927  }
928 
929  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedAiffReader)
930 };
931 
932 //==============================================================================
933 AiffAudioFormat::AiffAudioFormat() : AudioFormat (aiffFormatName, ".aiff .aif") {}
935 
937 {
938  return { 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000 };
939 }
940 
942 {
943  return { 8, 16, 24 };
944 }
945 
946 bool AiffAudioFormat::canDoStereo() { return true; }
947 bool AiffAudioFormat::canDoMono() { return true; }
948 
949 #if JUCE_MAC
950 bool AiffAudioFormat::canHandleFile (const File& f)
951 {
953  return true;
954 
955  auto type = f.getMacOSType();
956 
957  // (NB: written as hex to avoid four-char-constant warnings)
958  return type == 0x41494646 /* AIFF */ || type == 0x41494643 /* AIFC */
959  || type == 0x61696666 /* aiff */ || type == 0x61696663 /* aifc */;
960 }
961 #endif
962 
963 AudioFormatReader* AiffAudioFormat::createReaderFor (InputStream* sourceStream, bool deleteStreamIfOpeningFails)
964 {
965  std::unique_ptr<AiffAudioFormatReader> w (new AiffAudioFormatReader (sourceStream));
966 
967  if (w->sampleRate > 0 && w->numChannels > 0)
968  return w.release();
969 
970  if (! deleteStreamIfOpeningFails)
971  w->input = nullptr;
972 
973  return nullptr;
974 }
975 
977 {
979 }
980 
982 {
983  if (fin != nullptr)
984  {
985  AiffAudioFormatReader reader (fin);
986 
987  if (reader.lengthInSamples > 0)
988  return new MemoryMappedAiffReader (fin->getFile(), reader);
989  }
990 
991  return nullptr;
992 }
993 
995  double sampleRate,
996  unsigned int numberOfChannels,
997  int bitsPerSample,
998  const StringPairArray& metadataValues,
999  int /*qualityOptionIndex*/)
1000 {
1001  if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample))
1002  return new AiffAudioFormatWriter (out, sampleRate, numberOfChannels,
1003  (unsigned int) bitsPerSample, metadataValues);
1004 
1005  return nullptr;
1006 }
1007 
1008 } // namespace juce
AudioFormatWriter * createWriterFor(OutputStream *streamToWriteTo, double sampleRateToUse, unsigned int numberOfChannels, int bitsPerSample, const StringPairArray &metadataValues, int qualityOptionIndex) override
static const char *const appleRootSet
static JUCE_CONSTEXPR uint32 bigEndianInt(const void *bytes) noexcept
FileInputStream * createInputStream() const
Definition: juce_File.cpp:729
const File & getFile() const noexcept
virtual void readMaxLevels(int64 startSample, int64 numSamples, Range< float > *results, int numChannelsToRead)
static const char *const appleOneShot
static bool isUpperCase(juce_wchar character) noexcept
Array< int > getPossibleBitDepths() override
MemoryMappedAudioFormatReader * createMemoryMappedReader(const File &) override
static Type swapIfLittleEndian(Type value) noexcept
static JUCE_CONSTEXPR uint32 littleEndianInt(const void *bytes) noexcept
static const char *const appleKey
static JUCE_CONSTEXPR uint16 bigEndianShort(const void *bytes) noexcept
virtual bool canHandleFile(const File &fileToTest)
AudioFormatReader * createReaderFor(InputStream *sourceStream, bool deleteStreamIfOpeningFails) override
static const char *const appleBeats
Array< int > getPossibleSampleRates() override
static const char *const appleRootNote
static const char *const appleTag
static bool isLowerCase(juce_wchar character) noexcept
static bool isLetterOrDigit(char character) noexcept
static const char *const appleDenominator
static const char *const appleNumerator