OpenShot Library | libopenshot-audio  0.1.9
juce_WavAudioFormat.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 wavFormatName = "WAV file";
31 
32 //==============================================================================
33 const char* const WavAudioFormat::bwavDescription = "bwav description";
34 const char* const WavAudioFormat::bwavOriginator = "bwav originator";
35 const char* const WavAudioFormat::bwavOriginatorRef = "bwav originator ref";
36 const char* const WavAudioFormat::bwavOriginationDate = "bwav origination date";
37 const char* const WavAudioFormat::bwavOriginationTime = "bwav origination time";
38 const char* const WavAudioFormat::bwavTimeReference = "bwav time reference";
39 const char* const WavAudioFormat::bwavCodingHistory = "bwav coding history";
40 
42  const String& originator,
43  const String& originatorRef,
44  Time date,
45  int64 timeReferenceSamples,
46  const String& codingHistory)
47 {
49 
50  m.set (bwavDescription, description);
51  m.set (bwavOriginator, originator);
52  m.set (bwavOriginatorRef, originatorRef);
53  m.set (bwavOriginationDate, date.formatted ("%Y-%m-%d"));
54  m.set (bwavOriginationTime, date.formatted ("%H:%M:%S"));
55  m.set (bwavTimeReference, String (timeReferenceSamples));
56  m.set (bwavCodingHistory, codingHistory);
57 
58  return m;
59 }
60 
61 const char* const WavAudioFormat::acidOneShot = "acid one shot";
62 const char* const WavAudioFormat::acidRootSet = "acid root set";
63 const char* const WavAudioFormat::acidStretch = "acid stretch";
64 const char* const WavAudioFormat::acidDiskBased = "acid disk based";
65 const char* const WavAudioFormat::acidizerFlag = "acidizer flag";
66 const char* const WavAudioFormat::acidRootNote = "acid root note";
67 const char* const WavAudioFormat::acidBeats = "acid beats";
68 const char* const WavAudioFormat::acidDenominator = "acid denominator";
69 const char* const WavAudioFormat::acidNumerator = "acid numerator";
70 const char* const WavAudioFormat::acidTempo = "acid tempo";
71 
72 const char* const WavAudioFormat::riffInfoArchivalLocation = "IARL";
73 const char* const WavAudioFormat::riffInfoArtist = "IART";
74 const char* const WavAudioFormat::riffInfoBaseURL = "IBSU";
75 const char* const WavAudioFormat::riffInfoCinematographer = "ICNM";
76 const char* const WavAudioFormat::riffInfoComment = "CMNT";
77 const char* const WavAudioFormat::riffInfoComment2 = "ICMT";
78 const char* const WavAudioFormat::riffInfoComments = "COMM";
79 const char* const WavAudioFormat::riffInfoCommissioned = "ICMS";
80 const char* const WavAudioFormat::riffInfoCopyright = "ICOP";
81 const char* const WavAudioFormat::riffInfoCostumeDesigner = "ICDS";
82 const char* const WavAudioFormat::riffInfoCountry = "ICNT";
83 const char* const WavAudioFormat::riffInfoCropped = "ICRP";
84 const char* const WavAudioFormat::riffInfoDateCreated = "ICRD";
85 const char* const WavAudioFormat::riffInfoDateTimeOriginal = "IDIT";
86 const char* const WavAudioFormat::riffInfoDefaultAudioStream = "ICAS";
87 const char* const WavAudioFormat::riffInfoDimension = "IDIM";
88 const char* const WavAudioFormat::riffInfoDirectory = "DIRC";
89 const char* const WavAudioFormat::riffInfoDistributedBy = "IDST";
90 const char* const WavAudioFormat::riffInfoDotsPerInch = "IDPI";
91 const char* const WavAudioFormat::riffInfoEditedBy = "IEDT";
92 const char* const WavAudioFormat::riffInfoEighthLanguage = "IAS8";
93 const char* const WavAudioFormat::riffInfoEncodedBy = "CODE";
94 const char* const WavAudioFormat::riffInfoEndTimecode = "TCDO";
95 const char* const WavAudioFormat::riffInfoEngineer = "IENG";
96 const char* const WavAudioFormat::riffInfoFifthLanguage = "IAS5";
97 const char* const WavAudioFormat::riffInfoFirstLanguage = "IAS1";
98 const char* const WavAudioFormat::riffInfoFourthLanguage = "IAS4";
99 const char* const WavAudioFormat::riffInfoGenre = "GENR";
100 const char* const WavAudioFormat::riffInfoKeywords = "IKEY";
101 const char* const WavAudioFormat::riffInfoLanguage = "LANG";
102 const char* const WavAudioFormat::riffInfoLength = "TLEN";
103 const char* const WavAudioFormat::riffInfoLightness = "ILGT";
104 const char* const WavAudioFormat::riffInfoLocation = "LOCA";
105 const char* const WavAudioFormat::riffInfoLogoIconURL = "ILIU";
106 const char* const WavAudioFormat::riffInfoLogoURL = "ILGU";
107 const char* const WavAudioFormat::riffInfoMedium = "IMED";
108 const char* const WavAudioFormat::riffInfoMoreInfoBannerImage = "IMBI";
109 const char* const WavAudioFormat::riffInfoMoreInfoBannerURL = "IMBU";
110 const char* const WavAudioFormat::riffInfoMoreInfoText = "IMIT";
111 const char* const WavAudioFormat::riffInfoMoreInfoURL = "IMIU";
112 const char* const WavAudioFormat::riffInfoMusicBy = "IMUS";
113 const char* const WavAudioFormat::riffInfoNinthLanguage = "IAS9";
114 const char* const WavAudioFormat::riffInfoNumberOfParts = "PRT2";
115 const char* const WavAudioFormat::riffInfoOrganisation = "TORG";
116 const char* const WavAudioFormat::riffInfoPart = "PRT1";
117 const char* const WavAudioFormat::riffInfoProducedBy = "IPRO";
118 const char* const WavAudioFormat::riffInfoProductName = "IPRD";
119 const char* const WavAudioFormat::riffInfoProductionDesigner = "IPDS";
120 const char* const WavAudioFormat::riffInfoProductionStudio = "ISDT";
121 const char* const WavAudioFormat::riffInfoRate = "RATE";
122 const char* const WavAudioFormat::riffInfoRated = "AGES";
123 const char* const WavAudioFormat::riffInfoRating = "IRTD";
124 const char* const WavAudioFormat::riffInfoRippedBy = "IRIP";
125 const char* const WavAudioFormat::riffInfoSecondaryGenre = "ISGN";
126 const char* const WavAudioFormat::riffInfoSecondLanguage = "IAS2";
127 const char* const WavAudioFormat::riffInfoSeventhLanguage = "IAS7";
128 const char* const WavAudioFormat::riffInfoSharpness = "ISHP";
129 const char* const WavAudioFormat::riffInfoSixthLanguage = "IAS6";
130 const char* const WavAudioFormat::riffInfoSoftware = "ISFT";
131 const char* const WavAudioFormat::riffInfoSoundSchemeTitle = "DISP";
132 const char* const WavAudioFormat::riffInfoSource = "ISRC";
133 const char* const WavAudioFormat::riffInfoSourceFrom = "ISRF";
134 const char* const WavAudioFormat::riffInfoStarring_ISTR = "ISTR";
135 const char* const WavAudioFormat::riffInfoStarring_STAR = "STAR";
136 const char* const WavAudioFormat::riffInfoStartTimecode = "TCOD";
137 const char* const WavAudioFormat::riffInfoStatistics = "STAT";
138 const char* const WavAudioFormat::riffInfoSubject = "ISBJ";
139 const char* const WavAudioFormat::riffInfoTapeName = "TAPE";
140 const char* const WavAudioFormat::riffInfoTechnician = "ITCH";
141 const char* const WavAudioFormat::riffInfoThirdLanguage = "IAS3";
142 const char* const WavAudioFormat::riffInfoTimeCode = "ISMP";
143 const char* const WavAudioFormat::riffInfoTitle = "INAM";
144 const char* const WavAudioFormat::riffInfoTrackNo = "IPRT";
145 const char* const WavAudioFormat::riffInfoTrackNumber = "TRCK";
146 const char* const WavAudioFormat::riffInfoURL = "TURL";
147 const char* const WavAudioFormat::riffInfoVegasVersionMajor = "VMAJ";
148 const char* const WavAudioFormat::riffInfoVegasVersionMinor = "VMIN";
149 const char* const WavAudioFormat::riffInfoVersion = "TVER";
150 const char* const WavAudioFormat::riffInfoWatermarkURL = "IWMU";
151 const char* const WavAudioFormat::riffInfoWrittenBy = "IWRI";
152 const char* const WavAudioFormat::riffInfoYear = "YEAR";
153 
154 const char* const WavAudioFormat::ISRC = "ISRC";
155 const char* const WavAudioFormat::tracktionLoopInfo = "tracktion loop info";
156 
157 //==============================================================================
158 namespace WavFileHelpers
159 {
160  JUCE_CONSTEXPR inline int chunkName (const char* name) noexcept { return (int) ByteOrder::littleEndianInt (name); }
161  JUCE_CONSTEXPR inline size_t roundUpSize (size_t sz) noexcept { return (sz + 3) & ~3u; }
162 
163  #if JUCE_MSVC
164  #pragma pack (push, 1)
165  #endif
166 
167  struct BWAVChunk
168  {
169  char description[256];
170  char originator[32];
171  char originatorRef[32];
172  char originationDate[10];
173  char originationTime[8];
174  uint32 timeRefLow;
175  uint32 timeRefHigh;
176  uint16 version;
177  uint8 umid[64];
178  uint8 reserved[190];
179  char codingHistory[1];
180 
181  void copyTo (StringPairArray& values, const int totalSize) const
182  {
183  values.set (WavAudioFormat::bwavDescription, String::fromUTF8 (description, sizeof (description)));
184  values.set (WavAudioFormat::bwavOriginator, String::fromUTF8 (originator, sizeof (originator)));
185  values.set (WavAudioFormat::bwavOriginatorRef, String::fromUTF8 (originatorRef, sizeof (originatorRef)));
186  values.set (WavAudioFormat::bwavOriginationDate, String::fromUTF8 (originationDate, sizeof (originationDate)));
187  values.set (WavAudioFormat::bwavOriginationTime, String::fromUTF8 (originationTime, sizeof (originationTime)));
188 
189  auto timeLow = ByteOrder::swapIfBigEndian (timeRefLow);
190  auto timeHigh = ByteOrder::swapIfBigEndian (timeRefHigh);
191  auto time = (((int64) timeHigh) << 32) + timeLow;
192 
193  values.set (WavAudioFormat::bwavTimeReference, String (time));
194  values.set (WavAudioFormat::bwavCodingHistory,
195  String::fromUTF8 (codingHistory, totalSize - (int) offsetof (BWAVChunk, codingHistory)));
196  }
197 
198  static MemoryBlock createFrom (const StringPairArray& values)
199  {
200  MemoryBlock data (roundUpSize (sizeof (BWAVChunk) + values[WavAudioFormat::bwavCodingHistory].getNumBytesAsUTF8()));
201  data.fillWith (0);
202 
203  auto* b = (BWAVChunk*) data.getData();
204 
205  // Allow these calls to overwrite an extra byte at the end, which is fine as long
206  // as they get called in the right order..
207  values[WavAudioFormat::bwavDescription] .copyToUTF8 (b->description, 257);
208  values[WavAudioFormat::bwavOriginator] .copyToUTF8 (b->originator, 33);
209  values[WavAudioFormat::bwavOriginatorRef] .copyToUTF8 (b->originatorRef, 33);
210  values[WavAudioFormat::bwavOriginationDate].copyToUTF8 (b->originationDate, 11);
211  values[WavAudioFormat::bwavOriginationTime].copyToUTF8 (b->originationTime, 9);
212 
213  auto time = values[WavAudioFormat::bwavTimeReference].getLargeIntValue();
214  b->timeRefLow = ByteOrder::swapIfBigEndian ((uint32) (time & 0xffffffff));
215  b->timeRefHigh = ByteOrder::swapIfBigEndian ((uint32) (time >> 32));
216 
217  values[WavAudioFormat::bwavCodingHistory].copyToUTF8 (b->codingHistory, 0x7fffffff);
218 
219  if (b->description[0] != 0
220  || b->originator[0] != 0
221  || b->originationDate[0] != 0
222  || b->originationTime[0] != 0
223  || b->codingHistory[0] != 0
224  || time != 0)
225  {
226  return data;
227  }
228 
229  return {};
230  }
231 
232  } JUCE_PACKED;
233 
234  //==============================================================================
235  inline AudioChannelSet canonicalWavChannelSet (int numChannels)
236  {
237  if (numChannels == 1) return AudioChannelSet::mono();
238  if (numChannels == 2) return AudioChannelSet::stereo();
239  if (numChannels == 3) return AudioChannelSet::createLCR();
240  if (numChannels == 4) return AudioChannelSet::quadraphonic();
241  if (numChannels == 5) return AudioChannelSet::create5point0();
242  if (numChannels == 6) return AudioChannelSet::create5point1();
243  if (numChannels == 7) return AudioChannelSet::create7point0SDDS();
244  if (numChannels == 8) return AudioChannelSet::create7point1SDDS();
245 
246  return AudioChannelSet::discreteChannels (numChannels);
247  }
248 
249  //==============================================================================
250  struct SMPLChunk
251  {
252  struct SampleLoop
253  {
254  uint32 identifier;
255  uint32 type; // these are different in AIFF and WAV
256  uint32 start;
257  uint32 end;
258  uint32 fraction;
259  uint32 playCount;
260  } JUCE_PACKED;
261 
262  uint32 manufacturer;
263  uint32 product;
264  uint32 samplePeriod;
265  uint32 midiUnityNote;
266  uint32 midiPitchFraction;
267  uint32 smpteFormat;
268  uint32 smpteOffset;
269  uint32 numSampleLoops;
270  uint32 samplerData;
271  SampleLoop loops[1];
272 
273  template <typename NameType>
274  static void setValue (StringPairArray& values, NameType name, uint32 val)
275  {
276  values.set (name, String (ByteOrder::swapIfBigEndian (val)));
277  }
278 
279  static void setValue (StringPairArray& values, int prefix, const char* name, uint32 val)
280  {
281  setValue (values, "Loop" + String (prefix) + name, val);
282  }
283 
284  void copyTo (StringPairArray& values, const int totalSize) const
285  {
286  setValue (values, "Manufacturer", manufacturer);
287  setValue (values, "Product", product);
288  setValue (values, "SamplePeriod", samplePeriod);
289  setValue (values, "MidiUnityNote", midiUnityNote);
290  setValue (values, "MidiPitchFraction", midiPitchFraction);
291  setValue (values, "SmpteFormat", smpteFormat);
292  setValue (values, "SmpteOffset", smpteOffset);
293  setValue (values, "NumSampleLoops", numSampleLoops);
294  setValue (values, "SamplerData", samplerData);
295 
296  for (int i = 0; i < (int) numSampleLoops; ++i)
297  {
298  if ((uint8*) (loops + (i + 1)) > ((uint8*) this) + totalSize)
299  break;
300 
301  setValue (values, i, "Identifier", loops[i].identifier);
302  setValue (values, i, "Type", loops[i].type);
303  setValue (values, i, "Start", loops[i].start);
304  setValue (values, i, "End", loops[i].end);
305  setValue (values, i, "Fraction", loops[i].fraction);
306  setValue (values, i, "PlayCount", loops[i].playCount);
307  }
308  }
309 
310  template <typename NameType>
311  static uint32 getValue (const StringPairArray& values, NameType name, const char* def)
312  {
313  return ByteOrder::swapIfBigEndian ((uint32) values.getValue (name, def).getIntValue());
314  }
315 
316  static uint32 getValue (const StringPairArray& values, int prefix, const char* name, const char* def)
317  {
318  return getValue (values, "Loop" + String (prefix) + name, def);
319  }
320 
321  static MemoryBlock createFrom (const StringPairArray& values)
322  {
323  MemoryBlock data;
324  auto numLoops = jmin (64, values.getValue ("NumSampleLoops", "0").getIntValue());
325 
326  data.setSize (roundUpSize (sizeof (SMPLChunk) + (size_t) (jmax (0, numLoops - 1)) * sizeof (SampleLoop)), true);
327 
328  auto s = static_cast<SMPLChunk*> (data.getData());
329 
330  s->manufacturer = getValue (values, "Manufacturer", "0");
331  s->product = getValue (values, "Product", "0");
332  s->samplePeriod = getValue (values, "SamplePeriod", "0");
333  s->midiUnityNote = getValue (values, "MidiUnityNote", "60");
334  s->midiPitchFraction = getValue (values, "MidiPitchFraction", "0");
335  s->smpteFormat = getValue (values, "SmpteFormat", "0");
336  s->smpteOffset = getValue (values, "SmpteOffset", "0");
337  s->numSampleLoops = ByteOrder::swapIfBigEndian ((uint32) numLoops);
338  s->samplerData = getValue (values, "SamplerData", "0");
339 
340  for (int i = 0; i < numLoops; ++i)
341  {
342  auto& loop = s->loops[i];
343  loop.identifier = getValue (values, i, "Identifier", "0");
344  loop.type = getValue (values, i, "Type", "0");
345  loop.start = getValue (values, i, "Start", "0");
346  loop.end = getValue (values, i, "End", "0");
347  loop.fraction = getValue (values, i, "Fraction", "0");
348  loop.playCount = getValue (values, i, "PlayCount", "0");
349  }
350 
351  return data;
352  }
353  } JUCE_PACKED;
354 
355  //==============================================================================
356  struct InstChunk
357  {
358  int8 baseNote;
359  int8 detune;
360  int8 gain;
361  int8 lowNote;
362  int8 highNote;
363  int8 lowVelocity;
364  int8 highVelocity;
365 
366  static void setValue (StringPairArray& values, const char* name, int val)
367  {
368  values.set (name, String (val));
369  }
370 
371  void copyTo (StringPairArray& values) const
372  {
373  setValue (values, "MidiUnityNote", baseNote);
374  setValue (values, "Detune", detune);
375  setValue (values, "Gain", gain);
376  setValue (values, "LowNote", lowNote);
377  setValue (values, "HighNote", highNote);
378  setValue (values, "LowVelocity", lowVelocity);
379  setValue (values, "HighVelocity", highVelocity);
380  }
381 
382  static int8 getValue (const StringPairArray& values, const char* name, const char* def)
383  {
384  return (int8) values.getValue (name, def).getIntValue();
385  }
386 
387  static MemoryBlock createFrom (const StringPairArray& values)
388  {
389  MemoryBlock data;
390  auto& keys = values.getAllKeys();
391 
392  if (keys.contains ("LowNote", true) && keys.contains ("HighNote", true))
393  {
394  data.setSize (8, true);
395  auto* inst = static_cast<InstChunk*> (data.getData());
396 
397  inst->baseNote = getValue (values, "MidiUnityNote", "60");
398  inst->detune = getValue (values, "Detune", "0");
399  inst->gain = getValue (values, "Gain", "0");
400  inst->lowNote = getValue (values, "LowNote", "0");
401  inst->highNote = getValue (values, "HighNote", "127");
402  inst->lowVelocity = getValue (values, "LowVelocity", "1");
403  inst->highVelocity = getValue (values, "HighVelocity", "127");
404  }
405 
406  return data;
407  }
408  } JUCE_PACKED;
409 
410  //==============================================================================
411  struct CueChunk
412  {
413  struct Cue
414  {
415  uint32 identifier;
416  uint32 order;
417  uint32 chunkID;
418  uint32 chunkStart;
419  uint32 blockStart;
420  uint32 offset;
421  } JUCE_PACKED;
422 
423  uint32 numCues;
424  Cue cues[1];
425 
426  static void setValue (StringPairArray& values, int prefix, const char* name, uint32 val)
427  {
428  values.set ("Cue" + String (prefix) + name, String (ByteOrder::swapIfBigEndian (val)));
429  }
430 
431  void copyTo (StringPairArray& values, const int totalSize) const
432  {
433  values.set ("NumCuePoints", String (ByteOrder::swapIfBigEndian (numCues)));
434 
435  for (int i = 0; i < (int) numCues; ++i)
436  {
437  if ((uint8*) (cues + (i + 1)) > ((uint8*) this) + totalSize)
438  break;
439 
440  setValue (values, i, "Identifier", cues[i].identifier);
441  setValue (values, i, "Order", cues[i].order);
442  setValue (values, i, "ChunkID", cues[i].chunkID);
443  setValue (values, i, "ChunkStart", cues[i].chunkStart);
444  setValue (values, i, "BlockStart", cues[i].blockStart);
445  setValue (values, i, "Offset", cues[i].offset);
446  }
447  }
448 
449  static MemoryBlock createFrom (const StringPairArray& values)
450  {
451  MemoryBlock data;
452  const int numCues = values.getValue ("NumCuePoints", "0").getIntValue();
453 
454  if (numCues > 0)
455  {
456  data.setSize (roundUpSize (sizeof (CueChunk) + (size_t) (numCues - 1) * sizeof (Cue)), true);
457 
458  auto c = static_cast<CueChunk*> (data.getData());
459 
460  c->numCues = ByteOrder::swapIfBigEndian ((uint32) numCues);
461 
462  const String dataChunkID (chunkName ("data"));
463  int nextOrder = 0;
464 
465  #if JUCE_DEBUG
466  Array<uint32> identifiers;
467  #endif
468 
469  for (int i = 0; i < numCues; ++i)
470  {
471  auto prefix = "Cue" + String (i);
472  auto identifier = (uint32) values.getValue (prefix + "Identifier", "0").getIntValue();
473 
474  #if JUCE_DEBUG
475  jassert (! identifiers.contains (identifier));
476  identifiers.add (identifier);
477  #endif
478 
479  auto order = values.getValue (prefix + "Order", String (nextOrder)).getIntValue();
480  nextOrder = jmax (nextOrder, order) + 1;
481 
482  auto& cue = c->cues[i];
483  cue.identifier = ByteOrder::swapIfBigEndian ((uint32) identifier);
484  cue.order = ByteOrder::swapIfBigEndian ((uint32) order);
485  cue.chunkID = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "ChunkID", dataChunkID).getIntValue());
486  cue.chunkStart = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "ChunkStart", "0").getIntValue());
487  cue.blockStart = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "BlockStart", "0").getIntValue());
488  cue.offset = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "Offset", "0").getIntValue());
489  }
490  }
491 
492  return data;
493  }
494 
495  } JUCE_PACKED;
496 
497  //==============================================================================
498  namespace ListChunk
499  {
500  static int getValue (const StringPairArray& values, const String& name)
501  {
502  return values.getValue (name, "0").getIntValue();
503  }
504 
505  static int getValue (const StringPairArray& values, const String& prefix, const char* name)
506  {
507  return getValue (values, prefix + name);
508  }
509 
510  static void appendLabelOrNoteChunk (const StringPairArray& values, const String& prefix,
511  const int chunkType, MemoryOutputStream& out)
512  {
513  auto label = values.getValue (prefix + "Text", prefix);
514  auto labelLength = (int) label.getNumBytesAsUTF8() + 1;
515  auto chunkLength = 4 + labelLength + (labelLength & 1);
516 
517  out.writeInt (chunkType);
518  out.writeInt (chunkLength);
519  out.writeInt (getValue (values, prefix, "Identifier"));
520  out.write (label.toUTF8(), (size_t) labelLength);
521 
522  if ((out.getDataSize() & 1) != 0)
523  out.writeByte (0);
524  }
525 
526  static void appendExtraChunk (const StringPairArray& values, const String& prefix, MemoryOutputStream& out)
527  {
528  auto text = values.getValue (prefix + "Text", prefix);
529 
530  auto textLength = (int) text.getNumBytesAsUTF8() + 1; // include null terminator
531  auto chunkLength = textLength + 20 + (textLength & 1);
532 
533  out.writeInt (chunkName ("ltxt"));
534  out.writeInt (chunkLength);
535  out.writeInt (getValue (values, prefix, "Identifier"));
536  out.writeInt (getValue (values, prefix, "SampleLength"));
537  out.writeInt (getValue (values, prefix, "Purpose"));
538  out.writeShort ((short) getValue (values, prefix, "Country"));
539  out.writeShort ((short) getValue (values, prefix, "Language"));
540  out.writeShort ((short) getValue (values, prefix, "Dialect"));
541  out.writeShort ((short) getValue (values, prefix, "CodePage"));
542  out.write (text.toUTF8(), (size_t) textLength);
543 
544  if ((out.getDataSize() & 1) != 0)
545  out.writeByte (0);
546  }
547 
548  static MemoryBlock createFrom (const StringPairArray& values)
549  {
550  auto numCueLabels = getValue (values, "NumCueLabels");
551  auto numCueNotes = getValue (values, "NumCueNotes");
552  auto numCueRegions = getValue (values, "NumCueRegions");
553 
554  MemoryOutputStream out;
555 
556  if (numCueLabels + numCueNotes + numCueRegions > 0)
557  {
558  out.writeInt (chunkName ("adtl"));
559 
560  for (int i = 0; i < numCueLabels; ++i)
561  appendLabelOrNoteChunk (values, "CueLabel" + String (i), chunkName ("labl"), out);
562 
563  for (int i = 0; i < numCueNotes; ++i)
564  appendLabelOrNoteChunk (values, "CueNote" + String (i), chunkName ("note"), out);
565 
566  for (int i = 0; i < numCueRegions; ++i)
567  appendExtraChunk (values, "CueRegion" + String (i), out);
568  }
569 
570  return out.getMemoryBlock();
571  }
572  }
573 
574  //==============================================================================
575  /** Reads a RIFF List Info chunk from a stream positioned just after the size byte. */
576  namespace ListInfoChunk
577  {
578  static const char* const types[] =
579  {
660  WavAudioFormat::riffInfoYear
661  };
662 
663  static bool isMatchingTypeIgnoringCase (const int value, const char* const name) noexcept
664  {
665  for (int i = 0; i < 4; ++i)
666  if ((juce_wchar) name[i] != CharacterFunctions::toUpperCase ((juce_wchar) ((value >> (i * 8)) & 0xff)))
667  return false;
668 
669  return true;
670  }
671 
672  static void addToMetadata (StringPairArray& values, InputStream& input, int64 chunkEnd)
673  {
674  while (input.getPosition() < chunkEnd)
675  {
676  auto infoType = input.readInt();
677  auto infoLength = chunkEnd - input.getPosition();
678 
679  if (infoLength > 0)
680  {
681  infoLength = jmin (infoLength, (int64) input.readInt());
682 
683  if (infoLength <= 0)
684  return;
685 
686  for (auto& type : types)
687  {
688  if (isMatchingTypeIgnoringCase (infoType, type))
689  {
690  MemoryBlock mb;
691  input.readIntoMemoryBlock (mb, (ssize_t) infoLength);
692  values.set (type, String::createStringFromData ((const char*) mb.getData(),
693  (int) mb.getSize()));
694  break;
695  }
696  }
697  }
698  }
699  }
700 
701  static bool writeValue (const StringPairArray& values, MemoryOutputStream& out, const char* paramName)
702  {
703  auto value = values.getValue (paramName, {});
704 
705  if (value.isEmpty())
706  return false;
707 
708  auto valueLength = (int) value.getNumBytesAsUTF8() + 1;
709  auto chunkLength = valueLength + (valueLength & 1);
710 
711  out.writeInt (chunkName (paramName));
712  out.writeInt (chunkLength);
713  out.write (value.toUTF8(), (size_t) valueLength);
714 
715  if ((out.getDataSize() & 1) != 0)
716  out.writeByte (0);
717 
718  return true;
719  }
720 
721  static MemoryBlock createFrom (const StringPairArray& values)
722  {
723  MemoryOutputStream out;
724  out.writeInt (chunkName ("INFO"));
725  bool anyParamsDefined = false;
726 
727  for (auto& type : types)
728  if (writeValue (values, out, type))
729  anyParamsDefined = true;
730 
731  return anyParamsDefined ? out.getMemoryBlock() : MemoryBlock();
732  }
733  }
734 
735  //==============================================================================
736  struct AcidChunk
737  {
738  /** Reads an acid RIFF chunk from a stream positioned just after the size byte. */
739  AcidChunk (InputStream& input, size_t length)
740  {
741  zerostruct (*this);
742  input.read (this, (int) jmin (sizeof (*this), length));
743  }
744 
745  AcidChunk (const StringPairArray& values)
746  {
747  zerostruct (*this);
748 
749  flags = getFlagIfPresent (values, WavAudioFormat::acidOneShot, 0x01)
750  | getFlagIfPresent (values, WavAudioFormat::acidRootSet, 0x02)
751  | getFlagIfPresent (values, WavAudioFormat::acidStretch, 0x04)
752  | getFlagIfPresent (values, WavAudioFormat::acidDiskBased, 0x08)
753  | getFlagIfPresent (values, WavAudioFormat::acidizerFlag, 0x10);
754 
755  if (values[WavAudioFormat::acidRootSet].getIntValue() != 0)
756  rootNote = ByteOrder::swapIfBigEndian ((uint16) values[WavAudioFormat::acidRootNote].getIntValue());
757 
758  numBeats = ByteOrder::swapIfBigEndian ((uint32) values[WavAudioFormat::acidBeats].getIntValue());
759  meterDenominator = ByteOrder::swapIfBigEndian ((uint16) values[WavAudioFormat::acidDenominator].getIntValue());
760  meterNumerator = ByteOrder::swapIfBigEndian ((uint16) values[WavAudioFormat::acidNumerator].getIntValue());
761 
762  if (values.containsKey (WavAudioFormat::acidTempo))
763  tempo = swapFloatByteOrder (values[WavAudioFormat::acidTempo].getFloatValue());
764  }
765 
766  static MemoryBlock createFrom (const StringPairArray& values)
767  {
768  return AcidChunk (values).toMemoryBlock();
769  }
770 
771  MemoryBlock toMemoryBlock() const
772  {
773  return (flags != 0 || rootNote != 0 || numBeats != 0 || meterDenominator != 0 || meterNumerator != 0)
774  ? MemoryBlock (this, sizeof (*this)) : MemoryBlock();
775  }
776 
777  void addToMetadata (StringPairArray& values) const
778  {
779  setBoolFlag (values, WavAudioFormat::acidOneShot, 0x01);
780  setBoolFlag (values, WavAudioFormat::acidRootSet, 0x02);
781  setBoolFlag (values, WavAudioFormat::acidStretch, 0x04);
782  setBoolFlag (values, WavAudioFormat::acidDiskBased, 0x08);
783  setBoolFlag (values, WavAudioFormat::acidizerFlag, 0x10);
784 
785  if (flags & 0x02) // root note set
786  values.set (WavAudioFormat::acidRootNote, String (ByteOrder::swapIfBigEndian (rootNote)));
787 
788  values.set (WavAudioFormat::acidBeats, String (ByteOrder::swapIfBigEndian (numBeats)));
789  values.set (WavAudioFormat::acidDenominator, String (ByteOrder::swapIfBigEndian (meterDenominator)));
790  values.set (WavAudioFormat::acidNumerator, String (ByteOrder::swapIfBigEndian (meterNumerator)));
791  values.set (WavAudioFormat::acidTempo, String (swapFloatByteOrder (tempo)));
792  }
793 
794  void setBoolFlag (StringPairArray& values, const char* name, uint32 mask) const
795  {
796  values.set (name, (flags & ByteOrder::swapIfBigEndian (mask)) ? "1" : "0");
797  }
798 
799  static uint32 getFlagIfPresent (const StringPairArray& values, const char* name, uint32 flag)
800  {
801  return values[name].getIntValue() != 0 ? ByteOrder::swapIfBigEndian (flag) : 0;
802  }
803 
804  static float swapFloatByteOrder (const float x) noexcept
805  {
806  #ifdef JUCE_BIG_ENDIAN
807  union { uint32 asInt; float asFloat; } n;
808  n.asFloat = x;
809  n.asInt = ByteOrder::swap (n.asInt);
810  return n.asFloat;
811  #else
812  return x;
813  #endif
814  }
815 
816  uint32 flags;
817  uint16 rootNote;
818  uint16 reserved1;
819  float reserved2;
820  uint32 numBeats;
821  uint16 meterDenominator;
822  uint16 meterNumerator;
823  float tempo;
824 
825  } JUCE_PACKED;
826 
827  //==============================================================================
829  {
830  static MemoryBlock createFrom (const StringPairArray& values)
831  {
832  MemoryOutputStream out;
833  auto s = values[WavAudioFormat::tracktionLoopInfo];
834 
835  if (s.isNotEmpty())
836  {
837  out.writeString (s);
838 
839  if ((out.getDataSize() & 1) != 0)
840  out.writeByte (0);
841  }
842 
843  return out.getMemoryBlock();
844  }
845  };
846 
847  //==============================================================================
848  namespace AXMLChunk
849  {
850  static void addToMetadata (StringPairArray& destValues, const String& source)
851  {
852  if (auto xml = parseXML (source))
853  {
854  if (xml->hasTagName ("ebucore:ebuCoreMain"))
855  {
856  if (auto xml2 = xml->getChildByName ("ebucore:coreMetadata"))
857  {
858  if (auto xml3 = xml2->getChildByName ("ebucore:identifier"))
859  {
860  if (auto xml4 = xml3->getChildByName ("dc:identifier"))
861  {
862  auto ISRCCode = xml4->getAllSubText().fromFirstOccurrenceOf ("ISRC:", false, true);
863 
864  if (ISRCCode.isNotEmpty())
865  destValues.set (WavAudioFormat::ISRC, ISRCCode);
866  }
867  }
868  }
869  }
870  }
871  }
872 
873  static MemoryBlock createFrom (const StringPairArray& values)
874  {
875  auto ISRC = values.getValue (WavAudioFormat::ISRC, {});
876  MemoryOutputStream xml;
877 
878  if (ISRC.isNotEmpty())
879  {
880  xml << "<ebucore:ebuCoreMain xmlns:dc=\" http://purl.org/dc/elements/1.1/\" "
881  "xmlns:ebucore=\"urn:ebu:metadata-schema:ebuCore_2012\">"
882  "<ebucore:coreMetadata>"
883  "<ebucore:identifier typeLabel=\"GUID\" "
884  "typeDefinition=\"Globally Unique Identifier\" "
885  "formatLabel=\"ISRC\" "
886  "formatDefinition=\"International Standard Recording Code\" "
887  "formatLink=\"http://www.ebu.ch/metadata/cs/ebu_IdentifierTypeCodeCS.xml#3.7\">"
888  "<dc:identifier>ISRC:" << ISRC << "</dc:identifier>"
889  "</ebucore:identifier>"
890  "</ebucore:coreMetadata>"
891  "</ebucore:ebuCoreMain>";
892 
893  xml.writeRepeatedByte (0, xml.getDataSize()); // ensures even size, null termination and room for future growing
894  }
895 
896  return xml.getMemoryBlock();
897  }
898  }
899 
900  //==============================================================================
902  {
903  uint32 data1;
904  uint16 data2;
905  uint16 data3;
906  uint8 data4[8];
907 
908  bool operator== (const ExtensibleWavSubFormat& other) const noexcept { return memcmp (this, &other, sizeof (*this)) == 0; }
909  bool operator!= (const ExtensibleWavSubFormat& other) const noexcept { return ! operator== (other); }
910 
911  } JUCE_PACKED;
912 
913  static const ExtensibleWavSubFormat pcmFormat = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
914  static const ExtensibleWavSubFormat IEEEFloatFormat = { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
915  static const ExtensibleWavSubFormat ambisonicFormat = { 0x00000001, 0x0721, 0x11d3, { 0x86, 0x44, 0xC8, 0xC1, 0xCA, 0x00, 0x00, 0x00 } };
916 
917  struct DataSize64Chunk // chunk ID = 'ds64' if data size > 0xffffffff, 'JUNK' otherwise
918  {
919  uint32 riffSizeLow; // low 4 byte size of RF64 block
920  uint32 riffSizeHigh; // high 4 byte size of RF64 block
921  uint32 dataSizeLow; // low 4 byte size of data chunk
922  uint32 dataSizeHigh; // high 4 byte size of data chunk
923  uint32 sampleCountLow; // low 4 byte sample count of fact chunk
924  uint32 sampleCountHigh; // high 4 byte sample count of fact chunk
925  uint32 tableLength; // number of valid entries in array 'table'
926  } JUCE_PACKED;
927 
928  #if JUCE_MSVC
929  #pragma pack (pop)
930  #endif
931 }
932 
933 //==============================================================================
935 {
936 public:
937  WavAudioFormatReader (InputStream* in) : AudioFormatReader (in, wavFormatName)
938  {
939  using namespace WavFileHelpers;
940  uint64 len = 0, end = 0;
941  int cueNoteIndex = 0;
942  int cueLabelIndex = 0;
943  int cueRegionIndex = 0;
944 
945  auto streamStartPos = input->getPosition();
946  auto firstChunkType = input->readInt();
947 
948  if (firstChunkType == chunkName ("RF64"))
949  {
950  input->skipNextBytes (4); // size is -1 for RF64
951  isRF64 = true;
952  }
953  else if (firstChunkType == chunkName ("RIFF"))
954  {
955  len = (uint64) (uint32) input->readInt();
956  end = len + (uint64) input->getPosition();
957  }
958  else
959  {
960  return;
961  }
962 
963  auto startOfRIFFChunk = input->getPosition();
964 
965  if (input->readInt() == chunkName ("WAVE"))
966  {
967  if (isRF64 && input->readInt() == chunkName ("ds64"))
968  {
969  auto length = (uint32) input->readInt();
970 
971  if (length < 28)
972  return;
973 
974  auto chunkEnd = input->getPosition() + length + (length & 1);
975  len = (uint64) input->readInt64();
976  end = len + (uint64) startOfRIFFChunk;
977  dataLength = input->readInt64();
978  input->setPosition (chunkEnd);
979  }
980 
981  while ((uint64) input->getPosition() < end && ! input->isExhausted())
982  {
983  auto chunkType = input->readInt();
984  auto length = (uint32) input->readInt();
985  auto chunkEnd = input->getPosition() + length + (length & 1);
986 
987  if (chunkType == chunkName ("fmt "))
988  {
989  // read the format chunk
990  auto format = (unsigned short) input->readShort();
991  numChannels = (unsigned int) input->readShort();
992  sampleRate = input->readInt();
993  auto bytesPerSec = input->readInt();
994  input->skipNextBytes (2);
995  bitsPerSample = (unsigned int) (int) input->readShort();
996 
997  if (bitsPerSample > 64)
998  {
999  bytesPerFrame = bytesPerSec / (int) sampleRate;
1000  bitsPerSample = 8 * (unsigned int) bytesPerFrame / numChannels;
1001  }
1002  else
1003  {
1004  bytesPerFrame = numChannels * bitsPerSample / 8;
1005  }
1006 
1007  if (format == 3)
1008  {
1009  usesFloatingPointData = true;
1010  }
1011  else if (format == 0xfffe) // WAVE_FORMAT_EXTENSIBLE
1012  {
1013  if (length < 40) // too short
1014  {
1015  bytesPerFrame = 0;
1016  }
1017  else
1018  {
1019  input->skipNextBytes (4); // skip over size and bitsPerSample
1020  auto channelMask = input->readInt();
1021  metadataValues.set ("ChannelMask", String (channelMask));
1022  channelLayout = getChannelLayoutFromMask (channelMask, numChannels);
1023 
1024  ExtensibleWavSubFormat subFormat;
1025  subFormat.data1 = (uint32) input->readInt();
1026  subFormat.data2 = (uint16) input->readShort();
1027  subFormat.data3 = (uint16) input->readShort();
1028  input->read (subFormat.data4, sizeof (subFormat.data4));
1029 
1030  if (subFormat == IEEEFloatFormat)
1031  usesFloatingPointData = true;
1032  else if (subFormat != pcmFormat && subFormat != ambisonicFormat)
1033  bytesPerFrame = 0;
1034  }
1035  }
1036  else if (format == 0x674f // WAVE_FORMAT_OGG_VORBIS_MODE_1
1037  || format == 0x6750 // WAVE_FORMAT_OGG_VORBIS_MODE_2
1038  || format == 0x6751 // WAVE_FORMAT_OGG_VORBIS_MODE_3
1039  || format == 0x676f // WAVE_FORMAT_OGG_VORBIS_MODE_1_PLUS
1040  || format == 0x6770 // WAVE_FORMAT_OGG_VORBIS_MODE_2_PLUS
1041  || format == 0x6771) // WAVE_FORMAT_OGG_VORBIS_MODE_3_PLUS
1042  {
1043  isSubformatOggVorbis = true;
1044  sampleRate = 0; // to mark the wav reader as failed
1045  input->setPosition (streamStartPos);
1046  return;
1047  }
1048  else if (format != 1)
1049  {
1050  bytesPerFrame = 0;
1051  }
1052  }
1053  else if (chunkType == chunkName ("data"))
1054  {
1055  if (! isRF64) // data size is expected to be -1, actual data size is in ds64 chunk
1056  dataLength = length;
1057 
1058  dataChunkStart = input->getPosition();
1059  lengthInSamples = (bytesPerFrame > 0) ? (dataLength / bytesPerFrame) : 0;
1060  }
1061  else if (chunkType == chunkName ("bext"))
1062  {
1063  bwavChunkStart = input->getPosition();
1064  bwavSize = length;
1065 
1066  HeapBlock<BWAVChunk> bwav;
1067  bwav.calloc (jmax ((size_t) length + 1, sizeof (BWAVChunk)), 1);
1068  input->read (bwav, (int) length);
1069  bwav->copyTo (metadataValues, (int) length);
1070  }
1071  else if (chunkType == chunkName ("smpl"))
1072  {
1073  HeapBlock<SMPLChunk> smpl;
1074  smpl.calloc (jmax ((size_t) length + 1, sizeof (SMPLChunk)), 1);
1075  input->read (smpl, (int) length);
1076  smpl->copyTo (metadataValues, (int) length);
1077  }
1078  else if (chunkType == chunkName ("inst") || chunkType == chunkName ("INST")) // need to check which...
1079  {
1080  HeapBlock<InstChunk> inst;
1081  inst.calloc (jmax ((size_t) length + 1, sizeof (InstChunk)), 1);
1082  input->read (inst, (int) length);
1083  inst->copyTo (metadataValues);
1084  }
1085  else if (chunkType == chunkName ("cue "))
1086  {
1087  HeapBlock<CueChunk> cue;
1088  cue.calloc (jmax ((size_t) length + 1, sizeof (CueChunk)), 1);
1089  input->read (cue, (int) length);
1090  cue->copyTo (metadataValues, (int) length);
1091  }
1092  else if (chunkType == chunkName ("axml"))
1093  {
1094  MemoryBlock axml;
1095  input->readIntoMemoryBlock (axml, (ssize_t) length);
1096  AXMLChunk::addToMetadata (metadataValues, axml.toString());
1097  }
1098  else if (chunkType == chunkName ("LIST"))
1099  {
1100  auto subChunkType = input->readInt();
1101 
1102  if (subChunkType == chunkName ("info") || subChunkType == chunkName ("INFO"))
1103  {
1104  ListInfoChunk::addToMetadata (metadataValues, *input, chunkEnd);
1105  }
1106  else if (subChunkType == chunkName ("adtl"))
1107  {
1108  while (input->getPosition() < chunkEnd)
1109  {
1110  auto adtlChunkType = input->readInt();
1111  auto adtlLength = (uint32) input->readInt();
1112  auto adtlChunkEnd = input->getPosition() + (adtlLength + (adtlLength & 1));
1113 
1114  if (adtlChunkType == chunkName ("labl") || adtlChunkType == chunkName ("note"))
1115  {
1116  String prefix;
1117 
1118  if (adtlChunkType == chunkName ("labl"))
1119  prefix << "CueLabel" << cueLabelIndex++;
1120  else if (adtlChunkType == chunkName ("note"))
1121  prefix << "CueNote" << cueNoteIndex++;
1122 
1123  auto identifier = (uint32) input->readInt();
1124  auto stringLength = (int) adtlLength - 4;
1125 
1126  MemoryBlock textBlock;
1127  input->readIntoMemoryBlock (textBlock, stringLength);
1128 
1129  metadataValues.set (prefix + "Identifier", String (identifier));
1130  metadataValues.set (prefix + "Text", textBlock.toString());
1131  }
1132  else if (adtlChunkType == chunkName ("ltxt"))
1133  {
1134  auto prefix = "CueRegion" + String (cueRegionIndex++);
1135  auto identifier = (uint32) input->readInt();
1136  auto sampleLength = (uint32) input->readInt();
1137  auto purpose = (uint32) input->readInt();
1138  auto country = (uint16) input->readShort();
1139  auto language = (uint16) input->readShort();
1140  auto dialect = (uint16) input->readShort();
1141  auto codePage = (uint16) input->readShort();
1142  auto stringLength = adtlLength - 20;
1143 
1144  MemoryBlock textBlock;
1145  input->readIntoMemoryBlock (textBlock, (int) stringLength);
1146 
1147  metadataValues.set (prefix + "Identifier", String (identifier));
1148  metadataValues.set (prefix + "SampleLength", String (sampleLength));
1149  metadataValues.set (prefix + "Purpose", String (purpose));
1150  metadataValues.set (prefix + "Country", String (country));
1151  metadataValues.set (prefix + "Language", String (language));
1152  metadataValues.set (prefix + "Dialect", String (dialect));
1153  metadataValues.set (prefix + "CodePage", String (codePage));
1154  metadataValues.set (prefix + "Text", textBlock.toString());
1155  }
1156 
1157  input->setPosition (adtlChunkEnd);
1158  }
1159  }
1160  }
1161  else if (chunkType == chunkName ("acid"))
1162  {
1163  AcidChunk (*input, length).addToMetadata (metadataValues);
1164  }
1165  else if (chunkType == chunkName ("Trkn"))
1166  {
1167  MemoryBlock tracktion;
1168  input->readIntoMemoryBlock (tracktion, (ssize_t) length);
1169  metadataValues.set (WavAudioFormat::tracktionLoopInfo, tracktion.toString());
1170  }
1171  else if (chunkEnd <= input->getPosition())
1172  {
1173  break;
1174  }
1175 
1176  input->setPosition (chunkEnd);
1177  }
1178  }
1179 
1180  if (cueLabelIndex > 0) metadataValues.set ("NumCueLabels", String (cueLabelIndex));
1181  if (cueNoteIndex > 0) metadataValues.set ("NumCueNotes", String (cueNoteIndex));
1182  if (cueRegionIndex > 0) metadataValues.set ("NumCueRegions", String (cueRegionIndex));
1183  if (metadataValues.size() > 0) metadataValues.set ("MetaDataSource", "WAV");
1184  }
1185 
1186  //==============================================================================
1187  bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
1188  int64 startSampleInFile, int numSamples) override
1189  {
1190  clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
1191  startSampleInFile, numSamples, lengthInSamples);
1192 
1193  if (numSamples <= 0)
1194  return true;
1195 
1196  input->setPosition (dataChunkStart + startSampleInFile * bytesPerFrame);
1197 
1198  while (numSamples > 0)
1199  {
1200  const int tempBufSize = 480 * 3 * 4; // (keep this a multiple of 3)
1201  char tempBuffer[tempBufSize];
1202 
1203  auto numThisTime = jmin (tempBufSize / bytesPerFrame, numSamples);
1204  auto bytesRead = input->read (tempBuffer, numThisTime * bytesPerFrame);
1205 
1206  if (bytesRead < numThisTime * bytesPerFrame)
1207  {
1208  jassert (bytesRead >= 0);
1209  zeromem (tempBuffer + bytesRead, (size_t) (numThisTime * bytesPerFrame - bytesRead));
1210  }
1211 
1212  copySampleData (bitsPerSample, usesFloatingPointData,
1213  destSamples, startOffsetInDestBuffer, numDestChannels,
1214  tempBuffer, (int) numChannels, numThisTime);
1215 
1216  startOffsetInDestBuffer += numThisTime;
1217  numSamples -= numThisTime;
1218  }
1219 
1220  return true;
1221  }
1222 
1223  static void copySampleData (unsigned int bitsPerSample, const bool usesFloatingPointData,
1224  int* const* destSamples, int startOffsetInDestBuffer, int numDestChannels,
1225  const void* sourceData, int numChannels, int numSamples) noexcept
1226  {
1227  switch (bitsPerSample)
1228  {
1229  case 8: ReadHelper<AudioData::Int32, AudioData::UInt8, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break;
1230  case 16: ReadHelper<AudioData::Int32, AudioData::Int16, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break;
1231  case 24: ReadHelper<AudioData::Int32, AudioData::Int24, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break;
1232  case 32: if (usesFloatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples);
1233  else ReadHelper<AudioData::Int32, AudioData::Int32, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples);
1234  break;
1235  default: jassertfalse; break;
1236  }
1237  }
1238 
1239  //==============================================================================
1241  {
1242  if (channelLayout.size() == static_cast<int> (numChannels))
1243  return channelLayout;
1244 
1245  return WavFileHelpers::canonicalWavChannelSet (static_cast<int> (numChannels));
1246  }
1247 
1248  static AudioChannelSet getChannelLayoutFromMask (int dwChannelMask, size_t totalNumChannels)
1249  {
1250  AudioChannelSet wavFileChannelLayout;
1251 
1252  // AudioChannelSet and wav's dwChannelMask are compatible
1253  BigInteger channelBits (dwChannelMask);
1254 
1255  for (auto bit = channelBits.findNextSetBit (0); bit >= 0; bit = channelBits.findNextSetBit (bit + 1))
1256  wavFileChannelLayout.addChannel (static_cast<AudioChannelSet::ChannelType> (bit + 1));
1257 
1258  // channel layout and number of channels do not match
1259  if (wavFileChannelLayout.size() != static_cast<int> (totalNumChannels))
1260  {
1261  // for backward compatibility with old wav files, assume 1 or 2
1262  // channel wav files are mono/stereo respectively
1263  if (totalNumChannels <= 2 && dwChannelMask == 0)
1264  wavFileChannelLayout = AudioChannelSet::canonicalChannelSet (static_cast<int> (totalNumChannels));
1265  else
1266  {
1267  auto discreteSpeaker = static_cast<int> (AudioChannelSet::discreteChannel0);
1268 
1269  while (wavFileChannelLayout.size() < static_cast<int> (totalNumChannels))
1270  wavFileChannelLayout.addChannel (static_cast<AudioChannelSet::ChannelType> (discreteSpeaker++));
1271  }
1272  }
1273 
1274  return wavFileChannelLayout;
1275  }
1276 
1277  int64 bwavChunkStart = 0, bwavSize = 0;
1278  int64 dataChunkStart = 0, dataLength = 0;
1279  int bytesPerFrame = 0;
1280  bool isRF64 = false;
1281  bool isSubformatOggVorbis = false;
1282 
1283  AudioChannelSet channelLayout;
1284 
1285 private:
1286  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatReader)
1287 };
1288 
1289 //==============================================================================
1291 {
1292 public:
1293  WavAudioFormatWriter (OutputStream* const out, const double rate,
1294  const AudioChannelSet& channelLayoutToUse, const unsigned int bits,
1295  const StringPairArray& metadataValues)
1296  : AudioFormatWriter (out, wavFormatName, rate, channelLayoutToUse, bits)
1297  {
1298  using namespace WavFileHelpers;
1299 
1300  if (metadataValues.size() > 0)
1301  {
1302  // The meta data should have been sanitised for the WAV format.
1303  // If it was originally sourced from an AIFF file the MetaDataSource
1304  // key should be removed (or set to "WAV") once this has been done
1305  jassert (metadataValues.getValue ("MetaDataSource", "None") != "AIFF");
1306 
1307  bwavChunk = BWAVChunk::createFrom (metadataValues);
1308  axmlChunk = AXMLChunk::createFrom (metadataValues);
1309  smplChunk = SMPLChunk::createFrom (metadataValues);
1310  instChunk = InstChunk::createFrom (metadataValues);
1311  cueChunk = CueChunk ::createFrom (metadataValues);
1312  listChunk = ListChunk::createFrom (metadataValues);
1313  listInfoChunk = ListInfoChunk::createFrom (metadataValues);
1314  acidChunk = AcidChunk::createFrom (metadataValues);
1315  trckChunk = TracktionChunk::createFrom (metadataValues);
1316  }
1317 
1318  headerPosition = out->getPosition();
1319  writeHeader();
1320  }
1321 
1322  ~WavAudioFormatWriter() override
1323  {
1324  writeHeader();
1325  }
1326 
1327  //==============================================================================
1328  bool write (const int** data, int numSamples) override
1329  {
1330  jassert (numSamples >= 0);
1331  jassert (data != nullptr && *data != nullptr); // the input must contain at least one channel!
1332 
1333  if (writeFailed)
1334  return false;
1335 
1336  auto bytes = numChannels * (size_t) numSamples * bitsPerSample / 8;
1337  tempBlock.ensureSize (bytes, false);
1338 
1339  switch (bitsPerSample)
1340  {
1341  case 8: WriteHelper<AudioData::UInt8, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break;
1342  case 16: WriteHelper<AudioData::Int16, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break;
1343  case 24: WriteHelper<AudioData::Int24, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break;
1344  case 32: WriteHelper<AudioData::Int32, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break;
1345  default: jassertfalse; break;
1346  }
1347 
1348  if (! output->write (tempBlock.getData(), bytes))
1349  {
1350  // failed to write to disk, so let's try writing the header.
1351  // If it's just run out of disk space, then if it does manage
1352  // to write the header, we'll still have a useable file..
1353  writeHeader();
1354  writeFailed = true;
1355  return false;
1356  }
1357 
1358  bytesWritten += bytes;
1359  lengthInSamples += (uint64) numSamples;
1360  return true;
1361  }
1362 
1363  bool flush() override
1364  {
1365  auto lastWritePos = output->getPosition();
1366  writeHeader();
1367 
1368  if (output->setPosition (lastWritePos))
1369  return true;
1370 
1371  // if this fails, you've given it an output stream that can't seek! It needs
1372  // to be able to seek back to write the header
1373  jassertfalse;
1374  return false;
1375  }
1376 
1377 private:
1378  MemoryBlock tempBlock, bwavChunk, axmlChunk, smplChunk, instChunk, cueChunk, listChunk, listInfoChunk, acidChunk, trckChunk;
1379  uint64 lengthInSamples = 0, bytesWritten = 0;
1380  int64 headerPosition = 0;
1381  bool writeFailed = false;
1382 
1383  void writeHeader()
1384  {
1385  if ((bytesWritten & 1) != 0) // pad to an even length
1386  output->writeByte (0);
1387 
1388  using namespace WavFileHelpers;
1389 
1390  if (headerPosition != output->getPosition() && ! output->setPosition (headerPosition))
1391  {
1392  // if this fails, you've given it an output stream that can't seek! It needs to be
1393  // able to seek back to go back and write the header after the data has been written.
1394  jassertfalse;
1395  return;
1396  }
1397 
1398  const size_t bytesPerFrame = numChannels * bitsPerSample / 8;
1399  uint64 audioDataSize = bytesPerFrame * lengthInSamples;
1400  auto channelMask = getChannelMaskFromChannelLayout (channelLayout);
1401 
1402  const bool isRF64 = (bytesWritten >= 0x100000000LL);
1403  const bool isWaveFmtEx = isRF64 || (channelMask != 0);
1404 
1405  int64 riffChunkSize = (int64) (4 /* 'RIFF' */ + 8 + 40 /* WAVEFORMATEX */
1406  + 8 + audioDataSize + (audioDataSize & 1)
1407  + chunkSize (bwavChunk)
1408  + chunkSize (axmlChunk)
1409  + chunkSize (smplChunk)
1410  + chunkSize (instChunk)
1411  + chunkSize (cueChunk)
1412  + chunkSize (listChunk)
1413  + chunkSize (listInfoChunk)
1414  + chunkSize (acidChunk)
1415  + chunkSize (trckChunk)
1416  + (8 + 28)); // (ds64 chunk)
1417 
1418  riffChunkSize += (riffChunkSize & 1);
1419 
1420  if (isRF64)
1421  writeChunkHeader (chunkName ("RF64"), -1);
1422  else
1423  writeChunkHeader (chunkName ("RIFF"), (int) riffChunkSize);
1424 
1425  output->writeInt (chunkName ("WAVE"));
1426 
1427  if (! isRF64)
1428  {
1429  #if ! JUCE_WAV_DO_NOT_PAD_HEADER_SIZE
1430  /* NB: This junk chunk is added for padding, so that the header is a fixed size
1431  regardless of whether it's RF64 or not. That way, we can begin recording a file,
1432  and when it's finished, can go back and write either a RIFF or RF64 header,
1433  depending on whether more than 2^32 samples were written.
1434 
1435  The JUCE_WAV_DO_NOT_PAD_HEADER_SIZE macro allows you to disable this feature in case
1436  you need to create files for crappy WAV players with bugs that stop them skipping chunks
1437  which they don't recognise. But DO NOT USE THIS option unless you really have no choice,
1438  because it means that if you write more than 2^32 samples to the file, you'll corrupt it.
1439  */
1440  writeChunkHeader (chunkName ("JUNK"), 28 + (isWaveFmtEx? 0 : 24));
1441  output->writeRepeatedByte (0, 28 /* ds64 */ + (isWaveFmtEx? 0 : 24));
1442  #endif
1443  }
1444  else
1445  {
1446  #if JUCE_WAV_DO_NOT_PAD_HEADER_SIZE
1447  // If you disable padding, then you MUST NOT write more than 2^32 samples to a file.
1448  jassertfalse;
1449  #endif
1450 
1451  writeChunkHeader (chunkName ("ds64"), 28); // chunk size for uncompressed data (no table)
1452  output->writeInt64 (riffChunkSize);
1453  output->writeInt64 ((int64) audioDataSize);
1454  output->writeRepeatedByte (0, 12);
1455  }
1456 
1457  if (isWaveFmtEx)
1458  {
1459  writeChunkHeader (chunkName ("fmt "), 40);
1460  output->writeShort ((short) (uint16) 0xfffe); // WAVE_FORMAT_EXTENSIBLE
1461  }
1462  else
1463  {
1464  writeChunkHeader (chunkName ("fmt "), 16);
1465  output->writeShort (bitsPerSample < 32 ? (short) 1 /*WAVE_FORMAT_PCM*/
1466  : (short) 3 /*WAVE_FORMAT_IEEE_FLOAT*/);
1467  }
1468 
1469  output->writeShort ((short) numChannels);
1470  output->writeInt ((int) sampleRate);
1471  output->writeInt ((int) (bytesPerFrame * sampleRate)); // nAvgBytesPerSec
1472  output->writeShort ((short) bytesPerFrame); // nBlockAlign
1473  output->writeShort ((short) bitsPerSample); // wBitsPerSample
1474 
1475  if (isWaveFmtEx)
1476  {
1477  output->writeShort (22); // cbSize (size of the extension)
1478  output->writeShort ((short) bitsPerSample); // wValidBitsPerSample
1479  output->writeInt (channelMask);
1480 
1481  const ExtensibleWavSubFormat& subFormat = bitsPerSample < 32 ? pcmFormat : IEEEFloatFormat;
1482 
1483  output->writeInt ((int) subFormat.data1);
1484  output->writeShort ((short) subFormat.data2);
1485  output->writeShort ((short) subFormat.data3);
1486  output->write (subFormat.data4, sizeof (subFormat.data4));
1487  }
1488 
1489  writeChunk (bwavChunk, chunkName ("bext"));
1490  writeChunk (axmlChunk, chunkName ("axml"));
1491  writeChunk (smplChunk, chunkName ("smpl"));
1492  writeChunk (instChunk, chunkName ("inst"), 7);
1493  writeChunk (cueChunk, chunkName ("cue "));
1494  writeChunk (listChunk, chunkName ("LIST"));
1495  writeChunk (listInfoChunk, chunkName ("LIST"));
1496  writeChunk (acidChunk, chunkName ("acid"));
1497  writeChunk (trckChunk, chunkName ("Trkn"));
1498 
1499  writeChunkHeader (chunkName ("data"), isRF64 ? -1 : (int) (lengthInSamples * bytesPerFrame));
1500 
1501  usesFloatingPointData = (bitsPerSample == 32);
1502  }
1503 
1504  static size_t chunkSize (const MemoryBlock& data) noexcept { return data.getSize() > 0 ? (8 + data.getSize()) : 0; }
1505 
1506  void writeChunkHeader (int chunkType, int size) const
1507  {
1508  output->writeInt (chunkType);
1509  output->writeInt (size);
1510  }
1511 
1512  void writeChunk (const MemoryBlock& data, int chunkType, int size = 0) const
1513  {
1514  if (data.getSize() > 0)
1515  {
1516  writeChunkHeader (chunkType, size != 0 ? size : (int) data.getSize());
1517  *output << data;
1518  }
1519  }
1520 
1521  static int getChannelMaskFromChannelLayout (const AudioChannelSet& channelLayout)
1522  {
1523  if (channelLayout.isDiscreteLayout())
1524  return 0;
1525 
1526  // Don't add an extended format chunk for mono and stereo. Basically, all wav players
1527  // interpret a wav file with only one or two channels to be mono or stereo anyway.
1528  if (channelLayout == AudioChannelSet::mono() || channelLayout == AudioChannelSet::stereo())
1529  return 0;
1530 
1531  auto channels = channelLayout.getChannelTypes();
1532  auto wavChannelMask = 0;
1533 
1534  for (auto channel : channels)
1535  {
1536  int wavChannelBit = static_cast<int> (channel) - 1;
1537  jassert (wavChannelBit >= 0 && wavChannelBit <= 31);
1538 
1539  wavChannelMask |= (1 << wavChannelBit);
1540  }
1541 
1542  return wavChannelMask;
1543  }
1544 
1545  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatWriter)
1546 };
1547 
1548 //==============================================================================
1550 {
1551 public:
1552  MemoryMappedWavReader (const File& wavFile, const WavAudioFormatReader& reader)
1553  : MemoryMappedAudioFormatReader (wavFile, reader, reader.dataChunkStart,
1554  reader.dataLength, reader.bytesPerFrame)
1555  {
1556  }
1557 
1558  bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
1559  int64 startSampleInFile, int numSamples) override
1560  {
1561  clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
1562  startSampleInFile, numSamples, lengthInSamples);
1563 
1564  if (map == nullptr || ! mappedSection.contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
1565  {
1566  jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read.
1567  return false;
1568  }
1569 
1570  WavAudioFormatReader::copySampleData (bitsPerSample, usesFloatingPointData,
1571  destSamples, startOffsetInDestBuffer, numDestChannels,
1572  sampleToPointer (startSampleInFile), (int) numChannels, numSamples);
1573  return true;
1574  }
1575 
1576  void getSample (int64 sample, float* result) const noexcept override
1577  {
1578  auto num = (int) numChannels;
1579 
1580  if (map == nullptr || ! mappedSection.contains (sample))
1581  {
1582  jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read.
1583 
1584  zeromem (result, sizeof (float) * (size_t) num);
1585  return;
1586  }
1587 
1588  auto dest = &result;
1589  auto source = sampleToPointer (sample);
1590 
1591  switch (bitsPerSample)
1592  {
1593  case 8: ReadHelper<AudioData::Float32, AudioData::UInt8, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num); break;
1594  case 16: ReadHelper<AudioData::Float32, AudioData::Int16, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num); break;
1595  case 24: ReadHelper<AudioData::Float32, AudioData::Int24, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num); break;
1596  case 32: if (usesFloatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
1598  break;
1599  default: jassertfalse; break;
1600  }
1601  }
1602 
1603  void readMaxLevels (int64 startSampleInFile, int64 numSamples, Range<float>* results, int numChannelsToRead) override
1604  {
1605  numSamples = jmin (numSamples, lengthInSamples - startSampleInFile);
1606 
1607  if (map == nullptr || numSamples <= 0 || ! mappedSection.contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
1608  {
1609  jassert (numSamples <= 0); // you must make sure that the window contains all the samples you're going to attempt to read.
1610 
1611  for (int i = 0; i < numChannelsToRead; ++i)
1612  results[i] = {};
1613 
1614  return;
1615  }
1616 
1617  switch (bitsPerSample)
1618  {
1619  case 8: scanMinAndMax<AudioData::UInt8> (startSampleInFile, numSamples, results, numChannelsToRead); break;
1620  case 16: scanMinAndMax<AudioData::Int16> (startSampleInFile, numSamples, results, numChannelsToRead); break;
1621  case 24: scanMinAndMax<AudioData::Int24> (startSampleInFile, numSamples, results, numChannelsToRead); break;
1622  case 32: if (usesFloatingPointData) scanMinAndMax<AudioData::Float32> (startSampleInFile, numSamples, results, numChannelsToRead);
1623  else scanMinAndMax<AudioData::Int32> (startSampleInFile, numSamples, results, numChannelsToRead);
1624  break;
1625  default: jassertfalse; break;
1626  }
1627  }
1628 
1629 private:
1630  template <typename SampleType>
1631  void scanMinAndMax (int64 startSampleInFile, int64 numSamples, Range<float>* results, int numChannelsToRead) const noexcept
1632  {
1633  for (int i = 0; i < numChannelsToRead; ++i)
1634  results[i] = scanMinAndMaxInterleaved<SampleType, AudioData::LittleEndian> (i, startSampleInFile, numSamples);
1635  }
1636 
1637  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedWavReader)
1638 };
1639 
1640 //==============================================================================
1641 WavAudioFormat::WavAudioFormat() : AudioFormat (wavFormatName, ".wav .bwf") {}
1643 
1645 {
1646  return { 8000, 11025, 12000, 16000, 22050, 32000, 44100,
1647  48000, 88200, 96000, 176400, 192000, 352800, 384000 };
1648 }
1649 
1651 {
1652  return { 8, 16, 24, 32 };
1653 }
1654 
1655 bool WavAudioFormat::canDoStereo() { return true; }
1656 bool WavAudioFormat::canDoMono() { return true; }
1657 
1659 {
1660  auto channelTypes = channelSet.getChannelTypes();
1661 
1662  // When
1663  if (channelSet.isDiscreteLayout())
1664  return true;
1665 
1666  // WAV supports all channel types from left ... topRearRight
1667  for (auto channel : channelTypes)
1668  if (channel < AudioChannelSet::left || channel > AudioChannelSet::topRearRight)
1669  return false;
1670 
1671  return true;
1672 }
1673 
1674 AudioFormatReader* WavAudioFormat::createReaderFor (InputStream* sourceStream, bool deleteStreamIfOpeningFails)
1675 {
1676  std::unique_ptr<WavAudioFormatReader> r (new WavAudioFormatReader (sourceStream));
1677 
1678  #if JUCE_USE_OGGVORBIS
1679  if (r->isSubformatOggVorbis)
1680  {
1681  r->input = nullptr;
1682  return OggVorbisAudioFormat().createReaderFor (sourceStream, deleteStreamIfOpeningFails);
1683  }
1684  #endif
1685 
1686  if (r->sampleRate > 0 && r->numChannels > 0 && r->bytesPerFrame > 0 && r->bitsPerSample <= 32)
1687  return r.release();
1688 
1689  if (! deleteStreamIfOpeningFails)
1690  r->input = nullptr;
1691 
1692  return nullptr;
1693 }
1694 
1696 {
1698 }
1699 
1701 {
1702  if (fin != nullptr)
1703  {
1704  WavAudioFormatReader reader (fin);
1705 
1706  if (reader.lengthInSamples > 0)
1707  return new MemoryMappedWavReader (fin->getFile(), reader);
1708  }
1709 
1710  return nullptr;
1711 }
1712 
1714  unsigned int numChannels, int bitsPerSample,
1715  const StringPairArray& metadataValues, int qualityOptionIndex)
1716 {
1717  return createWriterFor (out, sampleRate, WavFileHelpers::canonicalWavChannelSet (static_cast<int> (numChannels)),
1718  bitsPerSample, metadataValues, qualityOptionIndex);
1719 }
1720 
1722  double sampleRate,
1723  const AudioChannelSet& channelLayout,
1724  int bitsPerSample,
1725  const StringPairArray& metadataValues,
1726  int /*qualityOptionIndex*/)
1727 {
1728  if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample) && isChannelLayoutSupported (channelLayout))
1729  return new WavAudioFormatWriter (out, sampleRate, channelLayout,
1730  (unsigned int) bitsPerSample, metadataValues);
1731 
1732  return nullptr;
1733 }
1734 
1735 namespace WavFileHelpers
1736 {
1737  static bool slowCopyWavFileWithNewMetadata (const File& file, const StringPairArray& metadata)
1738  {
1739  TemporaryFile tempFile (file);
1740  WavAudioFormat wav;
1741 
1742  std::unique_ptr<AudioFormatReader> reader (wav.createReaderFor (file.createInputStream(), true));
1743 
1744  if (reader != nullptr)
1745  {
1746  std::unique_ptr<OutputStream> outStream (tempFile.getFile().createOutputStream());
1747 
1748  if (outStream != nullptr)
1749  {
1750  std::unique_ptr<AudioFormatWriter> writer (wav.createWriterFor (outStream.get(), reader->sampleRate,
1751  reader->numChannels, (int) reader->bitsPerSample,
1752  metadata, 0));
1753 
1754  if (writer != nullptr)
1755  {
1756  outStream.release();
1757 
1758  bool ok = writer->writeFromAudioReader (*reader, 0, -1);
1759  writer.reset();
1760  reader.reset();
1761 
1762  return ok && tempFile.overwriteTargetFileWithTemporary();
1763  }
1764  }
1765  }
1766 
1767  return false;
1768  }
1769 }
1770 
1771 bool WavAudioFormat::replaceMetadataInFile (const File& wavFile, const StringPairArray& newMetadata)
1772 {
1773  using namespace WavFileHelpers;
1774 
1775  std::unique_ptr<WavAudioFormatReader> reader (static_cast<WavAudioFormatReader*> (createReaderFor (wavFile.createInputStream(), true)));
1776 
1777  if (reader != nullptr)
1778  {
1779  auto bwavPos = reader->bwavChunkStart;
1780  auto bwavSize = reader->bwavSize;
1781  reader.reset();
1782 
1783  if (bwavSize > 0)
1784  {
1785  auto chunk = BWAVChunk::createFrom (newMetadata);
1786 
1787  if (chunk.getSize() <= (size_t) bwavSize)
1788  {
1789  // the new one will fit in the space available, so write it directly..
1790  auto oldSize = wavFile.getSize();
1791 
1792  {
1793  FileOutputStream out (wavFile);
1794 
1795  if (out.openedOk())
1796  {
1797  out.setPosition (bwavPos);
1798  out << chunk;
1799  out.setPosition (oldSize);
1800  }
1801  }
1802 
1803  jassert (wavFile.getSize() == oldSize);
1804  return true;
1805  }
1806  }
1807  }
1808 
1809  return slowCopyWavFileWithNewMetadata (wavFile, newMetadata);
1810 }
1811 
1812 //==============================================================================
1813 #if JUCE_UNIT_TESTS
1814 
1815 struct WaveAudioFormatTests : public UnitTest
1816 {
1817  WaveAudioFormatTests() : UnitTest ("Wave audio format tests") {}
1818 
1819  void runTest() override
1820  {
1821  beginTest ("Setting up metadata");
1822 
1823  StringPairArray metadataValues = WavAudioFormat::createBWAVMetadata ("description",
1824  "originator",
1825  "originatorRef",
1827  numTestAudioBufferSamples,
1828  "codingHistory");
1829 
1830  for (int i = numElementsInArray (WavFileHelpers::ListInfoChunk::types); --i >= 0;)
1831  metadataValues.set (WavFileHelpers::ListInfoChunk::types[i],
1832  WavFileHelpers::ListInfoChunk::types[i]);
1833 
1834  if (metadataValues.size() > 0)
1835  metadataValues.set ("MetaDataSource", "WAV");
1836 
1837  metadataValues.addArray (createDefaultSMPLMetadata());
1838 
1839  WavAudioFormat format;
1840  MemoryBlock memoryBlock;
1841 
1842  {
1843  beginTest ("Creating a basic wave writer");
1844 
1845  std::unique_ptr<AudioFormatWriter> writer (format.createWriterFor (new MemoryOutputStream (memoryBlock, false),
1846  44100.0, numTestAudioBufferChannels,
1847  32, metadataValues, 0));
1848  expect (writer != nullptr);
1849 
1850  AudioBuffer<float> buffer (numTestAudioBufferChannels, numTestAudioBufferSamples);
1851  buffer.clear();
1852 
1853  beginTest ("Writing audio data to the basic wave writer");
1854  expect (writer->writeFromAudioSampleBuffer (buffer, 0, numTestAudioBufferSamples));
1855  }
1856 
1857  {
1858  beginTest ("Creating a basic wave reader");
1859 
1860  std::unique_ptr<AudioFormatReader> reader (format.createReaderFor (new MemoryInputStream (memoryBlock, false), false));
1861  expect (reader != nullptr);
1862  expect (reader->metadataValues == metadataValues, "Somehow, the metadata is different!");
1863  }
1864  }
1865 
1866 private:
1867  enum
1868  {
1869  numTestAudioBufferChannels = 2,
1870  numTestAudioBufferSamples = 256
1871  };
1872 
1873  StringPairArray createDefaultSMPLMetadata() const
1874  {
1875  StringPairArray m;
1876 
1877  m.set ("Manufacturer", "0");
1878  m.set ("Product", "0");
1879  m.set ("SamplePeriod", "0");
1880  m.set ("MidiUnityNote", "60");
1881  m.set ("MidiPitchFraction", "0");
1882  m.set ("SmpteFormat", "0");
1883  m.set ("SmpteOffset", "0");
1884  m.set ("NumSampleLoops", "0");
1885  m.set ("SamplerData", "0");
1886 
1887  return m;
1888  }
1889 
1890  JUCE_DECLARE_NON_COPYABLE (WaveAudioFormatTests)
1891 };
1892 
1893 static const WaveAudioFormatTests waveAudioFormatTests;
1894 
1895 #endif
1896 
1897 } // namespace juce
Non-typed individual channels are indexed upwards from this value.
static const char *const riffInfoOrganisation
Metadata property name used in INFO chunks.
size_t getSize() const noexcept
Returns the block&#39;s current allocated size, in bytes.
static const char *const riffInfoTapeName
Metadata property name used in INFO chunks.
static Type swapIfBigEndian(Type value) noexcept
Swaps the byte order of a signed or unsigned integer if the CPU is big-endian.
Represents a set of audio channel types.
static const char *const bwavOriginatorRef
Metadata property name used in BWAV chunks.
static juce_wchar toUpperCase(juce_wchar character) noexcept
Converts a character to upper-case.
Manages a temporary file, which will be deleted when this object is deleted.
static const char *const riffInfoComment2
Metadata property name used in INFO chunks.
static const char *const riffInfoMoreInfoBannerURL
Metadata property name used in INFO chunks.
static const char *const acidDenominator
Metadata property name used in acid chunks.
static const char *const tracktionLoopInfo
Metadata property name used when reading a WAV file with a Tracktion chunk.
static const char *const riffInfoWatermarkURL
Metadata property name used in INFO chunks.
static const char *const acidNumerator
Metadata property name used in acid chunks.
static const char *const riffInfoProductionStudio
Metadata property name used in INFO chunks.
static StringPairArray createBWAVMetadata(const String &description, const String &originator, const String &originatorRef, Time dateAndTime, int64 timeReferenceSamples, const String &codingHistory)
Utility function to fill out the appropriate metadata for a BWAV file.
static const char *const acidDiskBased
Metadata property name used in acid chunks.
static const char *const riffInfoCountry
Metadata property name used in INFO chunks.
static const char *const riffInfoTechnician
Metadata property name used in INFO chunks.
bool overwriteTargetFileWithTemporary() const
Tries to move the temporary file to overwrite the target file that was specified in the constructor...
Reads and writes the Ogg-Vorbis audio format.
static const char *const riffInfoStartTimecode
Metadata property name used in INFO chunks.
static const char *const riffInfoSeventhLanguage
Metadata property name used in INFO chunks.
static const char *const riffInfoRate
Metadata property name used in INFO chunks.
const File & getFile() const noexcept
Returns the temporary file.
virtual bool writeByte(char byte)
Writes a single byte to the stream.
bool isChannelLayoutSupported(const AudioChannelSet &channelSet) override
Returns true if the channel layout is supported by this format.
static const char *const riffInfoMoreInfoBannerImage
Metadata property name used in INFO chunks.
static const char *const riffInfoLength
Metadata property name used in INFO chunks.
static const char *const riffInfoFifthLanguage
Metadata property name used in INFO chunks.
static const char *const riffInfoStarring_ISTR
Metadata property name used in INFO chunks.
bool write(const int **data, int numSamples) override
Writes a set of samples to the audio stream.
static const char *const riffInfoSecondLanguage
Metadata property name used in INFO chunks.
static AudioChannelSet JUCE_CALLTYPE create7point1SDDS()
Creates a set for a 7.1 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre, LFE).
static const char *const riffInfoNumberOfParts
Metadata property name used in INFO chunks.
bool setPosition(int64) override
Tries to move the stream&#39;s output position.
static const char *const riffInfoVegasVersionMajor
Metadata property name used in INFO chunks.
The base class for streams that read data.
static const char *const riffInfoProductName
Metadata property name used in INFO chunks.
const StringArray & getAllKeys() const noexcept
Returns a list of all keys in the array.
bool canDoStereo() override
Returns true if the format can do 2-channel audio.
void add(const ElementType &newElement)
Appends a new element at the end of the array.
Definition: juce_Array.h:375
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.
int size() const noexcept
Returns the number of channels in the set.
static const char *const bwavCodingHistory
Metadata property name used in BWAV chunks.
MemoryBlock getMemoryBlock() const
Returns a copy of the stream&#39;s data as a memory block.
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.
Very simple container class to hold a pointer to some data on the heap.
static const char *const acidBeats
Metadata property name used in acid chunks.
String formatted(const String &format) const
Converts this date/time to a string with a user-defined format.
Definition: juce_Time.cpp:326
static const char *const riffInfoSixthLanguage
Metadata property name used in INFO chunks.
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 riffInfoEndTimecode
Metadata property name used in INFO chunks.
String getValue(StringRef, const String &defaultReturnValue) const
Finds the value corresponding to a key string.
Array< ChannelType > getChannelTypes() const
Returns an array of all the types in this channel set.
int64 lengthInSamples
The total number of samples in the audio stream.
~WavAudioFormat() override
Destructor.
The JUCE String class!
Definition: juce_String.h:42
MemoryMappedAudioFormatReader * createMemoryMappedReader(const File &) override
Attempts to create a MemoryMappedAudioFormatReader, if possible for this format.
static String createStringFromData(const void *data, int size)
Creates a string from data in an unknown format.
static const char *const riffInfoMusicBy
Metadata property name used in INFO chunks.
void addChannel(ChannelType newChannelType)
Adds a channel to the set.
FileInputStream * createInputStream() const
Creates a stream to read from this file.
Definition: juce_File.cpp:709
static const char *const riffInfoVegasVersionMinor
Metadata property name used in INFO chunks.
static const char *const riffInfoRating
Metadata property name used in INFO chunks.
static const char *const riffInfoURL
Metadata property name used in INFO chunks.
static Time JUCE_CALLTYPE getCurrentTime() noexcept
Returns a Time object that is set to the current system time.
Definition: juce_Time.cpp:218
static const char *const riffInfoComment
Metadata property name used in INFO chunks.
const File & getFile() const noexcept
Returns the file that this stream is reading from.
AudioChannelSet getChannelLayout() override
Get the channel layout of the audio stream.
static const char *const riffInfoFourthLanguage
Metadata property name used in INFO chunks.
static const char *const riffInfoTrackNo
Metadata property name used in INFO chunks.
static const char *const riffInfoEighthLanguage
Metadata property name used in INFO chunks.
static const char *const riffInfoDateCreated
Metadata property name used in INFO chunks.
static const char *const riffInfoFirstLanguage
Metadata property name used in INFO chunks.
int size() const noexcept
Returns the number of strings in the array.
static const char *const riffInfoStarring_STAR
Metadata property name used in INFO chunks.
static const char *const riffInfoSubject
Metadata property name used in INFO chunks.
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.
virtual int64 getPosition()=0
Returns the offset of the next byte that will be read from the stream.
Array< int > getPossibleSampleRates() override
Returns a set of sample rates that the format can read and write.
static const char *const bwavDescription
Metadata property name used in BWAV chunks.
static const char *const riffInfoBaseURL
Metadata property name used in INFO chunks.
An arbitrarily large integer class.
static const char *const riffInfoDimension
Metadata property name used in INFO chunks.
void calloc(SizeType newNumElements, const size_t elementSize=sizeof(ElementType))
Allocates a specified amount of memory and clears it.
static const char *const riffInfoSource
Metadata property name used in INFO chunks.
Used by AudioFormatReader subclasses to copy data to different formats.
static const char *const riffInfoDotsPerInch
Metadata property name used in INFO chunks.
void getSample(int64 sample, float *result) const noexcept override
Returns the samples for all channels at a given sample position.
This is a base class for classes that perform a unit test.
Definition: juce_UnitTest.h:73
static AudioChannelSet JUCE_CALLTYPE mono()
Creates a one-channel mono set (centre).
static const char *const riffInfoLogoURL
Metadata property name used in INFO chunks.
String toString() const
Attempts to parse the contents of the block as a zero-terminated UTF8 string.
static AudioChannelSet JUCE_CALLTYPE create7point0SDDS()
Creates a set for a SDDS 7.0 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre).
static const char *const riffInfoStatistics
Metadata property name used in INFO chunks.
AcidChunk(InputStream &input, size_t length)
Reads an acid RIFF chunk from a stream positioned just after the size byte.
static const char *const riffInfoEditedBy
Metadata property name used in INFO chunks.
Reads and Writes WAV format audio files.
void * getData() const noexcept
Returns a void pointer to the data.
static const char *const riffInfoArchivalLocation
Metadata property name used in INFO chunks.
static const char *const riffInfoMedium
Metadata property name used in INFO chunks.
static const char *const riffInfoMoreInfoURL
Metadata property name used in INFO chunks.
bool isDiscreteLayout() const noexcept
Returns if this is a channel layout made-up of discrete channels.
int64 getSize() const
Returns the size of the file in bytes.
static const char *const riffInfoSoundSchemeTitle
Metadata property name used in INFO chunks.
static const char *const riffInfoGenre
Metadata property name used in INFO chunks.
bool writeRepeatedByte(uint8 byte, size_t numTimesToRepeat) override
Writes a byte to the output stream a given number of times.
bool replaceMetadataInFile(const File &wavFile, const StringPairArray &newMetadata)
Utility function to replace the metadata in a wav file with a new set of values.
static const char *const riffInfoDefaultAudioStream
Metadata property name used in INFO chunks.
bool write(const void *, size_t) override
Writes a block of data to the stream.
static const char *const riffInfoSourceFrom
Metadata property name used in INFO chunks.
virtual int readInt()
Reads four bytes from the stream as a little-endian 32-bit value.
static AudioChannelSet JUCE_CALLTYPE create5point1()
Creates a set for a 5.1 surround setup (left, right, centre, leftSurround, rightSurround, LFE).
static JUCE_CONSTEXPR uint16 swap(uint16 value) noexcept
Swaps the upper and lower bytes of a 16-bit integer.
static AudioChannelSet JUCE_CALLTYPE create5point0()
Creates a set for a 5.0 surround setup (left, right, centre, leftSurround, rightSurround).
static const char *const bwavOriginationTime
Metadata property name used in BWAV chunks.
static const char *const riffInfoProductionDesigner
Metadata property name used in INFO chunks.
Subclasses of AudioFormat are used to read and write different audio file formats.
virtual bool writeShort(short value)
Writes a 16-bit integer to the stream in a little-endian byte order.
static const char *const bwavOriginationDate
Metadata property name used in BWAV chunks.
static const char *const riffInfoMoreInfoText
Metadata property name used in INFO chunks.
static const char *const riffInfoKeywords
Metadata property name used in INFO chunks.
static const char *const riffInfoPart
Metadata property name used in INFO chunks.
static const char *const riffInfoDistributedBy
Metadata property name used in INFO chunks.
static JUCE_CONSTEXPR uint32 littleEndianInt(const void *bytes) noexcept
Turns 4 bytes into a little-endian integer.
static const char *const ISRC
Metadata property name used when reading an ISRC code from an AXML chunk.
static const char *const acidRootNote
Metadata property name used in acid chunks.
Represents a local file or directory.
Definition: juce_File.h:44
The base class for streams that write data to some kind of destination.
Holds a resizable array of primitive or copy-by-value objects.
Definition: juce_Array.h:59
static const char *const acidTempo
Metadata property name used in acid chunks.
static const char *const riffInfoCopyright
Metadata property name used in INFO chunks.
static const char *const riffInfoYear
Metadata property name used in INFO chunks.
static const char *const riffInfoLocation
Metadata property name used in INFO chunks.
bool canDoMono() override
Returns true if the format can do 1-channel audio.
static const char *const riffInfoTrackNumber
Metadata property name used in INFO chunks.
static const char *const riffInfoDirectory
Metadata property name used in INFO chunks.
Array< int > getPossibleBitDepths() override
Returns a set of bit depths that the format can read and write.
FileOutputStream * createOutputStream(size_t bufferSize=0x8000) const
Creates a stream to write to this file.
Definition: juce_File.cpp:719
static AudioChannelSet JUCE_CALLTYPE canonicalChannelSet(int numChannels)
Create a canonical channel set for a given number of channels.
size_t getDataSize() const noexcept
Returns the number of bytes of data that have been written to the stream.
static const char *const riffInfoLanguage
Metadata property name used in INFO chunks.
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.
An input stream that reads from a local file.
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.
static AudioChannelSet JUCE_CALLTYPE createLCR()
Creates a set containing an LCR set (left, right, centre).
static const char *const riffInfoVersion
Metadata property name used in INFO chunks.
Writes samples to an audio file stream.
Used by AudioFormatWriter subclasses to copy data to different formats.
static AudioChannelSet JUCE_CALLTYPE stereo()
Creates a set containing a stereo set (left, right).
static const char *const riffInfoSoftware
Metadata property name used in INFO chunks.
An output stream that writes into a local file.
static const char *const riffInfoRated
Metadata property name used in INFO chunks.
bool containsKey(StringRef key) const noexcept
Returns true if the given key exists.
static const char *const riffInfoSharpness
Metadata property name used in INFO chunks.
bool openedOk() const noexcept
Returns true if the stream opened without problems.
int getIntValue() const noexcept
Reads the value of the string as a decimal number (up to 32 bits in size).
static const char *const riffInfoWrittenBy
Metadata property name used in INFO chunks.
static const char *const acidOneShot
Metadata property name used in acid chunks.
static AudioChannelSet JUCE_CALLTYPE discreteChannels(int numChannels)
Creates a set of untyped discrete channels.
static const char *const riffInfoDateTimeOriginal
Metadata property name used in INFO chunks.
static const char *const riffInfoNinthLanguage
Metadata property name used in INFO chunks.
WavAudioFormat()
Creates a format object.
static const char *const riffInfoCostumeDesigner
Metadata property name used in INFO chunks.
virtual bool writeInt(int value)
Writes a 32-bit integer to the stream in a little-endian byte order.
A container for holding a set of strings which are keyed by another string.
void addArray(const StringPairArray &other)
Adds the items from another array to this one.
Reads samples from an audio file stream.
static const char *const riffInfoLogoIconURL
Metadata property name used in INFO chunks.
static AudioChannelSet JUCE_CALLTYPE quadraphonic()
Creates a set for quadraphonic surround setup (left, right, leftSurround, rightSurround) ...
static const char *const riffInfoEngineer
Metadata property name used in INFO chunks.
Writes data to an internal memory buffer, which grows as required.
virtual bool writeString(const String &text)
Stores a string in the stream in a binary format.
static const char *const riffInfoComments
Metadata property name used in INFO chunks.
static const char *const riffInfoArtist
Metadata property name used in INFO chunks.
static const char *const bwavOriginator
Metadata property name used in BWAV chunks.
virtual int read(void *destBuffer, int maxBytesToRead)=0
Reads some data from the stream into a memory buffer.
int findNextSetBit(int startIndex) const noexcept
Looks for the index of the next set bit after a given starting point.
static const char *const riffInfoTimeCode
Metadata property name used in INFO chunks.
static const char *const riffInfoLightness
Metadata property name used in INFO chunks.
static const char *const acidStretch
Metadata property name used in acid chunks.
size_t getNumBytesAsUTF8() const noexcept
Returns the number of bytes required to represent this string as UTF8.
A class to hold a resizable block of raw data.
virtual int64 getPosition()=0
Returns the stream&#39;s current position.
static String fromUTF8(const char *utf8buffer, int bufferSizeBytes=-1)
Creates a String from a UTF-8 encoded buffer.
static const char *const riffInfoThirdLanguage
Metadata property name used in INFO chunks.
static const char *const riffInfoTitle
Metadata property name used in INFO chunks.
Holds an absolute date and time.
Definition: juce_Time.h:40
static const char *const riffInfoSecondaryGenre
Metadata property name used in INFO chunks.
bool flush() override
Some formats may support a flush operation that makes sure the file is in a valid state before carryi...
A specialised type of AudioFormatReader that uses a MemoryMappedFile to read directly from an audio f...
static const char *const riffInfoProducedBy
Metadata property name used in INFO chunks.
void fillWith(uint8 valueToUse) noexcept
Fills the entire memory block with a repeated byte value.
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 riffInfoCinematographer
Metadata property name used in INFO chunks.
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 acidizerFlag
Metadata property name used in acid chunks.
static const char *const acidRootSet
Metadata property name used in acid chunks.
static const char *const riffInfoCommissioned
Metadata property name used in INFO chunks.
Allows a block of data to be accessed as a stream.
void setSize(const size_t newSize, bool initialiseNewSpaceToZero=false)
Resizes the memory block.
static const char *const riffInfoCropped
Metadata property name used in INFO chunks.
static const char *const riffInfoEncodedBy
Metadata property name used in INFO chunks.
static const char *const bwavTimeReference
Metadata property name used in BWAV chunks.
void clear() noexcept
Clears all the samples in all channels.
static const char *const riffInfoRippedBy
Metadata property name used in INFO chunks.