OpenShot Library | libopenshot-audio  0.1.9
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 //==============================================================================
384 {
385 public:
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  {
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 bitsPerSample, bool usesFloatingPointData,
604  int* const* destSamples, int startOffsetInDestBuffer, int numDestChannels,
605  const void* sourceData, int numChannels, int numSamples) noexcept
606  {
607  switch (bitsPerSample)
608  {
609  case 8: ReadHelper<AudioData::Int32, AudioData::Int8, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break;
610  case 16: ReadHelper<AudioData::Int32, AudioData::Int16, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break;
611  case 24: ReadHelper<AudioData::Int32, AudioData::Int24, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break;
612  case 32: if (usesFloatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples);
613  else ReadHelper<AudioData::Int32, AudioData::Int32, Endianness>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples);
614  break;
615  default: jassertfalse; break;
616  }
617  }
618 
619  int bytesPerFrame;
620  int64 dataChunkStart;
621  bool littleEndian;
622 
623 private:
624  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AiffAudioFormatReader)
625 };
626 
627 //==============================================================================
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 //==============================================================================
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, sizeof (float) * (size_t) num);
850  return;
851  }
852 
853  float** dest = &result;
854  const void* source = sampleToPointer (sample);
855 
856  if (littleEndian)
857  {
858  switch (bitsPerSample)
859  {
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);
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);
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 
910 private:
911  const bool littleEndian;
912 
913  template <typename SampleType>
914  void scanMinAndMax (int64 startSampleInFile, int64 numSamples, Range<float>* results, int numChannelsToRead) const noexcept
915  {
916  for (int i = 0; i < numChannelsToRead; ++i)
917  results[i] = scanMinAndMaxForChannel<SampleType> (i, startSampleInFile, numSamples);
918  }
919 
920  template <typename SampleType>
921  Range<float> scanMinAndMaxForChannel (int channel, int64 startSampleInFile, int64 numSamples) const noexcept
922  {
923  return littleEndian ? scanMinAndMaxInterleaved<SampleType, AudioData::LittleEndian> (channel, startSampleInFile, numSamples)
924  : scanMinAndMaxInterleaved<SampleType, AudioData::BigEndian> (channel, startSampleInFile, numSamples);
925  }
926 
927  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedAiffReader)
928 };
929 
930 //==============================================================================
931 AiffAudioFormat::AiffAudioFormat() : AudioFormat (aiffFormatName, ".aiff .aif") {}
933 
935 {
936  return { 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000 };
937 }
938 
940 {
941  return { 8, 16, 24 };
942 }
943 
944 bool AiffAudioFormat::canDoStereo() { return true; }
945 bool AiffAudioFormat::canDoMono() { return true; }
946 
947 #if JUCE_MAC
949 {
951  return true;
952 
953  auto type = f.getMacOSType();
954 
955  // (NB: written as hex to avoid four-char-constant warnings)
956  return type == 0x41494646 /* AIFF */ || type == 0x41494643 /* AIFC */
957  || type == 0x61696666 /* aiff */ || type == 0x61696663 /* aifc */;
958 }
959 #endif
960 
961 AudioFormatReader* AiffAudioFormat::createReaderFor (InputStream* sourceStream, bool deleteStreamIfOpeningFails)
962 {
963  std::unique_ptr<AiffAudioFormatReader> w (new AiffAudioFormatReader (sourceStream));
964 
965  if (w->sampleRate > 0 && w->numChannels > 0)
966  return w.release();
967 
968  if (! deleteStreamIfOpeningFails)
969  w->input = nullptr;
970 
971  return nullptr;
972 }
973 
975 {
977 }
978 
980 {
981  if (fin != nullptr)
982  {
983  AiffAudioFormatReader reader (fin);
984 
985  if (reader.lengthInSamples > 0)
986  return new MemoryMappedAiffReader (fin->getFile(), reader);
987  }
988 
989  return nullptr;
990 }
991 
993  double sampleRate,
994  unsigned int numberOfChannels,
995  int bitsPerSample,
996  const StringPairArray& metadataValues,
997  int /*qualityOptionIndex*/)
998 {
999  if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample))
1000  return new AiffAudioFormatWriter (out, sampleRate, numberOfChannels,
1001  (unsigned int) bitsPerSample, metadataValues);
1002 
1003  return nullptr;
1004 }
1005 
1006 } // namespace juce
size_t getSize() const noexcept
Returns the block&#39;s current allocated size, in bytes.
virtual bool writeShortBigEndian(short value)
Writes a 16-bit integer to the stream in a big-endian byte order.
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.
bool readSamples(int **destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) override
Subclasses must implement this method to perform the low-level read operation.
bool canDoMono() override
Returns true if the format can do 1-channel audio.
virtual void skipNextBytes(int64 numBytesToSkip)
Reads and discards a number of bytes from the stream.
static const char *const appleRootSet
Metadata property name used when reading a aiff file with a basc chunk.
virtual int readIntBigEndian()
Reads four bytes from the stream as a big-endian 32-bit value.
virtual bool writeByte(char byte)
Writes a single byte to the stream.
void getSample(int64 sample, float *result) const noexcept override
Returns the samples for all channels at a given sample position.
The base class for streams that read data.
const StringArray & getAllKeys() const noexcept
Returns a list of all keys in the array.
void add(const ElementType &newElement)
Appends a new element at the end of the array.
Definition: juce_Array.h:375
~AiffAudioFormat() override
Destructor.
Very simple container class to hold a pointer to some data on the heap.
static JUCE_CONSTEXPR uint32 bigEndianInt(const void *bytes) noexcept
Turns 4 bytes into a big-endian integer.
String getValue(StringRef, const String &defaultReturnValue) const
Finds the value corresponding to a key string.
int64 lengthInSamples
The total number of samples in the audio stream.
bool readSamples(int **destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) override
Subclasses must implement this method to perform the low-level read operation.
A special array for holding a list of strings.
The JUCE String class!
Definition: juce_String.h:42
FileInputStream * createInputStream() const
Creates a stream to read from this file.
Definition: juce_File.cpp:709
const File & getFile() const noexcept
Returns the file that this stream is reading from.
virtual short readShortBigEndian()
Reads two bytes from the stream as a little-endian 16-bit value.
int size() const noexcept
Returns the number of strings in the array.
void calloc(SizeType newNumElements, const size_t elementSize=sizeof(ElementType))
Allocates a specified amount of memory and clears it.
Used by AudioFormatReader subclasses to copy data to different formats.
String toString() const
Attempts to parse the contents of the block as a zero-terminated UTF8 string.
static const char *const appleOneShot
Metadata property name used when reading a aiff file with a basc chunk.
void * getData() const noexcept
Returns a void pointer to the data.
static bool isUpperCase(juce_wchar character) noexcept
Checks whether a unicode character is upper-case.
Array< int > getPossibleBitDepths() override
Returns a set of bit depths that the format can read and write.
virtual bool writeIntBigEndian(int value)
Writes a 32-bit integer to the stream in a big-endian byte order.
bool write(const void *, size_t) override
Writes a block of data to the stream.
MemoryMappedAudioFormatReader * createMemoryMappedReader(const File &) override
Attempts to create a MemoryMappedAudioFormatReader, if possible for this format.
Subclasses of AudioFormat are used to read and write different audio file formats.
static Type swapIfLittleEndian(Type value) noexcept
Swaps the byte order of a signed or unsigned integer if the CPU is little-endian. ...
static JUCE_CONSTEXPR uint32 littleEndianInt(const void *bytes) noexcept
Turns 4 bytes into a little-endian integer.
Represents a local file or directory.
Definition: juce_File.h:44
The base class for streams that write data to some kind of destination.
bool canHandleFile(const File &fileToTest) override
Returns true if this the given file can be read by this format.
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.
static const char *const appleKey
Metadata property name used when reading a aiff file with a basc chunk.
static JUCE_CONSTEXPR uint16 bigEndianShort(const void *bytes) noexcept
Turns 2 bytes into a big-endian integer.
OSType getMacOSType() const
OSX ONLY - Finds the OSType of a file from the its resources.
size_t getDataSize() const noexcept
Returns the number of bytes of data that have been written to the stream.
void set(const String &key, const String &value)
Adds or amends a key/value pair.
virtual size_t readIntoMemoryBlock(MemoryBlock &destBlock, ssize_t maxNumBytesToRead=-1)
Reads from the stream and appends the data to a MemoryBlock.
bool contains(StringRef stringToLookFor, bool ignoreCase=false) const
Searches for a string in the array.
An input stream that reads from a local file.
String joinIntoString(StringRef separatorString, int startIndex=0, int numberOfElements=-1) const
Joins the strings in the array together into one string.
virtual bool canHandleFile(const File &fileToTest)
Returns true if this the given file can be read by this format.
Writes samples to an audio file stream.
Used by AudioFormatWriter subclasses to copy data to different formats.
AudioFormatReader * createReaderFor(InputStream *sourceStream, bool deleteStreamIfOpeningFails) override
Tries to create an object that can read from a stream containing audio data in this format...
static const char *const appleBeats
Metadata property name used when reading a aiff file with a basc chunk.
int getIntValue() const noexcept
Reads the value of the string as a decimal number (up to 32 bits in size).
bool read(float *const *destChannels, int numDestChannels, int64 startSampleInSource, int numSamplesToRead)
Reads samples from the stream.
Array< int > getPossibleSampleRates() override
Returns a set of sample rates that the format can read and write.
static const char *const appleRootNote
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.
bool canDoStereo() override
Returns true if the format can do 2-channel audio.
A container for holding a set of strings which are keyed by another string.
Reads samples from an audio file stream.
Writes data to an internal memory buffer, which grows as required.
virtual int read(void *destBuffer, int maxBytesToRead)=0
Reads some data from the stream into a memory buffer.
bool write(const int **data, int numSamples) override
Writes a set of samples to the audio stream.
static bool isLowerCase(juce_wchar character) noexcept
Checks whether a unicode character is lower-case.
AiffAudioFormat()
Creates an format object.
A class to hold a resizable block of raw data.
virtual int64 getPosition()=0
Returns the stream&#39;s current position.
static bool isLetterOrDigit(char character) noexcept
Checks whether a character is alphabetic or numeric.
A specialised type of AudioFormatReader that uses a MemoryMappedFile to read directly from an audio f...
bool contains(ParameterType elementToLookFor) const
Returns true if the array contains at least one occurrence of an object.
Definition: juce_Array.h:357
static const char *const appleDenominator
Metadata property name used when reading a aiff file with a basc chunk.
void add(String stringToAdd)
Appends a string at the end of the array.
void setSize(const size_t newSize, bool initialiseNewSpaceToZero=false)
Resizes the memory block.
static const char *const appleNumerator
Metadata property name used when reading a aiff file with a basc chunk.
Wraps a pointer to a null-terminated UTF-8 character string, and provides various methods to operate ...