OpenShot Audio Library | OpenShotAudio  0.3.0
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));
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  //==============================================================================
576  namespace ListInfoChunk
577  {
578  static const char* const types[] =
579  {
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  {
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 
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  //==============================================================================
828  struct TracktionChunk
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  //==============================================================================
901  struct ExtensibleWavSubFormat
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 //==============================================================================
934 class WavAudioFormatReader : public AudioFormatReader
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 = (int) (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)
1056  {
1057  if (dataLength > 0)
1058  chunkEnd = input->getPosition() + dataLength + (dataLength & 1);
1059  }
1060  else
1061  {
1062  dataLength = length;
1063  }
1064 
1065  dataChunkStart = input->getPosition();
1066  lengthInSamples = (bytesPerFrame > 0) ? (dataLength / bytesPerFrame) : 0;
1067  }
1068  else if (chunkType == chunkName ("bext"))
1069  {
1070  bwavChunkStart = input->getPosition();
1071  bwavSize = length;
1072 
1073  HeapBlock<BWAVChunk> bwav;
1074  bwav.calloc (jmax ((size_t) length + 1, sizeof (BWAVChunk)), 1);
1075  input->read (bwav, (int) length);
1076  bwav->copyTo (metadataValues, (int) length);
1077  }
1078  else if (chunkType == chunkName ("smpl"))
1079  {
1080  HeapBlock<SMPLChunk> smpl;
1081  smpl.calloc (jmax ((size_t) length + 1, sizeof (SMPLChunk)), 1);
1082  input->read (smpl, (int) length);
1083  smpl->copyTo (metadataValues, (int) length);
1084  }
1085  else if (chunkType == chunkName ("inst") || chunkType == chunkName ("INST")) // need to check which...
1086  {
1087  HeapBlock<InstChunk> inst;
1088  inst.calloc (jmax ((size_t) length + 1, sizeof (InstChunk)), 1);
1089  input->read (inst, (int) length);
1090  inst->copyTo (metadataValues);
1091  }
1092  else if (chunkType == chunkName ("cue "))
1093  {
1094  HeapBlock<CueChunk> cue;
1095  cue.calloc (jmax ((size_t) length + 1, sizeof (CueChunk)), 1);
1096  input->read (cue, (int) length);
1097  cue->copyTo (metadataValues, (int) length);
1098  }
1099  else if (chunkType == chunkName ("axml"))
1100  {
1101  MemoryBlock axml;
1102  input->readIntoMemoryBlock (axml, (ssize_t) length);
1103  AXMLChunk::addToMetadata (metadataValues, axml.toString());
1104  }
1105  else if (chunkType == chunkName ("LIST"))
1106  {
1107  auto subChunkType = input->readInt();
1108 
1109  if (subChunkType == chunkName ("info") || subChunkType == chunkName ("INFO"))
1110  {
1111  ListInfoChunk::addToMetadata (metadataValues, *input, chunkEnd);
1112  }
1113  else if (subChunkType == chunkName ("adtl"))
1114  {
1115  while (input->getPosition() < chunkEnd)
1116  {
1117  auto adtlChunkType = input->readInt();
1118  auto adtlLength = (uint32) input->readInt();
1119  auto adtlChunkEnd = input->getPosition() + (adtlLength + (adtlLength & 1));
1120 
1121  if (adtlChunkType == chunkName ("labl") || adtlChunkType == chunkName ("note"))
1122  {
1123  String prefix;
1124 
1125  if (adtlChunkType == chunkName ("labl"))
1126  prefix << "CueLabel" << cueLabelIndex++;
1127  else if (adtlChunkType == chunkName ("note"))
1128  prefix << "CueNote" << cueNoteIndex++;
1129 
1130  auto identifier = (uint32) input->readInt();
1131  auto stringLength = (int) adtlLength - 4;
1132 
1133  MemoryBlock textBlock;
1134  input->readIntoMemoryBlock (textBlock, stringLength);
1135 
1136  metadataValues.set (prefix + "Identifier", String (identifier));
1137  metadataValues.set (prefix + "Text", textBlock.toString());
1138  }
1139  else if (adtlChunkType == chunkName ("ltxt"))
1140  {
1141  auto prefix = "CueRegion" + String (cueRegionIndex++);
1142  auto identifier = (uint32) input->readInt();
1143  auto sampleLength = (uint32) input->readInt();
1144  auto purpose = (uint32) input->readInt();
1145  auto country = (uint16) input->readShort();
1146  auto language = (uint16) input->readShort();
1147  auto dialect = (uint16) input->readShort();
1148  auto codePage = (uint16) input->readShort();
1149  auto stringLength = adtlLength - 20;
1150 
1151  MemoryBlock textBlock;
1152  input->readIntoMemoryBlock (textBlock, (int) stringLength);
1153 
1154  metadataValues.set (prefix + "Identifier", String (identifier));
1155  metadataValues.set (prefix + "SampleLength", String (sampleLength));
1156  metadataValues.set (prefix + "Purpose", String (purpose));
1157  metadataValues.set (prefix + "Country", String (country));
1158  metadataValues.set (prefix + "Language", String (language));
1159  metadataValues.set (prefix + "Dialect", String (dialect));
1160  metadataValues.set (prefix + "CodePage", String (codePage));
1161  metadataValues.set (prefix + "Text", textBlock.toString());
1162  }
1163 
1164  input->setPosition (adtlChunkEnd);
1165  }
1166  }
1167  }
1168  else if (chunkType == chunkName ("acid"))
1169  {
1170  AcidChunk (*input, length).addToMetadata (metadataValues);
1171  }
1172  else if (chunkType == chunkName ("Trkn"))
1173  {
1174  MemoryBlock tracktion;
1175  input->readIntoMemoryBlock (tracktion, (ssize_t) length);
1176  metadataValues.set (WavAudioFormat::tracktionLoopInfo, tracktion.toString());
1177  }
1178  else if (chunkEnd <= input->getPosition())
1179  {
1180  break;
1181  }
1182 
1183  input->setPosition (chunkEnd);
1184  }
1185  }
1186 
1187  if (cueLabelIndex > 0) metadataValues.set ("NumCueLabels", String (cueLabelIndex));
1188  if (cueNoteIndex > 0) metadataValues.set ("NumCueNotes", String (cueNoteIndex));
1189  if (cueRegionIndex > 0) metadataValues.set ("NumCueRegions", String (cueRegionIndex));
1190  if (metadataValues.size() > 0) metadataValues.set ("MetaDataSource", "WAV");
1191  }
1192 
1193  //==============================================================================
1194  bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
1195  int64 startSampleInFile, int numSamples) override
1196  {
1197  clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
1198  startSampleInFile, numSamples, lengthInSamples);
1199 
1200  if (numSamples <= 0)
1201  return true;
1202 
1203  input->setPosition (dataChunkStart + startSampleInFile * bytesPerFrame);
1204 
1205  while (numSamples > 0)
1206  {
1207  const int tempBufSize = 480 * 3 * 4; // (keep this a multiple of 3)
1208  char tempBuffer[tempBufSize];
1209 
1210  auto numThisTime = jmin (tempBufSize / bytesPerFrame, numSamples);
1211  auto bytesRead = input->read (tempBuffer, numThisTime * bytesPerFrame);
1212 
1213  if (bytesRead < numThisTime * bytesPerFrame)
1214  {
1215  jassert (bytesRead >= 0);
1216  zeromem (tempBuffer + bytesRead, (size_t) (numThisTime * bytesPerFrame - bytesRead));
1217  }
1218 
1219  copySampleData (bitsPerSample, usesFloatingPointData,
1220  destSamples, startOffsetInDestBuffer, numDestChannels,
1221  tempBuffer, (int) numChannels, numThisTime);
1222 
1223  startOffsetInDestBuffer += numThisTime;
1224  numSamples -= numThisTime;
1225  }
1226 
1227  return true;
1228  }
1229 
1230  static void copySampleData (unsigned int numBitsPerSample, const bool floatingPointData,
1231  int* const* destSamples, int startOffsetInDestBuffer, int numDestChannels,
1232  const void* sourceData, int numberOfChannels, int numSamples) noexcept
1233  {
1234  switch (numBitsPerSample)
1235  {
1236  case 8: ReadHelper<AudioData::Int32, AudioData::UInt8, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); break;
1237  case 16: ReadHelper<AudioData::Int32, AudioData::Int16, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); break;
1238  case 24: ReadHelper<AudioData::Int32, AudioData::Int24, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); break;
1239  case 32: if (floatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
1240  else ReadHelper<AudioData::Int32, AudioData::Int32, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
1241  break;
1242  default: jassertfalse; break;
1243  }
1244  }
1245 
1246  //==============================================================================
1247  AudioChannelSet getChannelLayout() override
1248  {
1249  if (channelLayout.size() == static_cast<int> (numChannels))
1250  return channelLayout;
1251 
1252  return WavFileHelpers::canonicalWavChannelSet (static_cast<int> (numChannels));
1253  }
1254 
1255  static AudioChannelSet getChannelLayoutFromMask (int dwChannelMask, size_t totalNumChannels)
1256  {
1257  AudioChannelSet wavFileChannelLayout;
1258 
1259  // AudioChannelSet and wav's dwChannelMask are compatible
1260  BigInteger channelBits (dwChannelMask);
1261 
1262  for (auto bit = channelBits.findNextSetBit (0); bit >= 0; bit = channelBits.findNextSetBit (bit + 1))
1263  wavFileChannelLayout.addChannel (static_cast<AudioChannelSet::ChannelType> (bit + 1));
1264 
1265  // channel layout and number of channels do not match
1266  if (wavFileChannelLayout.size() != static_cast<int> (totalNumChannels))
1267  {
1268  // for backward compatibility with old wav files, assume 1 or 2
1269  // channel wav files are mono/stereo respectively
1270  if (totalNumChannels <= 2 && dwChannelMask == 0)
1271  wavFileChannelLayout = AudioChannelSet::canonicalChannelSet (static_cast<int> (totalNumChannels));
1272  else
1273  {
1274  auto discreteSpeaker = static_cast<int> (AudioChannelSet::discreteChannel0);
1275 
1276  while (wavFileChannelLayout.size() < static_cast<int> (totalNumChannels))
1277  wavFileChannelLayout.addChannel (static_cast<AudioChannelSet::ChannelType> (discreteSpeaker++));
1278  }
1279  }
1280 
1281  return wavFileChannelLayout;
1282  }
1283 
1284  int64 bwavChunkStart = 0, bwavSize = 0;
1285  int64 dataChunkStart = 0, dataLength = 0;
1286  int bytesPerFrame = 0;
1287  bool isRF64 = false;
1288  bool isSubformatOggVorbis = false;
1289 
1290  AudioChannelSet channelLayout;
1291 
1292 private:
1293  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatReader)
1294 };
1295 
1296 //==============================================================================
1297 class WavAudioFormatWriter : public AudioFormatWriter
1298 {
1299 public:
1300  WavAudioFormatWriter (OutputStream* const out, const double rate,
1301  const AudioChannelSet& channelLayoutToUse, const unsigned int bits,
1302  const StringPairArray& metadataValues)
1303  : AudioFormatWriter (out, wavFormatName, rate, channelLayoutToUse, bits)
1304  {
1305  using namespace WavFileHelpers;
1306 
1307  if (metadataValues.size() > 0)
1308  {
1309  // The meta data should have been sanitised for the WAV format.
1310  // If it was originally sourced from an AIFF file the MetaDataSource
1311  // key should be removed (or set to "WAV") once this has been done
1312  jassert (metadataValues.getValue ("MetaDataSource", "None") != "AIFF");
1313 
1314  bwavChunk = BWAVChunk::createFrom (metadataValues);
1315  axmlChunk = AXMLChunk::createFrom (metadataValues);
1316  smplChunk = SMPLChunk::createFrom (metadataValues);
1317  instChunk = InstChunk::createFrom (metadataValues);
1318  cueChunk = CueChunk ::createFrom (metadataValues);
1319  listChunk = ListChunk::createFrom (metadataValues);
1320  listInfoChunk = ListInfoChunk::createFrom (metadataValues);
1321  acidChunk = AcidChunk::createFrom (metadataValues);
1322  trckChunk = TracktionChunk::createFrom (metadataValues);
1323  }
1324 
1325  headerPosition = out->getPosition();
1326  writeHeader();
1327  }
1328 
1329  ~WavAudioFormatWriter() override
1330  {
1331  writeHeader();
1332  }
1333 
1334  //==============================================================================
1335  bool write (const int** data, int numSamples) override
1336  {
1337  jassert (numSamples >= 0);
1338  jassert (data != nullptr && *data != nullptr); // the input must contain at least one channel!
1339 
1340  if (writeFailed)
1341  return false;
1342 
1343  auto bytes = numChannels * (size_t) numSamples * bitsPerSample / 8;
1344  tempBlock.ensureSize (bytes, false);
1345 
1346  switch (bitsPerSample)
1347  {
1348  case 8: WriteHelper<AudioData::UInt8, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break;
1349  case 16: WriteHelper<AudioData::Int16, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break;
1350  case 24: WriteHelper<AudioData::Int24, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break;
1351  case 32: WriteHelper<AudioData::Int32, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break;
1352  default: jassertfalse; break;
1353  }
1354 
1355  if (! output->write (tempBlock.getData(), bytes))
1356  {
1357  // failed to write to disk, so let's try writing the header.
1358  // If it's just run out of disk space, then if it does manage
1359  // to write the header, we'll still have a usable file..
1360  writeHeader();
1361  writeFailed = true;
1362  return false;
1363  }
1364 
1365  bytesWritten += bytes;
1366  lengthInSamples += (uint64) numSamples;
1367  return true;
1368  }
1369 
1370  bool flush() override
1371  {
1372  auto lastWritePos = output->getPosition();
1373  writeHeader();
1374 
1375  if (output->setPosition (lastWritePos))
1376  return true;
1377 
1378  // if this fails, you've given it an output stream that can't seek! It needs
1379  // to be able to seek back to write the header
1380  jassertfalse;
1381  return false;
1382  }
1383 
1384 private:
1385  MemoryBlock tempBlock, bwavChunk, axmlChunk, smplChunk, instChunk, cueChunk, listChunk, listInfoChunk, acidChunk, trckChunk;
1386  uint64 lengthInSamples = 0, bytesWritten = 0;
1387  int64 headerPosition = 0;
1388  bool writeFailed = false;
1389 
1390  void writeHeader()
1391  {
1392  if ((bytesWritten & 1) != 0) // pad to an even length
1393  output->writeByte (0);
1394 
1395  using namespace WavFileHelpers;
1396 
1397  if (headerPosition != output->getPosition() && ! output->setPosition (headerPosition))
1398  {
1399  // if this fails, you've given it an output stream that can't seek! It needs to be
1400  // able to seek back to go back and write the header after the data has been written.
1401  jassertfalse;
1402  return;
1403  }
1404 
1405  const size_t bytesPerFrame = numChannels * bitsPerSample / 8;
1406  uint64 audioDataSize = bytesPerFrame * lengthInSamples;
1407  auto channelMask = getChannelMaskFromChannelLayout (channelLayout);
1408 
1409  const bool isRF64 = (bytesWritten >= 0x100000000LL);
1410  const bool isWaveFmtEx = isRF64 || (channelMask != 0);
1411 
1412  int64 riffChunkSize = (int64) (4 /* 'RIFF' */ + 8 + 40 /* WAVEFORMATEX */
1413  + 8 + audioDataSize + (audioDataSize & 1)
1414  + chunkSize (bwavChunk)
1415  + chunkSize (axmlChunk)
1416  + chunkSize (smplChunk)
1417  + chunkSize (instChunk)
1418  + chunkSize (cueChunk)
1419  + chunkSize (listChunk)
1420  + chunkSize (listInfoChunk)
1421  + chunkSize (acidChunk)
1422  + chunkSize (trckChunk)
1423  + (8 + 28)); // (ds64 chunk)
1424 
1425  riffChunkSize += (riffChunkSize & 1);
1426 
1427  if (isRF64)
1428  writeChunkHeader (chunkName ("RF64"), -1);
1429  else
1430  writeChunkHeader (chunkName ("RIFF"), (int) riffChunkSize);
1431 
1432  output->writeInt (chunkName ("WAVE"));
1433 
1434  if (! isRF64)
1435  {
1436  #if ! JUCE_WAV_DO_NOT_PAD_HEADER_SIZE
1437  /* NB: This junk chunk is added for padding, so that the header is a fixed size
1438  regardless of whether it's RF64 or not. That way, we can begin recording a file,
1439  and when it's finished, can go back and write either a RIFF or RF64 header,
1440  depending on whether more than 2^32 samples were written.
1441 
1442  The JUCE_WAV_DO_NOT_PAD_HEADER_SIZE macro allows you to disable this feature in case
1443  you need to create files for crappy WAV players with bugs that stop them skipping chunks
1444  which they don't recognise. But DO NOT USE THIS option unless you really have no choice,
1445  because it means that if you write more than 2^32 samples to the file, you'll corrupt it.
1446  */
1447  writeChunkHeader (chunkName ("JUNK"), 28 + (isWaveFmtEx? 0 : 24));
1448  output->writeRepeatedByte (0, 28 /* ds64 */ + (isWaveFmtEx? 0 : 24));
1449  #endif
1450  }
1451  else
1452  {
1453  #if JUCE_WAV_DO_NOT_PAD_HEADER_SIZE
1454  // If you disable padding, then you MUST NOT write more than 2^32 samples to a file.
1455  jassertfalse;
1456  #endif
1457 
1458  writeChunkHeader (chunkName ("ds64"), 28); // chunk size for uncompressed data (no table)
1459  output->writeInt64 (riffChunkSize);
1460  output->writeInt64 ((int64) audioDataSize);
1461  output->writeRepeatedByte (0, 12);
1462  }
1463 
1464  if (isWaveFmtEx)
1465  {
1466  writeChunkHeader (chunkName ("fmt "), 40);
1467  output->writeShort ((short) (uint16) 0xfffe); // WAVE_FORMAT_EXTENSIBLE
1468  }
1469  else
1470  {
1471  writeChunkHeader (chunkName ("fmt "), 16);
1472  output->writeShort (bitsPerSample < 32 ? (short) 1 /*WAVE_FORMAT_PCM*/
1473  : (short) 3 /*WAVE_FORMAT_IEEE_FLOAT*/);
1474  }
1475 
1476  output->writeShort ((short) numChannels);
1477  output->writeInt ((int) sampleRate);
1478  output->writeInt ((int) (bytesPerFrame * sampleRate)); // nAvgBytesPerSec
1479  output->writeShort ((short) bytesPerFrame); // nBlockAlign
1480  output->writeShort ((short) bitsPerSample); // wBitsPerSample
1481 
1482  if (isWaveFmtEx)
1483  {
1484  output->writeShort (22); // cbSize (size of the extension)
1485  output->writeShort ((short) bitsPerSample); // wValidBitsPerSample
1486  output->writeInt (channelMask);
1487 
1488  const ExtensibleWavSubFormat& subFormat = bitsPerSample < 32 ? pcmFormat : IEEEFloatFormat;
1489 
1490  output->writeInt ((int) subFormat.data1);
1491  output->writeShort ((short) subFormat.data2);
1492  output->writeShort ((short) subFormat.data3);
1493  output->write (subFormat.data4, sizeof (subFormat.data4));
1494  }
1495 
1496  writeChunk (bwavChunk, chunkName ("bext"));
1497  writeChunk (axmlChunk, chunkName ("axml"));
1498  writeChunk (smplChunk, chunkName ("smpl"));
1499  writeChunk (instChunk, chunkName ("inst"), 7);
1500  writeChunk (cueChunk, chunkName ("cue "));
1501  writeChunk (listChunk, chunkName ("LIST"));
1502  writeChunk (listInfoChunk, chunkName ("LIST"));
1503  writeChunk (acidChunk, chunkName ("acid"));
1504  writeChunk (trckChunk, chunkName ("Trkn"));
1505 
1506  writeChunkHeader (chunkName ("data"), isRF64 ? -1 : (int) (lengthInSamples * bytesPerFrame));
1507 
1508  usesFloatingPointData = (bitsPerSample == 32);
1509  }
1510 
1511  static size_t chunkSize (const MemoryBlock& data) noexcept { return data.getSize() > 0 ? (8 + data.getSize()) : 0; }
1512 
1513  void writeChunkHeader (int chunkType, int size) const
1514  {
1515  output->writeInt (chunkType);
1516  output->writeInt (size);
1517  }
1518 
1519  void writeChunk (const MemoryBlock& data, int chunkType, int size = 0) const
1520  {
1521  if (data.getSize() > 0)
1522  {
1523  writeChunkHeader (chunkType, size != 0 ? size : (int) data.getSize());
1524  *output << data;
1525  }
1526  }
1527 
1528  static int getChannelMaskFromChannelLayout (const AudioChannelSet& layout)
1529  {
1530  if (layout.isDiscreteLayout())
1531  return 0;
1532 
1533  // Don't add an extended format chunk for mono and stereo. Basically, all wav players
1534  // interpret a wav file with only one or two channels to be mono or stereo anyway.
1535  if (layout == AudioChannelSet::mono() || layout == AudioChannelSet::stereo())
1536  return 0;
1537 
1538  auto channels = layout.getChannelTypes();
1539  auto wavChannelMask = 0;
1540 
1541  for (auto channel : channels)
1542  {
1543  int wavChannelBit = static_cast<int> (channel) - 1;
1544  jassert (wavChannelBit >= 0 && wavChannelBit <= 31);
1545 
1546  wavChannelMask |= (1 << wavChannelBit);
1547  }
1548 
1549  return wavChannelMask;
1550  }
1551 
1552  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatWriter)
1553 };
1554 
1555 //==============================================================================
1556 class MemoryMappedWavReader : public MemoryMappedAudioFormatReader
1557 {
1558 public:
1559  MemoryMappedWavReader (const File& wavFile, const WavAudioFormatReader& reader)
1560  : MemoryMappedAudioFormatReader (wavFile, reader, reader.dataChunkStart,
1561  reader.dataLength, reader.bytesPerFrame)
1562  {
1563  }
1564 
1565  bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
1566  int64 startSampleInFile, int numSamples) override
1567  {
1568  clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
1569  startSampleInFile, numSamples, lengthInSamples);
1570 
1571  if (map == nullptr || ! mappedSection.contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
1572  {
1573  jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read.
1574  return false;
1575  }
1576 
1577  WavAudioFormatReader::copySampleData (bitsPerSample, usesFloatingPointData,
1578  destSamples, startOffsetInDestBuffer, numDestChannels,
1579  sampleToPointer (startSampleInFile), (int) numChannels, numSamples);
1580  return true;
1581  }
1582 
1583  void getSample (int64 sample, float* result) const noexcept override
1584  {
1585  auto num = (int) numChannels;
1586 
1587  if (map == nullptr || ! mappedSection.contains (sample))
1588  {
1589  jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read.
1590 
1591  zeromem (result, (size_t) num * sizeof (float));
1592  return;
1593  }
1594 
1595  auto dest = &result;
1596  auto source = sampleToPointer (sample);
1597 
1598  switch (bitsPerSample)
1599  {
1600  case 8: ReadHelper<AudioData::Float32, AudioData::UInt8, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num); break;
1601  case 16: ReadHelper<AudioData::Float32, AudioData::Int16, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num); break;
1602  case 24: ReadHelper<AudioData::Float32, AudioData::Int24, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num); break;
1603  case 32: if (usesFloatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
1604  else ReadHelper<AudioData::Float32, AudioData::Int32, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
1605  break;
1606  default: jassertfalse; break;
1607  }
1608  }
1609 
1610  void readMaxLevels (int64 startSampleInFile, int64 numSamples, Range<float>* results, int numChannelsToRead) override
1611  {
1612  numSamples = jmin (numSamples, lengthInSamples - startSampleInFile);
1613 
1614  if (map == nullptr || numSamples <= 0 || ! mappedSection.contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
1615  {
1616  jassert (numSamples <= 0); // you must make sure that the window contains all the samples you're going to attempt to read.
1617 
1618  for (int i = 0; i < numChannelsToRead; ++i)
1619  results[i] = {};
1620 
1621  return;
1622  }
1623 
1624  switch (bitsPerSample)
1625  {
1626  case 8: scanMinAndMax<AudioData::UInt8> (startSampleInFile, numSamples, results, numChannelsToRead); break;
1627  case 16: scanMinAndMax<AudioData::Int16> (startSampleInFile, numSamples, results, numChannelsToRead); break;
1628  case 24: scanMinAndMax<AudioData::Int24> (startSampleInFile, numSamples, results, numChannelsToRead); break;
1629  case 32: if (usesFloatingPointData) scanMinAndMax<AudioData::Float32> (startSampleInFile, numSamples, results, numChannelsToRead);
1630  else scanMinAndMax<AudioData::Int32> (startSampleInFile, numSamples, results, numChannelsToRead);
1631  break;
1632  default: jassertfalse; break;
1633  }
1634  }
1635 
1637 
1638 private:
1639  template <typename SampleType>
1640  void scanMinAndMax (int64 startSampleInFile, int64 numSamples, Range<float>* results, int numChannelsToRead) const noexcept
1641  {
1642  for (int i = 0; i < numChannelsToRead; ++i)
1643  results[i] = scanMinAndMaxInterleaved<SampleType, AudioData::LittleEndian> (i, startSampleInFile, numSamples);
1644  }
1645 
1646  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedWavReader)
1647 };
1648 
1649 //==============================================================================
1650 WavAudioFormat::WavAudioFormat() : AudioFormat (wavFormatName, ".wav .bwf") {}
1652 
1654 {
1655  return { 8000, 11025, 12000, 16000, 22050, 32000, 44100,
1656  48000, 88200, 96000, 176400, 192000, 352800, 384000 };
1657 }
1658 
1660 {
1661  return { 8, 16, 24, 32 };
1662 }
1663 
1664 bool WavAudioFormat::canDoStereo() { return true; }
1665 bool WavAudioFormat::canDoMono() { return true; }
1666 
1668 {
1669  auto channelTypes = channelSet.getChannelTypes();
1670 
1671  // When
1672  if (channelSet.isDiscreteLayout())
1673  return true;
1674 
1675  // WAV supports all channel types from left ... topRearRight
1676  for (auto channel : channelTypes)
1677  if (channel < AudioChannelSet::left || channel > AudioChannelSet::topRearRight)
1678  return false;
1679 
1680  return true;
1681 }
1682 
1683 AudioFormatReader* WavAudioFormat::createReaderFor (InputStream* sourceStream, bool deleteStreamIfOpeningFails)
1684 {
1685  std::unique_ptr<WavAudioFormatReader> r (new WavAudioFormatReader (sourceStream));
1686 
1687  #if JUCE_USE_OGGVORBIS
1688  if (r->isSubformatOggVorbis)
1689  {
1690  r->input = nullptr;
1691  return OggVorbisAudioFormat().createReaderFor (sourceStream, deleteStreamIfOpeningFails);
1692  }
1693  #endif
1694 
1695  if (r->sampleRate > 0 && r->numChannels > 0 && r->bytesPerFrame > 0 && r->bitsPerSample <= 32)
1696  return r.release();
1697 
1698  if (! deleteStreamIfOpeningFails)
1699  r->input = nullptr;
1700 
1701  return nullptr;
1702 }
1703 
1705 {
1707 }
1708 
1710 {
1711  if (fin != nullptr)
1712  {
1713  WavAudioFormatReader reader (fin);
1714 
1715  if (reader.lengthInSamples > 0)
1716  return new MemoryMappedWavReader (fin->getFile(), reader);
1717  }
1718 
1719  return nullptr;
1720 }
1721 
1723  unsigned int numChannels, int bitsPerSample,
1724  const StringPairArray& metadataValues, int qualityOptionIndex)
1725 {
1726  return createWriterFor (out, sampleRate, WavFileHelpers::canonicalWavChannelSet (static_cast<int> (numChannels)),
1727  bitsPerSample, metadataValues, qualityOptionIndex);
1728 }
1729 
1731  double sampleRate,
1732  const AudioChannelSet& channelLayout,
1733  int bitsPerSample,
1734  const StringPairArray& metadataValues,
1735  int /*qualityOptionIndex*/)
1736 {
1737  if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample) && isChannelLayoutSupported (channelLayout))
1738  return new WavAudioFormatWriter (out, sampleRate, channelLayout,
1739  (unsigned int) bitsPerSample, metadataValues);
1740 
1741  return nullptr;
1742 }
1743 
1744 namespace WavFileHelpers
1745 {
1746  static bool slowCopyWavFileWithNewMetadata (const File& file, const StringPairArray& metadata)
1747  {
1748  TemporaryFile tempFile (file);
1749  WavAudioFormat wav;
1750 
1751  std::unique_ptr<AudioFormatReader> reader (wav.createReaderFor (file.createInputStream(), true));
1752 
1753  if (reader != nullptr)
1754  {
1755  std::unique_ptr<OutputStream> outStream (tempFile.getFile().createOutputStream());
1756 
1757  if (outStream != nullptr)
1758  {
1759  std::unique_ptr<AudioFormatWriter> writer (wav.createWriterFor (outStream.get(), reader->sampleRate,
1760  reader->numChannels, (int) reader->bitsPerSample,
1761  metadata, 0));
1762 
1763  if (writer != nullptr)
1764  {
1765  outStream.release();
1766 
1767  bool ok = writer->writeFromAudioReader (*reader, 0, -1);
1768  writer.reset();
1769  reader.reset();
1770 
1771  return ok && tempFile.overwriteTargetFileWithTemporary();
1772  }
1773  }
1774  }
1775 
1776  return false;
1777  }
1778 }
1779 
1780 bool WavAudioFormat::replaceMetadataInFile (const File& wavFile, const StringPairArray& newMetadata)
1781 {
1782  using namespace WavFileHelpers;
1783 
1784  std::unique_ptr<WavAudioFormatReader> reader (static_cast<WavAudioFormatReader*> (createReaderFor (wavFile.createInputStream(), true)));
1785 
1786  if (reader != nullptr)
1787  {
1788  auto bwavPos = reader->bwavChunkStart;
1789  auto bwavSize = reader->bwavSize;
1790  reader.reset();
1791 
1792  if (bwavSize > 0)
1793  {
1794  auto chunk = BWAVChunk::createFrom (newMetadata);
1795 
1796  if (chunk.getSize() <= (size_t) bwavSize)
1797  {
1798  // the new one will fit in the space available, so write it directly..
1799  auto oldSize = wavFile.getSize();
1800 
1801  {
1802  FileOutputStream out (wavFile);
1803 
1804  if (out.openedOk())
1805  {
1806  out.setPosition (bwavPos);
1807  out << chunk;
1808  out.setPosition (oldSize);
1809  }
1810  }
1811 
1812  jassert (wavFile.getSize() == oldSize);
1813  return true;
1814  }
1815  }
1816  }
1817 
1818  return slowCopyWavFileWithNewMetadata (wavFile, newMetadata);
1819 }
1820 
1821 
1822 //==============================================================================
1823 //==============================================================================
1824 #if JUCE_UNIT_TESTS
1825 
1826 struct WaveAudioFormatTests : public UnitTest
1827 {
1828  WaveAudioFormatTests()
1829  : UnitTest ("Wave audio format tests", UnitTestCategories::audio)
1830  {}
1831 
1832  void runTest() override
1833  {
1834  beginTest ("Setting up metadata");
1835 
1836  StringPairArray metadataValues = WavAudioFormat::createBWAVMetadata ("description",
1837  "originator",
1838  "originatorRef",
1840  numTestAudioBufferSamples,
1841  "codingHistory");
1842 
1843  for (int i = numElementsInArray (WavFileHelpers::ListInfoChunk::types); --i >= 0;)
1844  metadataValues.set (WavFileHelpers::ListInfoChunk::types[i],
1845  WavFileHelpers::ListInfoChunk::types[i]);
1846 
1847  if (metadataValues.size() > 0)
1848  metadataValues.set ("MetaDataSource", "WAV");
1849 
1850  metadataValues.addArray (createDefaultSMPLMetadata());
1851 
1852  WavAudioFormat format;
1853  MemoryBlock memoryBlock;
1854 
1855  {
1856  beginTest ("Creating a basic wave writer");
1857 
1858  std::unique_ptr<AudioFormatWriter> writer (format.createWriterFor (new MemoryOutputStream (memoryBlock, false),
1859  44100.0, numTestAudioBufferChannels,
1860  32, metadataValues, 0));
1861  expect (writer != nullptr);
1862 
1863  AudioBuffer<float> buffer (numTestAudioBufferChannels, numTestAudioBufferSamples);
1864  buffer.clear();
1865 
1866  beginTest ("Writing audio data to the basic wave writer");
1867  expect (writer->writeFromAudioSampleBuffer (buffer, 0, numTestAudioBufferSamples));
1868  }
1869 
1870  {
1871  beginTest ("Creating a basic wave reader");
1872 
1873  std::unique_ptr<AudioFormatReader> reader (format.createReaderFor (new MemoryInputStream (memoryBlock, false), false));
1874  expect (reader != nullptr);
1875  expect (reader->metadataValues == metadataValues, "Somehow, the metadata is different!");
1876  }
1877  }
1878 
1879 private:
1880  enum
1881  {
1882  numTestAudioBufferChannels = 2,
1883  numTestAudioBufferSamples = 256
1884  };
1885 
1886  StringPairArray createDefaultSMPLMetadata() const
1887  {
1888  StringPairArray m;
1889 
1890  m.set ("Manufacturer", "0");
1891  m.set ("Product", "0");
1892  m.set ("SamplePeriod", "0");
1893  m.set ("MidiUnityNote", "60");
1894  m.set ("MidiPitchFraction", "0");
1895  m.set ("SmpteFormat", "0");
1896  m.set ("SmpteOffset", "0");
1897  m.set ("NumSampleLoops", "0");
1898  m.set ("SamplerData", "0");
1899 
1900  return m;
1901  }
1902 
1903  JUCE_DECLARE_NON_COPYABLE (WaveAudioFormatTests)
1904 };
1905 
1906 static const WaveAudioFormatTests waveAudioFormatTests;
1907 
1908 #endif
1909 
1910 } // namespace juce
static AudioChannelSet JUCE_CALLTYPE quadraphonic()
static AudioChannelSet JUCE_CALLTYPE create5point0()
bool isDiscreteLayout() const noexcept
static AudioChannelSet JUCE_CALLTYPE mono()
static AudioChannelSet JUCE_CALLTYPE stereo()
static AudioChannelSet JUCE_CALLTYPE create5point1()
static AudioChannelSet JUCE_CALLTYPE create7point0SDDS()
static AudioChannelSet JUCE_CALLTYPE create7point1SDDS()
static AudioChannelSet JUCE_CALLTYPE canonicalChannelSet(int numChannels)
static AudioChannelSet JUCE_CALLTYPE discreteChannels(int numChannels)
static AudioChannelSet JUCE_CALLTYPE createLCR()
Array< ChannelType > getChannelTypes() const
static void clearSamplesBeyondAvailableLength(int **destChannels, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int &numSamples, int64 fileLengthInSamples)
AudioFormatReader(InputStream *sourceStream, const String &formatName)
virtual void readMaxLevels(int64 startSample, int64 numSamples, Range< float > *results, int numChannelsToRead)
static JUCE_CONSTEXPR uint16 swap(uint16 value) noexcept
static Type swapIfBigEndian(Type value) noexcept
static JUCE_CONSTEXPR uint32 littleEndianInt(const void *bytes) noexcept
static juce_wchar toUpperCase(juce_wchar character) noexcept
const File & getFile() const noexcept
bool setPosition(int64) override
bool openedOk() const noexcept
int64 getSize() const
FileInputStream * createInputStream() const
Definition: juce_File.cpp:729
virtual int64 getPosition()=0
virtual int64 readInt64()
virtual bool setPosition(int64 newPosition)=0
virtual bool isExhausted()=0
virtual short readShort()
virtual void skipNextBytes(int64 numBytesToSkip)
virtual size_t readIntoMemoryBlock(MemoryBlock &destBlock, ssize_t maxNumBytesToRead=-1)
virtual int read(void *destBuffer, int maxBytesToRead)=0
virtual int readInt()
void * getData() noexcept
void setSize(const size_t newSize, bool initialiseNewSpaceToZero=false)
size_t getSize() const noexcept
size_t getDataSize() const noexcept
bool write(const void *, size_t) override
virtual bool writeByte(char byte)
virtual bool writeInt(int value)
String getValue(StringRef, const String &defaultReturnValue) const
bool containsKey(StringRef key) const noexcept
void set(const String &key, const String &value)
size_t getNumBytesAsUTF8() const noexcept
static String createStringFromData(const void *data, int size)
static String fromUTF8(const char *utf8buffer, int bufferSizeBytes=-1)
static Time JUCE_CALLTYPE getCurrentTime() noexcept
Definition: juce_Time.cpp:218
String formatted(const String &format) const
Definition: juce_Time.cpp:326
static const char *const riffInfoCopyright
static const char *const acidRootSet
static const char *const riffInfoDirectory
static const char *const bwavCodingHistory
static const char *const bwavTimeReference
static const char *const acidDiskBased
static const char *const acidOneShot
static const char *const riffInfoOrganisation
static const char *const riffInfoFirstLanguage
static const char *const riffInfoEncodedBy
static const char *const riffInfoCommissioned
static const char *const riffInfoMusicBy
static const char *const riffInfoSharpness
static const char *const riffInfoStatistics
static const char *const riffInfoNinthLanguage
static const char *const riffInfoDefaultAudioStream
static const char *const riffInfoGenre
static const char *const riffInfoMoreInfoBannerImage
static const char *const riffInfoVegasVersionMajor
static const char *const riffInfoLocation
AudioFormatReader * createReaderFor(InputStream *sourceStream, bool deleteStreamIfOpeningFails) override
static const char *const riffInfoRate
static const char *const riffInfoCostumeDesigner
static const char *const riffInfoVersion
static const char *const riffInfoLightness
static const char *const riffInfoProductionStudio
static const char *const riffInfoProducedBy
static const char *const riffInfoEighthLanguage
static const char *const riffInfoCropped
static const char *const riffInfoRating
static const char *const riffInfoURL
static const char *const ISRC
static const char *const riffInfoMoreInfoBannerURL
static const char *const riffInfoStartTimecode
static const char *const bwavOriginatorRef
static const char *const riffInfoTitle
static const char *const riffInfoArtist
static const char *const riffInfoSixthLanguage
static const char *const riffInfoSecondaryGenre
static const char *const riffInfoFifthLanguage
static const char *const riffInfoDotsPerInch
static const char *const riffInfoDistributedBy
static const char *const riffInfoStarring_ISTR
static const char *const riffInfoProductName
static const char *const riffInfoKeywords
static const char *const riffInfoRippedBy
static const char *const riffInfoLanguage
static const char *const riffInfoDateTimeOriginal
static const char *const acidizerFlag
static const char *const riffInfoBaseURL
MemoryMappedAudioFormatReader * createMemoryMappedReader(const File &) override
bool isChannelLayoutSupported(const AudioChannelSet &channelSet) override
static const char *const riffInfoProductionDesigner
static const char *const acidDenominator
static const char *const riffInfoVegasVersionMinor
static const char *const riffInfoLength
bool replaceMetadataInFile(const File &wavFile, const StringPairArray &newMetadata)
static const char *const riffInfoTechnician
static const char *const riffInfoSoftware
static const char *const riffInfoStarring_STAR
static const char *const riffInfoDateCreated
static const char *const riffInfoSeventhLanguage
virtual AudioFormatWriter * createWriterFor(OutputStream *streamToWriteTo, double sampleRateToUse, unsigned int numberOfChannels, int bitsPerSample, const StringPairArray &metadataValues, int qualityOptionIndex)=0
static const char *const acidBeats
AudioFormatWriter * createWriterFor(OutputStream *streamToWriteTo, double sampleRateToUse, unsigned int numberOfChannels, int bitsPerSample, const StringPairArray &metadataValues, int qualityOptionIndex) override
static const char *const riffInfoLogoIconURL
static const char *const tracktionLoopInfo
static const char *const acidNumerator
static const char *const bwavOriginationDate
static const char *const riffInfoComments
static const char *const riffInfoNumberOfParts
static const char *const bwavDescription
static const char *const riffInfoSoundSchemeTitle
Array< int > getPossibleSampleRates() override
static const char *const riffInfoWatermarkURL
static const char *const riffInfoTrackNo
static const char *const riffInfoMedium
static const char *const acidStretch
Array< int > getPossibleBitDepths() override
static const char *const riffInfoThirdLanguage
static const char *const bwavOriginationTime
static const char *const riffInfoArchivalLocation
static const char *const riffInfoMoreInfoText
static const char *const riffInfoCinematographer
static const char *const riffInfoFourthLanguage
static const char *const riffInfoSubject
static const char *const riffInfoRated
static const char *const riffInfoDimension
static const char *const riffInfoEditedBy
static const char *const riffInfoYear
static const char *const riffInfoComment2
static StringPairArray createBWAVMetadata(const String &description, const String &originator, const String &originatorRef, Time dateAndTime, int64 timeReferenceSamples, const String &codingHistory)
static const char *const riffInfoTrackNumber
static const char *const riffInfoEngineer
static const char *const riffInfoWrittenBy
static const char *const riffInfoTimeCode
static const char *const riffInfoSourceFrom
static const char *const riffInfoSource
static const char *const riffInfoLogoURL
static const char *const riffInfoCountry
static const char *const riffInfoSecondLanguage
static const char *const riffInfoComment
static const char *const riffInfoTapeName
static const char *const riffInfoEndTimecode
static const char *const riffInfoPart
static const char *const bwavOriginator
static const char *const acidTempo
static const char *const acidRootNote
static const char *const riffInfoMoreInfoURL