OpenShot Audio Library | OpenShotAudio  0.6.0
juce_WavAudioFormat.cpp
1 /*
2  ==============================================================================
3 
4  This file is part of the JUCE library.
5  Copyright (c) 2022 - Raw Material Software Limited
6 
7  JUCE is an open source library subject to commercial or open-source
8  licensing.
9 
10  By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11  Agreement and JUCE Privacy Policy.
12 
13  End User License Agreement: www.juce.com/juce-7-licence
14  Privacy Policy: www.juce.com/juce-privacy-policy
15 
16  Or: You may also use this code under the terms of the GPL v3 (see
17  www.gnu.org/licenses).
18 
19  JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20  EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21  DISCLAIMED.
22 
23  ==============================================================================
24 */
25 
26 namespace juce
27 {
28 
29 using StringMap = std::unordered_map<String, String>;
30 
31 static auto toMap (const StringPairArray& array)
32 {
33  StringMap result;
34 
35  for (auto i = 0; i < array.size(); ++i)
36  result[array.getAllKeys()[i]] = array.getAllValues()[i];
37 
38  return result;
39 }
40 
41 static auto getValueWithDefault (const StringMap& m, const String& key, const String& fallback = {})
42 {
43  const auto iter = m.find (key);
44  return iter != m.cend() ? iter->second : fallback;
45 }
46 
47 static const char* const wavFormatName = "WAV file";
48 
49 //==============================================================================
50 const char* const WavAudioFormat::bwavDescription = "bwav description";
51 const char* const WavAudioFormat::bwavOriginator = "bwav originator";
52 const char* const WavAudioFormat::bwavOriginatorRef = "bwav originator ref";
53 const char* const WavAudioFormat::bwavOriginationDate = "bwav origination date";
54 const char* const WavAudioFormat::bwavOriginationTime = "bwav origination time";
55 const char* const WavAudioFormat::bwavTimeReference = "bwav time reference";
56 const char* const WavAudioFormat::bwavCodingHistory = "bwav coding history";
57 
59  const String& originator,
60  const String& originatorRef,
61  Time date,
62  int64 timeReferenceSamples,
63  const String& codingHistory)
64 {
66 
67  m.set (bwavDescription, description);
68  m.set (bwavOriginator, originator);
69  m.set (bwavOriginatorRef, originatorRef);
70  m.set (bwavOriginationDate, date.formatted ("%Y-%m-%d"));
71  m.set (bwavOriginationTime, date.formatted ("%H:%M:%S"));
72  m.set (bwavTimeReference, String (timeReferenceSamples));
73  m.set (bwavCodingHistory, codingHistory);
74 
75  return m;
76 }
77 
78 const char* const WavAudioFormat::acidOneShot = "acid one shot";
79 const char* const WavAudioFormat::acidRootSet = "acid root set";
80 const char* const WavAudioFormat::acidStretch = "acid stretch";
81 const char* const WavAudioFormat::acidDiskBased = "acid disk based";
82 const char* const WavAudioFormat::acidizerFlag = "acidizer flag";
83 const char* const WavAudioFormat::acidRootNote = "acid root note";
84 const char* const WavAudioFormat::acidBeats = "acid beats";
85 const char* const WavAudioFormat::acidDenominator = "acid denominator";
86 const char* const WavAudioFormat::acidNumerator = "acid numerator";
87 const char* const WavAudioFormat::acidTempo = "acid tempo";
88 
89 const char* const WavAudioFormat::riffInfoArchivalLocation = "IARL";
90 const char* const WavAudioFormat::riffInfoArtist = "IART";
91 const char* const WavAudioFormat::riffInfoBaseURL = "IBSU";
92 const char* const WavAudioFormat::riffInfoCinematographer = "ICNM";
93 const char* const WavAudioFormat::riffInfoComment = "CMNT";
94 const char* const WavAudioFormat::riffInfoComment2 = "ICMT";
95 const char* const WavAudioFormat::riffInfoComments = "COMM";
96 const char* const WavAudioFormat::riffInfoCommissioned = "ICMS";
97 const char* const WavAudioFormat::riffInfoCopyright = "ICOP";
98 const char* const WavAudioFormat::riffInfoCostumeDesigner = "ICDS";
99 const char* const WavAudioFormat::riffInfoCountry = "ICNT";
100 const char* const WavAudioFormat::riffInfoCropped = "ICRP";
101 const char* const WavAudioFormat::riffInfoDateCreated = "ICRD";
102 const char* const WavAudioFormat::riffInfoDateTimeOriginal = "IDIT";
103 const char* const WavAudioFormat::riffInfoDefaultAudioStream = "ICAS";
104 const char* const WavAudioFormat::riffInfoDimension = "IDIM";
105 const char* const WavAudioFormat::riffInfoDirectory = "DIRC";
106 const char* const WavAudioFormat::riffInfoDistributedBy = "IDST";
107 const char* const WavAudioFormat::riffInfoDotsPerInch = "IDPI";
108 const char* const WavAudioFormat::riffInfoEditedBy = "IEDT";
109 const char* const WavAudioFormat::riffInfoEighthLanguage = "IAS8";
110 const char* const WavAudioFormat::riffInfoEncodedBy = "CODE";
111 const char* const WavAudioFormat::riffInfoEndTimecode = "TCDO";
112 const char* const WavAudioFormat::riffInfoEngineer = "IENG";
113 const char* const WavAudioFormat::riffInfoFifthLanguage = "IAS5";
114 const char* const WavAudioFormat::riffInfoFirstLanguage = "IAS1";
115 const char* const WavAudioFormat::riffInfoFourthLanguage = "IAS4";
116 const char* const WavAudioFormat::riffInfoGenre = "GENR";
117 const char* const WavAudioFormat::riffInfoKeywords = "IKEY";
118 const char* const WavAudioFormat::riffInfoLanguage = "LANG";
119 const char* const WavAudioFormat::riffInfoLength = "TLEN";
120 const char* const WavAudioFormat::riffInfoLightness = "ILGT";
121 const char* const WavAudioFormat::riffInfoLocation = "LOCA";
122 const char* const WavAudioFormat::riffInfoLogoIconURL = "ILIU";
123 const char* const WavAudioFormat::riffInfoLogoURL = "ILGU";
124 const char* const WavAudioFormat::riffInfoMedium = "IMED";
125 const char* const WavAudioFormat::riffInfoMoreInfoBannerImage = "IMBI";
126 const char* const WavAudioFormat::riffInfoMoreInfoBannerURL = "IMBU";
127 const char* const WavAudioFormat::riffInfoMoreInfoText = "IMIT";
128 const char* const WavAudioFormat::riffInfoMoreInfoURL = "IMIU";
129 const char* const WavAudioFormat::riffInfoMusicBy = "IMUS";
130 const char* const WavAudioFormat::riffInfoNinthLanguage = "IAS9";
131 const char* const WavAudioFormat::riffInfoNumberOfParts = "PRT2";
132 const char* const WavAudioFormat::riffInfoOrganisation = "TORG";
133 const char* const WavAudioFormat::riffInfoPart = "PRT1";
134 const char* const WavAudioFormat::riffInfoProducedBy = "IPRO";
135 const char* const WavAudioFormat::riffInfoProductName = "IPRD";
136 const char* const WavAudioFormat::riffInfoProductionDesigner = "IPDS";
137 const char* const WavAudioFormat::riffInfoProductionStudio = "ISDT";
138 const char* const WavAudioFormat::riffInfoRate = "RATE";
139 const char* const WavAudioFormat::riffInfoRated = "AGES";
140 const char* const WavAudioFormat::riffInfoRating = "IRTD";
141 const char* const WavAudioFormat::riffInfoRippedBy = "IRIP";
142 const char* const WavAudioFormat::riffInfoSecondaryGenre = "ISGN";
143 const char* const WavAudioFormat::riffInfoSecondLanguage = "IAS2";
144 const char* const WavAudioFormat::riffInfoSeventhLanguage = "IAS7";
145 const char* const WavAudioFormat::riffInfoSharpness = "ISHP";
146 const char* const WavAudioFormat::riffInfoSixthLanguage = "IAS6";
147 const char* const WavAudioFormat::riffInfoSoftware = "ISFT";
148 const char* const WavAudioFormat::riffInfoSoundSchemeTitle = "DISP";
149 const char* const WavAudioFormat::riffInfoSource = "ISRC";
150 const char* const WavAudioFormat::riffInfoSourceFrom = "ISRF";
151 const char* const WavAudioFormat::riffInfoStarring_ISTR = "ISTR";
152 const char* const WavAudioFormat::riffInfoStarring_STAR = "STAR";
153 const char* const WavAudioFormat::riffInfoStartTimecode = "TCOD";
154 const char* const WavAudioFormat::riffInfoStatistics = "STAT";
155 const char* const WavAudioFormat::riffInfoSubject = "ISBJ";
156 const char* const WavAudioFormat::riffInfoTapeName = "TAPE";
157 const char* const WavAudioFormat::riffInfoTechnician = "ITCH";
158 const char* const WavAudioFormat::riffInfoThirdLanguage = "IAS3";
159 const char* const WavAudioFormat::riffInfoTimeCode = "ISMP";
160 const char* const WavAudioFormat::riffInfoTitle = "INAM";
161 const char* const WavAudioFormat::riffInfoTrackNo = "IPRT";
162 const char* const WavAudioFormat::riffInfoTrackNumber = "TRCK";
163 const char* const WavAudioFormat::riffInfoURL = "TURL";
164 const char* const WavAudioFormat::riffInfoVegasVersionMajor = "VMAJ";
165 const char* const WavAudioFormat::riffInfoVegasVersionMinor = "VMIN";
166 const char* const WavAudioFormat::riffInfoVersion = "TVER";
167 const char* const WavAudioFormat::riffInfoWatermarkURL = "IWMU";
168 const char* const WavAudioFormat::riffInfoWrittenBy = "IWRI";
169 const char* const WavAudioFormat::riffInfoYear = "YEAR";
170 
171 const char* const WavAudioFormat::aswgContentType = "contentType";
172 const char* const WavAudioFormat::aswgProject = "project";
173 const char* const WavAudioFormat::aswgOriginator = "originator";
174 const char* const WavAudioFormat::aswgOriginatorStudio = "originatorStudio";
175 const char* const WavAudioFormat::aswgNotes = "notes";
176 const char* const WavAudioFormat::aswgSession = "session";
177 const char* const WavAudioFormat::aswgState = "state";
178 const char* const WavAudioFormat::aswgEditor = "editor";
179 const char* const WavAudioFormat::aswgMixer = "mixer";
180 const char* const WavAudioFormat::aswgFxChainName = "fxChainName";
181 const char* const WavAudioFormat::aswgChannelConfig = "channelConfig";
182 const char* const WavAudioFormat::aswgAmbisonicFormat = "ambisonicFormat";
183 const char* const WavAudioFormat::aswgAmbisonicChnOrder = "ambisonicChnOrder";
184 const char* const WavAudioFormat::aswgAmbisonicNorm = "ambisonicNorm";
185 const char* const WavAudioFormat::aswgMicType = "micType";
186 const char* const WavAudioFormat::aswgMicConfig = "micConfig";
187 const char* const WavAudioFormat::aswgMicDistance = "micDistance";
188 const char* const WavAudioFormat::aswgRecordingLoc = "recordingLoc";
189 const char* const WavAudioFormat::aswgIsDesigned = "isDesigned";
190 const char* const WavAudioFormat::aswgRecEngineer = "recEngineer";
191 const char* const WavAudioFormat::aswgRecStudio = "recStudio";
192 const char* const WavAudioFormat::aswgImpulseLocation = "impulseLocation";
193 const char* const WavAudioFormat::aswgCategory = "category";
194 const char* const WavAudioFormat::aswgSubCategory = "subCategory";
195 const char* const WavAudioFormat::aswgCatId = "catId";
196 const char* const WavAudioFormat::aswgUserCategory = "userCategory";
197 const char* const WavAudioFormat::aswgUserData = "userData";
198 const char* const WavAudioFormat::aswgVendorCategory = "vendorCategory";
199 const char* const WavAudioFormat::aswgFxName = "fxName";
200 const char* const WavAudioFormat::aswgLibrary = "library";
201 const char* const WavAudioFormat::aswgCreatorId = "creatorId";
202 const char* const WavAudioFormat::aswgSourceId = "sourceId";
203 const char* const WavAudioFormat::aswgRmsPower = "rmsPower";
204 const char* const WavAudioFormat::aswgLoudness = "loudness";
205 const char* const WavAudioFormat::aswgLoudnessRange = "loudnessRange";
206 const char* const WavAudioFormat::aswgMaxPeak = "maxPeak";
207 const char* const WavAudioFormat::aswgSpecDensity = "specDensity";
208 const char* const WavAudioFormat::aswgZeroCrossRate = "zeroCrossRate";
209 const char* const WavAudioFormat::aswgPapr = "papr";
210 const char* const WavAudioFormat::aswgText = "text";
211 const char* const WavAudioFormat::aswgEfforts = "efforts";
212 const char* const WavAudioFormat::aswgEffortType = "effortType";
213 const char* const WavAudioFormat::aswgProjection = "projection";
214 const char* const WavAudioFormat::aswgLanguage = "language";
215 const char* const WavAudioFormat::aswgTimingRestriction = "timingRestriction";
216 const char* const WavAudioFormat::aswgCharacterName = "characterName";
217 const char* const WavAudioFormat::aswgCharacterGender = "characterGender";
218 const char* const WavAudioFormat::aswgCharacterAge = "characterAge";
219 const char* const WavAudioFormat::aswgCharacterRole = "characterRole";
220 const char* const WavAudioFormat::aswgActorName = "actorName";
221 const char* const WavAudioFormat::aswgActorGender = "actorGender";
222 const char* const WavAudioFormat::aswgDirector = "director";
223 const char* const WavAudioFormat::aswgDirection = "direction";
224 const char* const WavAudioFormat::aswgFxUsed = "fxUsed";
225 const char* const WavAudioFormat::aswgUsageRights = "usageRights";
226 const char* const WavAudioFormat::aswgIsUnion = "isUnion";
227 const char* const WavAudioFormat::aswgAccent = "accent";
228 const char* const WavAudioFormat::aswgEmotion = "emotion";
229 const char* const WavAudioFormat::aswgComposor = "composor";
230 const char* const WavAudioFormat::aswgArtist = "artist";
231 const char* const WavAudioFormat::aswgSongTitle = "songTitle";
232 const char* const WavAudioFormat::aswgGenre = "genre";
233 const char* const WavAudioFormat::aswgSubGenre = "subGenre";
234 const char* const WavAudioFormat::aswgProducer = "producer";
235 const char* const WavAudioFormat::aswgMusicSup = "musicSup";
236 const char* const WavAudioFormat::aswgInstrument = "instrument";
237 const char* const WavAudioFormat::aswgMusicPublisher = "musicPublisher";
238 const char* const WavAudioFormat::aswgRightsOwner = "rightsOwner";
239 const char* const WavAudioFormat::aswgIsSource = "isSource";
240 const char* const WavAudioFormat::aswgIsLoop = "isLoop";
241 const char* const WavAudioFormat::aswgIntensity = "intensity";
242 const char* const WavAudioFormat::aswgIsFinal = "isFinal";
243 const char* const WavAudioFormat::aswgOrderRef = "orderRef";
244 const char* const WavAudioFormat::aswgIsOst = "isOst";
245 const char* const WavAudioFormat::aswgIsCinematic = "isCinematic";
246 const char* const WavAudioFormat::aswgIsLicensed = "isLicensed";
247 const char* const WavAudioFormat::aswgIsDiegetic = "isDiegetic";
248 const char* const WavAudioFormat::aswgMusicVersion = "musicVersion";
249 const char* const WavAudioFormat::aswgIsrcId = "isrcId";
250 const char* const WavAudioFormat::aswgTempo = "tempo";
251 const char* const WavAudioFormat::aswgTimeSig = "timeSig";
252 const char* const WavAudioFormat::aswgInKey = "inKey";
253 const char* const WavAudioFormat::aswgBillingCode = "billingCode";
254 const char* const WavAudioFormat::aswgVersion = "IXML_VERSION";
255 
256 const char* const WavAudioFormat::ISRC = "ISRC";
257 const char* const WavAudioFormat::internationalStandardRecordingCode = "international standard recording code";
258 const char* const WavAudioFormat::tracktionLoopInfo = "tracktion loop info";
259 
260 //==============================================================================
261 namespace WavFileHelpers
262 {
263  constexpr inline int chunkName (const char* name) noexcept { return (int) ByteOrder::littleEndianInt (name); }
264  constexpr inline size_t roundUpSize (size_t sz) noexcept { return (sz + 3) & ~3u; }
265 
266  #if JUCE_MSVC
267  #pragma pack (push, 1)
268  #endif
269 
270  struct BWAVChunk
271  {
272  char description[256];
273  char originator[32];
274  char originatorRef[32];
275  char originationDate[10];
276  char originationTime[8];
277  uint32 timeRefLow;
278  uint32 timeRefHigh;
279  uint16 version;
280  uint8 umid[64];
281  uint8 reserved[190];
282  char codingHistory[1];
283 
284  void copyTo (StringMap& values, const int totalSize) const
285  {
286  values[WavAudioFormat::bwavDescription] = String::fromUTF8 (description, sizeof (description));
287  values[WavAudioFormat::bwavOriginator] = String::fromUTF8 (originator, sizeof (originator));
288  values[WavAudioFormat::bwavOriginatorRef] = String::fromUTF8 (originatorRef, sizeof (originatorRef));
289  values[WavAudioFormat::bwavOriginationDate] = String::fromUTF8 (originationDate, sizeof (originationDate));
290  values[WavAudioFormat::bwavOriginationTime] = String::fromUTF8 (originationTime, sizeof (originationTime));
291 
292  auto timeLow = ByteOrder::swapIfBigEndian (timeRefLow);
293  auto timeHigh = ByteOrder::swapIfBigEndian (timeRefHigh);
294  auto time = (((int64) timeHigh) << 32) + timeLow;
295 
296  values[WavAudioFormat::bwavTimeReference] = String (time);
297  values[WavAudioFormat::bwavCodingHistory] = String::fromUTF8 (codingHistory, totalSize - (int) offsetof (BWAVChunk, codingHistory));
298  }
299 
300  static MemoryBlock createFrom (const StringMap& values)
301  {
302  MemoryBlock data (roundUpSize (sizeof (BWAVChunk) + getValueWithDefault (values, WavAudioFormat::bwavCodingHistory).getNumBytesAsUTF8()));
303  data.fillWith (0);
304 
305  auto* b = (BWAVChunk*) data.getData();
306 
307  // Allow these calls to overwrite an extra byte at the end, which is fine as long
308  // as they get called in the right order.
309  getValueWithDefault (values, WavAudioFormat::bwavDescription) .copyToUTF8 (b->description, 257);
310  getValueWithDefault (values, WavAudioFormat::bwavOriginator) .copyToUTF8 (b->originator, 33);
311  getValueWithDefault (values, WavAudioFormat::bwavOriginatorRef) .copyToUTF8 (b->originatorRef, 33);
312  getValueWithDefault (values, WavAudioFormat::bwavOriginationDate).copyToUTF8 (b->originationDate, 11);
313  getValueWithDefault (values, WavAudioFormat::bwavOriginationTime).copyToUTF8 (b->originationTime, 9);
314 
315  auto time = getValueWithDefault (values, WavAudioFormat::bwavTimeReference).getLargeIntValue();
316  b->timeRefLow = ByteOrder::swapIfBigEndian ((uint32) (time & 0xffffffff));
317  b->timeRefHigh = ByteOrder::swapIfBigEndian ((uint32) (time >> 32));
318 
319  getValueWithDefault (values, WavAudioFormat::bwavCodingHistory).copyToUTF8 (b->codingHistory, 0x7fffffff);
320 
321  if (b->description[0] != 0
322  || b->originator[0] != 0
323  || b->originationDate[0] != 0
324  || b->originationTime[0] != 0
325  || b->codingHistory[0] != 0
326  || time != 0)
327  {
328  return data;
329  }
330 
331  return {};
332  }
333 
334  } JUCE_PACKED;
335 
336  //==============================================================================
337  inline AudioChannelSet canonicalWavChannelSet (int numChannels)
338  {
339  if (numChannels == 1) return AudioChannelSet::mono();
340  if (numChannels == 2) return AudioChannelSet::stereo();
341  if (numChannels == 3) return AudioChannelSet::createLCR();
342  if (numChannels == 4) return AudioChannelSet::quadraphonic();
343  if (numChannels == 5) return AudioChannelSet::create5point0();
344  if (numChannels == 6) return AudioChannelSet::create5point1();
345  if (numChannels == 7) return AudioChannelSet::create7point0SDDS();
346  if (numChannels == 8) return AudioChannelSet::create7point1SDDS();
347 
348  return AudioChannelSet::discreteChannels (numChannels);
349  }
350 
351  //==============================================================================
352  struct SMPLChunk
353  {
354  struct SampleLoop
355  {
356  uint32 identifier;
357  uint32 type; // these are different in AIFF and WAV
358  uint32 start;
359  uint32 end;
360  uint32 fraction;
361  uint32 playCount;
362  } JUCE_PACKED;
363 
364  uint32 manufacturer;
365  uint32 product;
366  uint32 samplePeriod;
367  uint32 midiUnityNote;
368  uint32 midiPitchFraction;
369  uint32 smpteFormat;
370  uint32 smpteOffset;
371  uint32 numSampleLoops;
372  uint32 samplerData;
373  SampleLoop loops[1];
374 
375  template <typename NameType>
376  static void setValue (StringMap& values, NameType name, uint32 val)
377  {
378  values[name] = String (ByteOrder::swapIfBigEndian (val));
379  }
380 
381  static void setValue (StringMap& values, int prefix, const char* name, uint32 val)
382  {
383  setValue (values, "Loop" + String (prefix) + name, val);
384  }
385 
386  void copyTo (StringMap& values, const int totalSize) const
387  {
388  setValue (values, "Manufacturer", manufacturer);
389  setValue (values, "Product", product);
390  setValue (values, "SamplePeriod", samplePeriod);
391  setValue (values, "MidiUnityNote", midiUnityNote);
392  setValue (values, "MidiPitchFraction", midiPitchFraction);
393  setValue (values, "SmpteFormat", smpteFormat);
394  setValue (values, "SmpteOffset", smpteOffset);
395  setValue (values, "NumSampleLoops", numSampleLoops);
396  setValue (values, "SamplerData", samplerData);
397 
398  for (int i = 0; i < (int) numSampleLoops; ++i)
399  {
400  if ((uint8*) (loops + (i + 1)) > ((uint8*) this) + totalSize)
401  break;
402 
403  setValue (values, i, "Identifier", loops[i].identifier);
404  setValue (values, i, "Type", loops[i].type);
405  setValue (values, i, "Start", loops[i].start);
406  setValue (values, i, "End", loops[i].end);
407  setValue (values, i, "Fraction", loops[i].fraction);
408  setValue (values, i, "PlayCount", loops[i].playCount);
409  }
410  }
411 
412  template <typename NameType>
413  static uint32 getValue (const StringMap& values, NameType name, const char* def)
414  {
415  return ByteOrder::swapIfBigEndian ((uint32) getValueWithDefault (values, name, def).getIntValue());
416  }
417 
418  static uint32 getValue (const StringMap& values, int prefix, const char* name, const char* def)
419  {
420  return getValue (values, "Loop" + String (prefix) + name, def);
421  }
422 
423  static MemoryBlock createFrom (const StringMap& values)
424  {
425  MemoryBlock data;
426  auto numLoops = jmin (64, getValueWithDefault (values, "NumSampleLoops", "0").getIntValue());
427 
428  data.setSize (roundUpSize (sizeof (SMPLChunk) + (size_t) (jmax (0, numLoops - 1)) * sizeof (SampleLoop)), true);
429 
430  auto s = static_cast<SMPLChunk*> (data.getData());
431 
432  s->manufacturer = getValue (values, "Manufacturer", "0");
433  s->product = getValue (values, "Product", "0");
434  s->samplePeriod = getValue (values, "SamplePeriod", "0");
435  s->midiUnityNote = getValue (values, "MidiUnityNote", "60");
436  s->midiPitchFraction = getValue (values, "MidiPitchFraction", "0");
437  s->smpteFormat = getValue (values, "SmpteFormat", "0");
438  s->smpteOffset = getValue (values, "SmpteOffset", "0");
439  s->numSampleLoops = ByteOrder::swapIfBigEndian ((uint32) numLoops);
440  s->samplerData = getValue (values, "SamplerData", "0");
441 
442  for (int i = 0; i < numLoops; ++i)
443  {
444  auto& loop = s->loops[i];
445  loop.identifier = getValue (values, i, "Identifier", "0");
446  loop.type = getValue (values, i, "Type", "0");
447  loop.start = getValue (values, i, "Start", "0");
448  loop.end = getValue (values, i, "End", "0");
449  loop.fraction = getValue (values, i, "Fraction", "0");
450  loop.playCount = getValue (values, i, "PlayCount", "0");
451  }
452 
453  return data;
454  }
455  } JUCE_PACKED;
456 
457  //==============================================================================
458  struct InstChunk
459  {
460  int8 baseNote;
461  int8 detune;
462  int8 gain;
463  int8 lowNote;
464  int8 highNote;
465  int8 lowVelocity;
466  int8 highVelocity;
467 
468  static void setValue (StringMap& values, const char* name, int val)
469  {
470  values[name] = String (val);
471  }
472 
473  void copyTo (StringMap& values) const
474  {
475  setValue (values, "MidiUnityNote", baseNote);
476  setValue (values, "Detune", detune);
477  setValue (values, "Gain", gain);
478  setValue (values, "LowNote", lowNote);
479  setValue (values, "HighNote", highNote);
480  setValue (values, "LowVelocity", lowVelocity);
481  setValue (values, "HighVelocity", highVelocity);
482  }
483 
484  static int8 getValue (const StringMap& values, const char* name, const char* def)
485  {
486  return (int8) getValueWithDefault (values, name, def).getIntValue();
487  }
488 
489  static MemoryBlock createFrom (const StringMap& values)
490  {
491  MemoryBlock data;
492 
493  if ( values.find ("LowNote") != values.cend()
494  && values.find ("HighNote") != values.cend())
495  {
496  data.setSize (8, true);
497  auto* inst = static_cast<InstChunk*> (data.getData());
498 
499  inst->baseNote = getValue (values, "MidiUnityNote", "60");
500  inst->detune = getValue (values, "Detune", "0");
501  inst->gain = getValue (values, "Gain", "0");
502  inst->lowNote = getValue (values, "LowNote", "0");
503  inst->highNote = getValue (values, "HighNote", "127");
504  inst->lowVelocity = getValue (values, "LowVelocity", "1");
505  inst->highVelocity = getValue (values, "HighVelocity", "127");
506  }
507 
508  return data;
509  }
510  } JUCE_PACKED;
511 
512  //==============================================================================
513  struct CueChunk
514  {
515  struct Cue
516  {
517  uint32 identifier;
518  uint32 order;
519  uint32 chunkID;
520  uint32 chunkStart;
521  uint32 blockStart;
522  uint32 offset;
523  } JUCE_PACKED;
524 
525  uint32 numCues;
526  Cue cues[1];
527 
528  static void setValue (StringMap& values, int prefix, const char* name, uint32 val)
529  {
530  values["Cue" + String (prefix) + name] = String (ByteOrder::swapIfBigEndian (val));
531  }
532 
533  void copyTo (StringMap& values, const int totalSize) const
534  {
535  values["NumCuePoints"] = String (ByteOrder::swapIfBigEndian (numCues));
536 
537  for (int i = 0; i < (int) numCues; ++i)
538  {
539  if ((uint8*) (cues + (i + 1)) > ((uint8*) this) + totalSize)
540  break;
541 
542  setValue (values, i, "Identifier", cues[i].identifier);
543  setValue (values, i, "Order", cues[i].order);
544  setValue (values, i, "ChunkID", cues[i].chunkID);
545  setValue (values, i, "ChunkStart", cues[i].chunkStart);
546  setValue (values, i, "BlockStart", cues[i].blockStart);
547  setValue (values, i, "Offset", cues[i].offset);
548  }
549  }
550 
551  static MemoryBlock createFrom (const StringMap& values)
552  {
553  MemoryBlock data;
554  const int numCues = getValueWithDefault (values, "NumCuePoints", "0").getIntValue();
555 
556  if (numCues > 0)
557  {
558  data.setSize (roundUpSize (sizeof (CueChunk) + (size_t) (numCues - 1) * sizeof (Cue)), true);
559 
560  auto c = static_cast<CueChunk*> (data.getData());
561 
562  c->numCues = ByteOrder::swapIfBigEndian ((uint32) numCues);
563 
564  const String dataChunkID (chunkName ("data"));
565  int nextOrder = 0;
566 
567  #if JUCE_DEBUG
568  Array<uint32> identifiers;
569  #endif
570 
571  for (int i = 0; i < numCues; ++i)
572  {
573  auto prefix = "Cue" + String (i);
574  auto identifier = (uint32) getValueWithDefault (values, prefix + "Identifier", "0").getIntValue();
575 
576  #if JUCE_DEBUG
577  jassert (! identifiers.contains (identifier));
578  identifiers.add (identifier);
579  #endif
580 
581  auto order = getValueWithDefault (values, prefix + "Order", String (nextOrder)).getIntValue();
582  nextOrder = jmax (nextOrder, order) + 1;
583 
584  auto& cue = c->cues[i];
585  cue.identifier = ByteOrder::swapIfBigEndian ((uint32) identifier);
586  cue.order = ByteOrder::swapIfBigEndian ((uint32) order);
587  cue.chunkID = ByteOrder::swapIfBigEndian ((uint32) getValueWithDefault (values, prefix + "ChunkID", dataChunkID).getIntValue());
588  cue.chunkStart = ByteOrder::swapIfBigEndian ((uint32) getValueWithDefault (values, prefix + "ChunkStart", "0").getIntValue());
589  cue.blockStart = ByteOrder::swapIfBigEndian ((uint32) getValueWithDefault (values, prefix + "BlockStart", "0").getIntValue());
590  cue.offset = ByteOrder::swapIfBigEndian ((uint32) getValueWithDefault (values, prefix + "Offset", "0").getIntValue());
591  }
592  }
593 
594  return data;
595  }
596 
597  } JUCE_PACKED;
598 
599  //==============================================================================
600  namespace ListChunk
601  {
602  static int getValue (const StringMap& values, const String& name)
603  {
604  return getValueWithDefault (values, name, "0").getIntValue();
605  }
606 
607  static int getValue (const StringMap& values, const String& prefix, const char* name)
608  {
609  return getValue (values, prefix + name);
610  }
611 
612  static void appendLabelOrNoteChunk (const StringMap& values, const String& prefix,
613  const int chunkType, MemoryOutputStream& out)
614  {
615  auto label = getValueWithDefault (values, prefix + "Text", prefix);
616  auto labelLength = (int) label.getNumBytesAsUTF8() + 1;
617  auto chunkLength = 4 + labelLength + (labelLength & 1);
618 
619  out.writeInt (chunkType);
620  out.writeInt (chunkLength);
621  out.writeInt (getValue (values, prefix, "Identifier"));
622  out.write (label.toUTF8(), (size_t) labelLength);
623 
624  if ((out.getDataSize() & 1) != 0)
625  out.writeByte (0);
626  }
627 
628  static void appendExtraChunk (const StringMap& values, const String& prefix, MemoryOutputStream& out)
629  {
630  auto text = getValueWithDefault (values, prefix + "Text", prefix);
631 
632  auto textLength = (int) text.getNumBytesAsUTF8() + 1; // include null terminator
633  auto chunkLength = textLength + 20 + (textLength & 1);
634 
635  out.writeInt (chunkName ("ltxt"));
636  out.writeInt (chunkLength);
637  out.writeInt (getValue (values, prefix, "Identifier"));
638  out.writeInt (getValue (values, prefix, "SampleLength"));
639  out.writeInt (getValue (values, prefix, "Purpose"));
640  out.writeShort ((short) getValue (values, prefix, "Country"));
641  out.writeShort ((short) getValue (values, prefix, "Language"));
642  out.writeShort ((short) getValue (values, prefix, "Dialect"));
643  out.writeShort ((short) getValue (values, prefix, "CodePage"));
644  out.write (text.toUTF8(), (size_t) textLength);
645 
646  if ((out.getDataSize() & 1) != 0)
647  out.writeByte (0);
648  }
649 
650  static MemoryBlock createFrom (const StringMap& values)
651  {
652  auto numCueLabels = getValue (values, "NumCueLabels");
653  auto numCueNotes = getValue (values, "NumCueNotes");
654  auto numCueRegions = getValue (values, "NumCueRegions");
655 
656  MemoryOutputStream out;
657 
658  if (numCueLabels + numCueNotes + numCueRegions > 0)
659  {
660  out.writeInt (chunkName ("adtl"));
661 
662  for (int i = 0; i < numCueLabels; ++i)
663  appendLabelOrNoteChunk (values, "CueLabel" + String (i), chunkName ("labl"), out);
664 
665  for (int i = 0; i < numCueNotes; ++i)
666  appendLabelOrNoteChunk (values, "CueNote" + String (i), chunkName ("note"), out);
667 
668  for (int i = 0; i < numCueRegions; ++i)
669  appendExtraChunk (values, "CueRegion" + String (i), out);
670  }
671 
672  return out.getMemoryBlock();
673  }
674  }
675 
676  //==============================================================================
678  namespace ListInfoChunk
679  {
680  static const char* const types[] =
681  {
763  };
764 
765  static bool isMatchingTypeIgnoringCase (const int value, const char* const name) noexcept
766  {
767  for (int i = 0; i < 4; ++i)
768  if ((juce_wchar) name[i] != CharacterFunctions::toUpperCase ((juce_wchar) ((value >> (i * 8)) & 0xff)))
769  return false;
770 
771  return true;
772  }
773 
774  static void addToMetadata (StringMap& values, InputStream& input, int64 chunkEnd)
775  {
776  while (input.getPosition() < chunkEnd)
777  {
778  auto infoType = input.readInt();
779  auto infoLength = chunkEnd - input.getPosition();
780 
781  if (infoLength > 0)
782  {
783  infoLength = jmin (infoLength, (int64) input.readInt());
784 
785  if (infoLength <= 0)
786  return;
787 
788  for (auto& type : types)
789  {
790  if (isMatchingTypeIgnoringCase (infoType, type))
791  {
792  MemoryBlock mb;
793  input.readIntoMemoryBlock (mb, (ssize_t) infoLength);
794  values[type] = String::createStringFromData ((const char*) mb.getData(),
795  (int) mb.getSize());
796  break;
797  }
798  }
799  }
800  }
801  }
802 
803  static bool writeValue (const StringMap& values, MemoryOutputStream& out, const char* paramName)
804  {
805  auto value = getValueWithDefault (values, paramName, {});
806 
807  if (value.isEmpty())
808  return false;
809 
810  auto valueLength = (int) value.getNumBytesAsUTF8() + 1;
811  auto chunkLength = valueLength + (valueLength & 1);
812 
813  out.writeInt (chunkName (paramName));
814  out.writeInt (chunkLength);
815  out.write (value.toUTF8(), (size_t) valueLength);
816 
817  if ((out.getDataSize() & 1) != 0)
818  out.writeByte (0);
819 
820  return true;
821  }
822 
823  static MemoryBlock createFrom (const StringMap& values)
824  {
825  MemoryOutputStream out;
826  out.writeInt (chunkName ("INFO"));
827  bool anyParamsDefined = false;
828 
829  for (auto& type : types)
830  if (writeValue (values, out, type))
831  anyParamsDefined = true;
832 
833  return anyParamsDefined ? out.getMemoryBlock() : MemoryBlock();
834  }
835  }
836 
837  //==============================================================================
838  struct AcidChunk
839  {
841  AcidChunk (InputStream& input, size_t length)
842  {
843  zerostruct (*this);
844  input.read (this, (int) jmin (sizeof (*this), length));
845  }
846 
847  AcidChunk (const StringMap& values)
848  {
849  zerostruct (*this);
850 
851  flags = getFlagIfPresent (values, WavAudioFormat::acidOneShot, 0x01)
852  | getFlagIfPresent (values, WavAudioFormat::acidRootSet, 0x02)
853  | getFlagIfPresent (values, WavAudioFormat::acidStretch, 0x04)
854  | getFlagIfPresent (values, WavAudioFormat::acidDiskBased, 0x08)
855  | getFlagIfPresent (values, WavAudioFormat::acidizerFlag, 0x10);
856 
857  if (getValueWithDefault (values, WavAudioFormat::acidRootSet).getIntValue() != 0)
858  rootNote = ByteOrder::swapIfBigEndian ((uint16) getValueWithDefault (values, WavAudioFormat::acidRootNote).getIntValue());
859 
860  numBeats = ByteOrder::swapIfBigEndian ((uint32) getValueWithDefault (values, WavAudioFormat::acidBeats).getIntValue());
861  meterDenominator = ByteOrder::swapIfBigEndian ((uint16) getValueWithDefault (values, WavAudioFormat::acidDenominator).getIntValue());
862  meterNumerator = ByteOrder::swapIfBigEndian ((uint16) getValueWithDefault (values, WavAudioFormat::acidNumerator).getIntValue());
863 
864  const auto iter = values.find (WavAudioFormat::acidTempo);
865 
866  if (iter != values.cend())
867  tempo = swapFloatByteOrder (iter->second.getFloatValue());
868  }
869 
870  static MemoryBlock createFrom (const StringMap& values)
871  {
872  return AcidChunk (values).toMemoryBlock();
873  }
874 
875  MemoryBlock toMemoryBlock() const
876  {
877  return (flags != 0 || rootNote != 0 || numBeats != 0 || meterDenominator != 0 || meterNumerator != 0)
878  ? MemoryBlock (this, sizeof (*this)) : MemoryBlock();
879  }
880 
881  void addToMetadata (StringMap& values) const
882  {
883  setBoolFlag (values, WavAudioFormat::acidOneShot, 0x01);
884  setBoolFlag (values, WavAudioFormat::acidRootSet, 0x02);
885  setBoolFlag (values, WavAudioFormat::acidStretch, 0x04);
886  setBoolFlag (values, WavAudioFormat::acidDiskBased, 0x08);
887  setBoolFlag (values, WavAudioFormat::acidizerFlag, 0x10);
888 
889  if (flags & 0x02) // root note set
890  values[WavAudioFormat::acidRootNote] = String (ByteOrder::swapIfBigEndian (rootNote));
891 
892  values[WavAudioFormat::acidBeats] = String (ByteOrder::swapIfBigEndian (numBeats));
893  values[WavAudioFormat::acidDenominator] = String (ByteOrder::swapIfBigEndian (meterDenominator));
894  values[WavAudioFormat::acidNumerator] = String (ByteOrder::swapIfBigEndian (meterNumerator));
895  values[WavAudioFormat::acidTempo] = String (swapFloatByteOrder (tempo));
896  }
897 
898  void setBoolFlag (StringMap& values, const char* name, uint32 mask) const
899  {
900  values[name] = (flags & ByteOrder::swapIfBigEndian (mask)) ? "1" : "0";
901  }
902 
903  static uint32 getFlagIfPresent (const StringMap& values, const char* name, uint32 flag)
904  {
905  return getValueWithDefault (values, name).getIntValue() != 0 ? ByteOrder::swapIfBigEndian (flag) : 0;
906  }
907 
908  static float swapFloatByteOrder (const float x) noexcept
909  {
910  #ifdef JUCE_BIG_ENDIAN
911  union { uint32 asInt; float asFloat; } n;
912  n.asFloat = x;
913  n.asInt = ByteOrder::swap (n.asInt);
914  return n.asFloat;
915  #else
916  return x;
917  #endif
918  }
919 
920  uint32 flags;
921  uint16 rootNote;
922  uint16 reserved1;
923  float reserved2;
924  uint32 numBeats;
925  uint16 meterDenominator;
926  uint16 meterNumerator;
927  float tempo;
928 
929  } JUCE_PACKED;
930 
931  //==============================================================================
932  struct TracktionChunk
933  {
934  static MemoryBlock createFrom (const StringMap& values)
935  {
936  MemoryOutputStream out;
937  auto s = getValueWithDefault (values, WavAudioFormat::tracktionLoopInfo);
938 
939  if (s.isNotEmpty())
940  {
941  out.writeString (s);
942 
943  if ((out.getDataSize() & 1) != 0)
944  out.writeByte (0);
945  }
946 
947  return out.getMemoryBlock();
948  }
949  };
950 
951  //==============================================================================
952  namespace IXMLChunk
953  {
954  static const std::unordered_set<String> aswgMetadataKeys
955  {
1039  };
1040 
1041  static void addToMetadata (StringMap& destValues, const String& source)
1042  {
1043  if (auto xml = parseXML (source))
1044  {
1045  if (xml->hasTagName ("BWFXML"))
1046  {
1047  if (const auto* entry = xml->getChildByName (WavAudioFormat::aswgVersion))
1048  destValues[WavAudioFormat::aswgVersion] = entry->getAllSubText();
1049 
1050  if (const auto* aswgElement = xml->getChildByName ("ASWG"))
1051  {
1052  for (const auto* entry : aswgElement->getChildIterator())
1053  {
1054  const auto& tag = entry->getTagName();
1055 
1056  if (aswgMetadataKeys.find (tag) != aswgMetadataKeys.end())
1057  destValues[tag] = entry->getAllSubText();
1058  }
1059  }
1060  }
1061  }
1062  }
1063 
1064  static MemoryBlock createFrom (const StringMap& values)
1065  {
1066  auto createTextElement = [] (const StringRef& key, const StringRef& value)
1067  {
1068  auto* elem = new XmlElement (key);
1069  elem->addTextElement (value);
1070  return elem;
1071  };
1072 
1073  std::unique_ptr<XmlElement> aswgElement;
1074 
1075  for (const auto& pair : values)
1076  {
1077  if (aswgMetadataKeys.find (pair.first) != aswgMetadataKeys.end())
1078  {
1079  if (aswgElement == nullptr)
1080  aswgElement = std::make_unique<XmlElement> ("ASWG");
1081 
1082  aswgElement->addChildElement (createTextElement (pair.first, pair.second));
1083  }
1084  }
1085 
1086  MemoryOutputStream outputStream;
1087 
1088  if (aswgElement != nullptr)
1089  {
1090  XmlElement xml ("BWFXML");
1091  auto aswgVersion = getValueWithDefault (values, WavAudioFormat::aswgVersion, "3.01");
1092  xml.addChildElement (createTextElement (WavAudioFormat::aswgVersion, aswgVersion));
1093  xml.addChildElement (aswgElement.release());
1094  xml.writeTo (outputStream);
1095  outputStream.writeRepeatedByte (0, outputStream.getDataSize());
1096  }
1097 
1098  return outputStream.getMemoryBlock();
1099  }
1100  }
1101 
1102  //==============================================================================
1103  namespace AXMLChunk
1104  {
1105  static void addToMetadata (StringMap& destValues, const String& source)
1106  {
1107  if (auto xml = parseXML (source))
1108  {
1109  if (xml->hasTagName ("ebucore:ebuCoreMain"))
1110  {
1111  if (auto xml2 = xml->getChildByName ("ebucore:coreMetadata"))
1112  {
1113  if (auto xml3 = xml2->getChildByName ("ebucore:identifier"))
1114  {
1115  if (auto xml4 = xml3->getChildByName ("dc:identifier"))
1116  {
1117  auto ISRCCode = xml4->getAllSubText().fromFirstOccurrenceOf ("ISRC:", false, true);
1118 
1119  if (ISRCCode.isNotEmpty())
1120  {
1121  // We set ISRC here for backwards compatibility.
1122  // If the INFO 'source' field is set in the info chunk, then the
1123  // value for this key will be overwritten later.
1125  }
1126  }
1127  }
1128  }
1129  }
1130  }
1131  }
1132 
1133  static MemoryBlock createFrom (const StringMap& values)
1134  {
1135  // Use the new ISRC key if it is present, but fall back to the
1136  // INFO 'source' value for backwards compatibility.
1137  auto ISRC = getValueWithDefault (values,
1139  getValueWithDefault (values, WavAudioFormat::riffInfoSource));
1140 
1141  MemoryOutputStream xml;
1142 
1143  if (ISRC.isNotEmpty())
1144  {
1145  // If you are trying to set the ISRC, make sure that you are using
1146  // WavAudioFormat::internationalStandardRecordingCode as the metadata key,
1147  // and that the value is 12 characters long. If you are trying to set the
1148  // 'source' field in the INFO chunk, set the
1149  // WavAudioFormat::internationalStandardRecordingCode metadata field to the
1150  // empty string to silence this assertion.
1151  jassert (ISRC.length() == 12);
1152 
1153  xml << "<ebucore:ebuCoreMain xmlns:dc=\" http://purl.org/dc/elements/1.1/\" "
1154  "xmlns:ebucore=\"urn:ebu:metadata-schema:ebuCore_2012\">"
1155  "<ebucore:coreMetadata>"
1156  "<ebucore:identifier typeLabel=\"GUID\" "
1157  "typeDefinition=\"Globally Unique Identifier\" "
1158  "formatLabel=\"ISRC\" "
1159  "formatDefinition=\"International Standard Recording Code\" "
1160  "formatLink=\"http://www.ebu.ch/metadata/cs/ebu_IdentifierTypeCodeCS.xml#3.7\">"
1161  "<dc:identifier>ISRC:" << ISRC << "</dc:identifier>"
1162  "</ebucore:identifier>"
1163  "</ebucore:coreMetadata>"
1164  "</ebucore:ebuCoreMain>";
1165 
1166  xml.writeRepeatedByte (0, xml.getDataSize()); // ensures even size, null termination and room for future growing
1167  }
1168 
1169  return xml.getMemoryBlock();
1170  }
1171  }
1172 
1173  //==============================================================================
1174  struct ExtensibleWavSubFormat
1175  {
1176  uint32 data1;
1177  uint16 data2;
1178  uint16 data3;
1179  uint8 data4[8];
1180 
1181  bool operator== (const ExtensibleWavSubFormat& other) const noexcept { return memcmp (this, &other, sizeof (*this)) == 0; }
1182  bool operator!= (const ExtensibleWavSubFormat& other) const noexcept { return ! operator== (other); }
1183 
1184  } JUCE_PACKED;
1185 
1186  static const ExtensibleWavSubFormat pcmFormat = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
1187  static const ExtensibleWavSubFormat IEEEFloatFormat = { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
1188  static const ExtensibleWavSubFormat ambisonicFormat = { 0x00000001, 0x0721, 0x11d3, { 0x86, 0x44, 0xC8, 0xC1, 0xCA, 0x00, 0x00, 0x00 } };
1189 
1190  struct DataSize64Chunk // chunk ID = 'ds64' if data size > 0xffffffff, 'JUNK' otherwise
1191  {
1192  uint32 riffSizeLow; // low 4 byte size of RF64 block
1193  uint32 riffSizeHigh; // high 4 byte size of RF64 block
1194  uint32 dataSizeLow; // low 4 byte size of data chunk
1195  uint32 dataSizeHigh; // high 4 byte size of data chunk
1196  uint32 sampleCountLow; // low 4 byte sample count of fact chunk
1197  uint32 sampleCountHigh; // high 4 byte sample count of fact chunk
1198  uint32 tableLength; // number of valid entries in array 'table'
1199  } JUCE_PACKED;
1200 
1201  #if JUCE_MSVC
1202  #pragma pack (pop)
1203  #endif
1204 }
1205 
1206 //==============================================================================
1207 class WavAudioFormatReader final : public AudioFormatReader
1208 {
1209 public:
1210  WavAudioFormatReader (InputStream* in) : AudioFormatReader (in, wavFormatName)
1211  {
1212  using namespace WavFileHelpers;
1213  uint64 len = 0, end = 0;
1214  int cueNoteIndex = 0;
1215  int cueLabelIndex = 0;
1216  int cueRegionIndex = 0;
1217 
1218  StringMap dict;
1219 
1220  auto streamStartPos = input->getPosition();
1221  auto firstChunkType = input->readInt();
1222 
1223  if (firstChunkType == chunkName ("RF64"))
1224  {
1225  input->skipNextBytes (4); // size is -1 for RF64
1226  isRF64 = true;
1227  }
1228  else if (firstChunkType == chunkName ("RIFF"))
1229  {
1230  len = (uint64) (uint32) input->readInt();
1231  end = len + (uint64) input->getPosition();
1232  }
1233  else
1234  {
1235  return;
1236  }
1237 
1238  auto startOfRIFFChunk = input->getPosition();
1239 
1240  if (input->readInt() == chunkName ("WAVE"))
1241  {
1242  if (isRF64 && input->readInt() == chunkName ("ds64"))
1243  {
1244  auto length = (uint32) input->readInt();
1245 
1246  if (length < 28)
1247  return;
1248 
1249  auto chunkEnd = input->getPosition() + length + (length & 1);
1250  len = (uint64) input->readInt64();
1251  end = len + (uint64) startOfRIFFChunk;
1252  dataLength = input->readInt64();
1253  input->setPosition (chunkEnd);
1254  }
1255 
1256  while ((uint64) input->getPosition() < end && ! input->isExhausted())
1257  {
1258  auto chunkType = input->readInt();
1259  auto length = (uint32) input->readInt();
1260  auto chunkEnd = input->getPosition() + length + (length & 1);
1261 
1262  if (chunkType == chunkName ("fmt "))
1263  {
1264  // read the format chunk
1265  auto format = (unsigned short) input->readShort();
1266  numChannels = (unsigned int) input->readShort();
1267  sampleRate = input->readInt();
1268  auto bytesPerSec = input->readInt();
1269  input->skipNextBytes (2);
1270  bitsPerSample = (unsigned int) (int) input->readShort();
1271 
1272  if (bitsPerSample > 64 && (int) sampleRate != 0)
1273  {
1274  bytesPerFrame = bytesPerSec / (int) sampleRate;
1275 
1276  if (numChannels != 0)
1277  bitsPerSample = 8 * (unsigned int) bytesPerFrame / numChannels;
1278  }
1279  else
1280  {
1281  bytesPerFrame = (int) (numChannels * bitsPerSample / 8);
1282  }
1283 
1284  if (format == 3)
1285  {
1286  usesFloatingPointData = true;
1287  }
1288  else if (format == 0xfffe) // WAVE_FORMAT_EXTENSIBLE
1289  {
1290  if (length < 40) // too short
1291  {
1292  bytesPerFrame = 0;
1293  }
1294  else
1295  {
1296  input->skipNextBytes (4); // skip over size and bitsPerSample
1297  auto channelMask = input->readInt();
1298  dict["ChannelMask"] = String (channelMask);
1299  channelLayout = getChannelLayoutFromMask (channelMask, numChannels);
1300 
1301  ExtensibleWavSubFormat subFormat;
1302  subFormat.data1 = (uint32) input->readInt();
1303  subFormat.data2 = (uint16) input->readShort();
1304  subFormat.data3 = (uint16) input->readShort();
1305  input->read (subFormat.data4, sizeof (subFormat.data4));
1306 
1307  if (subFormat == IEEEFloatFormat)
1308  usesFloatingPointData = true;
1309  else if (subFormat != pcmFormat && subFormat != ambisonicFormat)
1310  bytesPerFrame = 0;
1311  }
1312  }
1313  else if (format == 0x674f // WAVE_FORMAT_OGG_VORBIS_MODE_1
1314  || format == 0x6750 // WAVE_FORMAT_OGG_VORBIS_MODE_2
1315  || format == 0x6751 // WAVE_FORMAT_OGG_VORBIS_MODE_3
1316  || format == 0x676f // WAVE_FORMAT_OGG_VORBIS_MODE_1_PLUS
1317  || format == 0x6770 // WAVE_FORMAT_OGG_VORBIS_MODE_2_PLUS
1318  || format == 0x6771) // WAVE_FORMAT_OGG_VORBIS_MODE_3_PLUS
1319  {
1320  isSubformatOggVorbis = true;
1321  sampleRate = 0; // to mark the wav reader as failed
1322  input->setPosition (streamStartPos);
1323  return;
1324  }
1325  else if (format != 1)
1326  {
1327  bytesPerFrame = 0;
1328  }
1329  }
1330  else if (chunkType == chunkName ("data"))
1331  {
1332  if (isRF64)
1333  {
1334  if (dataLength > 0)
1335  chunkEnd = input->getPosition() + dataLength + (dataLength & 1);
1336  }
1337  else
1338  {
1339  dataLength = length;
1340  }
1341 
1342  dataChunkStart = input->getPosition();
1343  lengthInSamples = (bytesPerFrame > 0) ? (dataLength / bytesPerFrame) : 0;
1344  }
1345  else if (chunkType == chunkName ("bext"))
1346  {
1347  bwavChunkStart = input->getPosition();
1348  bwavSize = length;
1349 
1350  HeapBlock<BWAVChunk> bwav;
1351  bwav.calloc (jmax ((size_t) length + 1, sizeof (BWAVChunk)), 1);
1352  input->read (bwav, (int) length);
1353  bwav->copyTo (dict, (int) length);
1354  }
1355  else if (chunkType == chunkName ("smpl"))
1356  {
1357  HeapBlock<SMPLChunk> smpl;
1358  smpl.calloc (jmax ((size_t) length + 1, sizeof (SMPLChunk)), 1);
1359  input->read (smpl, (int) length);
1360  smpl->copyTo (dict, (int) length);
1361  }
1362  else if (chunkType == chunkName ("inst") || chunkType == chunkName ("INST")) // need to check which...
1363  {
1364  HeapBlock<InstChunk> inst;
1365  inst.calloc (jmax ((size_t) length + 1, sizeof (InstChunk)), 1);
1366  input->read (inst, (int) length);
1367  inst->copyTo (dict);
1368  }
1369  else if (chunkType == chunkName ("cue "))
1370  {
1371  HeapBlock<CueChunk> cue;
1372  cue.calloc (jmax ((size_t) length + 1, sizeof (CueChunk)), 1);
1373  input->read (cue, (int) length);
1374  cue->copyTo (dict, (int) length);
1375  }
1376  else if (chunkType == chunkName ("axml"))
1377  {
1378  MemoryBlock axml;
1379  input->readIntoMemoryBlock (axml, (ssize_t) length);
1380  AXMLChunk::addToMetadata (dict, axml.toString());
1381  }
1382  else if (chunkType == chunkName ("iXML"))
1383  {
1384  MemoryBlock ixml;
1385  input->readIntoMemoryBlock (ixml, (ssize_t) length);
1386  IXMLChunk::addToMetadata (dict, ixml.toString());
1387  }
1388  else if (chunkType == chunkName ("LIST"))
1389  {
1390  auto subChunkType = input->readInt();
1391 
1392  if (subChunkType == chunkName ("info") || subChunkType == chunkName ("INFO"))
1393  {
1394  ListInfoChunk::addToMetadata (dict, *input, chunkEnd);
1395  }
1396  else if (subChunkType == chunkName ("adtl"))
1397  {
1398  while (input->getPosition() < chunkEnd)
1399  {
1400  auto adtlChunkType = input->readInt();
1401  auto adtlLength = (uint32) input->readInt();
1402  auto adtlChunkEnd = input->getPosition() + (adtlLength + (adtlLength & 1));
1403 
1404  if (adtlChunkType == chunkName ("labl") || adtlChunkType == chunkName ("note"))
1405  {
1406  String prefix;
1407 
1408  if (adtlChunkType == chunkName ("labl"))
1409  prefix << "CueLabel" << cueLabelIndex++;
1410  else if (adtlChunkType == chunkName ("note"))
1411  prefix << "CueNote" << cueNoteIndex++;
1412 
1413  auto identifier = (uint32) input->readInt();
1414  auto stringLength = (int) adtlLength - 4;
1415 
1416  MemoryBlock textBlock;
1417  input->readIntoMemoryBlock (textBlock, stringLength);
1418 
1419  dict[prefix + "Identifier"] = String (identifier);
1420  dict[prefix + "Text"] = textBlock.toString();
1421  }
1422  else if (adtlChunkType == chunkName ("ltxt"))
1423  {
1424  auto prefix = "CueRegion" + String (cueRegionIndex++);
1425  auto identifier = (uint32) input->readInt();
1426  auto sampleLength = (uint32) input->readInt();
1427  auto purpose = (uint32) input->readInt();
1428  auto country = (uint16) input->readShort();
1429  auto language = (uint16) input->readShort();
1430  auto dialect = (uint16) input->readShort();
1431  auto codePage = (uint16) input->readShort();
1432  auto stringLength = adtlLength - 20;
1433 
1434  MemoryBlock textBlock;
1435  input->readIntoMemoryBlock (textBlock, (int) stringLength);
1436 
1437  dict[prefix + "Identifier"] = String (identifier);
1438  dict[prefix + "SampleLength"] = String (sampleLength);
1439  dict[prefix + "Purpose"] = String (purpose);
1440  dict[prefix + "Country"] = String (country);
1441  dict[prefix + "Language"] = String (language);
1442  dict[prefix + "Dialect"] = String (dialect);
1443  dict[prefix + "CodePage"] = String (codePage);
1444  dict[prefix + "Text"] = textBlock.toString();
1445  }
1446 
1447  input->setPosition (adtlChunkEnd);
1448  }
1449  }
1450  }
1451  else if (chunkType == chunkName ("acid"))
1452  {
1453  AcidChunk (*input, length).addToMetadata (dict);
1454  }
1455  else if (chunkType == chunkName ("Trkn"))
1456  {
1457  MemoryBlock tracktion;
1458  input->readIntoMemoryBlock (tracktion, (ssize_t) length);
1459  dict[WavAudioFormat::tracktionLoopInfo] = tracktion.toString();
1460  }
1461  else if (chunkEnd <= input->getPosition())
1462  {
1463  break;
1464  }
1465 
1466  input->setPosition (chunkEnd);
1467  }
1468  }
1469 
1470  if (cueLabelIndex > 0) dict["NumCueLabels"] = String (cueLabelIndex);
1471  if (cueNoteIndex > 0) dict["NumCueNotes"] = String (cueNoteIndex);
1472  if (cueRegionIndex > 0) dict["NumCueRegions"] = String (cueRegionIndex);
1473  if (dict.size() > 0) dict["MetaDataSource"] = "WAV";
1474 
1475  metadataValues.addUnorderedMap (dict);
1476  }
1477 
1478  //==============================================================================
1479  bool readSamples (int* const* destSamples, int numDestChannels, int startOffsetInDestBuffer,
1480  int64 startSampleInFile, int numSamples) override
1481  {
1482  clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
1483  startSampleInFile, numSamples, lengthInSamples);
1484 
1485  if (numSamples <= 0)
1486  return true;
1487 
1488  input->setPosition (dataChunkStart + startSampleInFile * bytesPerFrame);
1489 
1490  while (numSamples > 0)
1491  {
1492  const int tempBufSize = 480 * 3 * 4; // (keep this a multiple of 3)
1493  char tempBuffer[tempBufSize];
1494 
1495  auto numThisTime = jmin (tempBufSize / bytesPerFrame, numSamples);
1496  auto bytesRead = input->read (tempBuffer, numThisTime * bytesPerFrame);
1497 
1498  if (bytesRead < numThisTime * bytesPerFrame)
1499  {
1500  jassert (bytesRead >= 0);
1501  zeromem (tempBuffer + bytesRead, (size_t) (numThisTime * bytesPerFrame - bytesRead));
1502  }
1503 
1504  copySampleData (bitsPerSample, usesFloatingPointData,
1505  destSamples, startOffsetInDestBuffer, numDestChannels,
1506  tempBuffer, (int) numChannels, numThisTime);
1507 
1508  startOffsetInDestBuffer += numThisTime;
1509  numSamples -= numThisTime;
1510  }
1511 
1512  return true;
1513  }
1514 
1515  static void copySampleData (unsigned int numBitsPerSample, const bool floatingPointData,
1516  int* const* destSamples, int startOffsetInDestBuffer, int numDestChannels,
1517  const void* sourceData, int numberOfChannels, int numSamples) noexcept
1518  {
1519  switch (numBitsPerSample)
1520  {
1521  case 8: ReadHelper<AudioData::Int32, AudioData::UInt8, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); break;
1522  case 16: ReadHelper<AudioData::Int32, AudioData::Int16, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); break;
1523  case 24: ReadHelper<AudioData::Int32, AudioData::Int24, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples); break;
1524  case 32: if (floatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
1525  else ReadHelper<AudioData::Int32, AudioData::Int32, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
1526  break;
1527  default: jassertfalse; break;
1528  }
1529  }
1530 
1531  //==============================================================================
1532  AudioChannelSet getChannelLayout() override
1533  {
1534  if (channelLayout.size() == static_cast<int> (numChannels))
1535  return channelLayout;
1536 
1537  return WavFileHelpers::canonicalWavChannelSet (static_cast<int> (numChannels));
1538  }
1539 
1540  static AudioChannelSet getChannelLayoutFromMask (int dwChannelMask, size_t totalNumChannels)
1541  {
1542  AudioChannelSet wavFileChannelLayout;
1543 
1544  // AudioChannelSet and wav's dwChannelMask are compatible
1545  BigInteger channelBits (dwChannelMask);
1546 
1547  for (auto bit = channelBits.findNextSetBit (0); bit >= 0; bit = channelBits.findNextSetBit (bit + 1))
1548  wavFileChannelLayout.addChannel (static_cast<AudioChannelSet::ChannelType> (bit + 1));
1549 
1550  // channel layout and number of channels do not match
1551  if (wavFileChannelLayout.size() != static_cast<int> (totalNumChannels))
1552  {
1553  // for backward compatibility with old wav files, assume 1 or 2
1554  // channel wav files are mono/stereo respectively
1555  if (totalNumChannels <= 2 && dwChannelMask == 0)
1556  wavFileChannelLayout = AudioChannelSet::canonicalChannelSet (static_cast<int> (totalNumChannels));
1557  else
1558  {
1559  auto discreteSpeaker = static_cast<int> (AudioChannelSet::discreteChannel0);
1560 
1561  while (wavFileChannelLayout.size() < static_cast<int> (totalNumChannels))
1562  wavFileChannelLayout.addChannel (static_cast<AudioChannelSet::ChannelType> (discreteSpeaker++));
1563  }
1564  }
1565 
1566  return wavFileChannelLayout;
1567  }
1568 
1569  int64 bwavChunkStart = 0, bwavSize = 0;
1570  int64 dataChunkStart = 0, dataLength = 0;
1571  int bytesPerFrame = 0;
1572  bool isRF64 = false;
1573  bool isSubformatOggVorbis = false;
1574 
1575  AudioChannelSet channelLayout;
1576 
1577 private:
1578  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatReader)
1579 };
1580 
1581 //==============================================================================
1582 class WavAudioFormatWriter final : public AudioFormatWriter
1583 {
1584 public:
1585  WavAudioFormatWriter (OutputStream* const out, const double rate,
1586  const AudioChannelSet& channelLayoutToUse, const unsigned int bits,
1587  const StringPairArray& metadataValues)
1588  : AudioFormatWriter (out, wavFormatName, rate, channelLayoutToUse, bits)
1589  {
1590  using namespace WavFileHelpers;
1591 
1592  if (metadataValues.size() > 0)
1593  {
1594  // The meta data should have been sanitised for the WAV format.
1595  // If it was originally sourced from an AIFF file the MetaDataSource
1596  // key should be removed (or set to "WAV") once this has been done
1597  jassert (metadataValues.getValue ("MetaDataSource", "None") != "AIFF");
1598 
1599  const auto map = toMap (metadataValues);
1600 
1601  bwavChunk = BWAVChunk::createFrom (map);
1602  ixmlChunk = IXMLChunk::createFrom (map);
1603  axmlChunk = AXMLChunk::createFrom (map);
1604  smplChunk = SMPLChunk::createFrom (map);
1605  instChunk = InstChunk::createFrom (map);
1606  cueChunk = CueChunk ::createFrom (map);
1607  listChunk = ListChunk::createFrom (map);
1608  listInfoChunk = ListInfoChunk::createFrom (map);
1609  acidChunk = AcidChunk::createFrom (map);
1610  trckChunk = TracktionChunk::createFrom (map);
1611  }
1612 
1613  headerPosition = out->getPosition();
1614  writeHeader();
1615  }
1616 
1617  ~WavAudioFormatWriter() override
1618  {
1619  writeHeader();
1620  }
1621 
1622  //==============================================================================
1623  bool write (const int** data, int numSamples) override
1624  {
1625  jassert (numSamples >= 0);
1626  jassert (data != nullptr && *data != nullptr); // the input must contain at least one channel!
1627 
1628  if (writeFailed)
1629  return false;
1630 
1631  auto bytes = numChannels * (size_t) numSamples * bitsPerSample / 8;
1632  tempBlock.ensureSize (bytes, false);
1633 
1634  switch (bitsPerSample)
1635  {
1636  case 8: WriteHelper<AudioData::UInt8, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break;
1637  case 16: WriteHelper<AudioData::Int16, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break;
1638  case 24: WriteHelper<AudioData::Int24, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break;
1639  case 32: WriteHelper<AudioData::Int32, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples); break;
1640  default: jassertfalse; break;
1641  }
1642 
1643  if (! output->write (tempBlock.getData(), bytes))
1644  {
1645  // failed to write to disk, so let's try writing the header.
1646  // If it's just run out of disk space, then if it does manage
1647  // to write the header, we'll still have a usable file..
1648  writeHeader();
1649  writeFailed = true;
1650  return false;
1651  }
1652 
1653  bytesWritten += bytes;
1654  lengthInSamples += (uint64) numSamples;
1655  return true;
1656  }
1657 
1658  bool flush() override
1659  {
1660  auto lastWritePos = output->getPosition();
1661  writeHeader();
1662 
1663  if (output->setPosition (lastWritePos))
1664  return true;
1665 
1666  // if this fails, you've given it an output stream that can't seek! It needs
1667  // to be able to seek back to write the header
1668  jassertfalse;
1669  return false;
1670  }
1671 
1672 private:
1673  MemoryBlock tempBlock, bwavChunk, ixmlChunk, axmlChunk, smplChunk, instChunk, cueChunk, listChunk, listInfoChunk, acidChunk, trckChunk;
1674  uint64 lengthInSamples = 0, bytesWritten = 0;
1675  int64 headerPosition = 0;
1676  bool writeFailed = false;
1677 
1678  void writeHeader()
1679  {
1680  if ((bytesWritten & 1) != 0) // pad to an even length
1681  output->writeByte (0);
1682 
1683  using namespace WavFileHelpers;
1684 
1685  if (headerPosition != output->getPosition() && ! output->setPosition (headerPosition))
1686  {
1687  // if this fails, you've given it an output stream that can't seek! It needs to be
1688  // able to seek back to go back and write the header after the data has been written.
1689  jassertfalse;
1690  return;
1691  }
1692 
1693  const size_t bytesPerFrame = numChannels * bitsPerSample / 8;
1694  uint64 audioDataSize = bytesPerFrame * lengthInSamples;
1695  auto channelMask = getChannelMaskFromChannelLayout (channelLayout);
1696 
1697  const bool isRF64 = (bytesWritten >= 0x100000000LL);
1698  const bool isWaveFmtEx = isRF64 || (channelMask != 0);
1699 
1700  int64 riffChunkSize = (int64) (4 /* 'RIFF' */ + 8 + 40 /* WAVEFORMATEX */
1701  + 8 + audioDataSize + (audioDataSize & 1)
1702  + chunkSize (bwavChunk)
1703  + chunkSize (ixmlChunk)
1704  + chunkSize (axmlChunk)
1705  + chunkSize (smplChunk)
1706  + chunkSize (instChunk)
1707  + chunkSize (cueChunk)
1708  + chunkSize (listChunk)
1709  + chunkSize (listInfoChunk)
1710  + chunkSize (acidChunk)
1711  + chunkSize (trckChunk)
1712  + (8 + 28)); // (ds64 chunk)
1713 
1714  riffChunkSize += (riffChunkSize & 1);
1715 
1716  if (isRF64)
1717  writeChunkHeader (chunkName ("RF64"), -1);
1718  else
1719  writeChunkHeader (chunkName ("RIFF"), (int) riffChunkSize);
1720 
1721  output->writeInt (chunkName ("WAVE"));
1722 
1723  if (! isRF64)
1724  {
1725  #if ! JUCE_WAV_DO_NOT_PAD_HEADER_SIZE
1726  /* NB: This junk chunk is added for padding, so that the header is a fixed size
1727  regardless of whether it's RF64 or not. That way, we can begin recording a file,
1728  and when it's finished, can go back and write either a RIFF or RF64 header,
1729  depending on whether more than 2^32 samples were written.
1730 
1731  The JUCE_WAV_DO_NOT_PAD_HEADER_SIZE macro allows you to disable this feature in case
1732  you need to create files for crappy WAV players with bugs that stop them skipping chunks
1733  which they don't recognise. But DO NOT USE THIS option unless you really have no choice,
1734  because it means that if you write more than 2^32 samples to the file, you'll corrupt it.
1735  */
1736  writeChunkHeader (chunkName ("JUNK"), 28 + (isWaveFmtEx? 0 : 24));
1737  output->writeRepeatedByte (0, 28 /* ds64 */ + (isWaveFmtEx? 0 : 24));
1738  #endif
1739  }
1740  else
1741  {
1742  #if JUCE_WAV_DO_NOT_PAD_HEADER_SIZE
1743  // If you disable padding, then you MUST NOT write more than 2^32 samples to a file.
1744  jassertfalse;
1745  #endif
1746 
1747  writeChunkHeader (chunkName ("ds64"), 28); // chunk size for uncompressed data (no table)
1748  output->writeInt64 (riffChunkSize);
1749  output->writeInt64 ((int64) audioDataSize);
1750  output->writeRepeatedByte (0, 12);
1751  }
1752 
1753  if (isWaveFmtEx)
1754  {
1755  writeChunkHeader (chunkName ("fmt "), 40);
1756  output->writeShort ((short) (uint16) 0xfffe); // WAVE_FORMAT_EXTENSIBLE
1757  }
1758  else
1759  {
1760  writeChunkHeader (chunkName ("fmt "), 16);
1761  output->writeShort (bitsPerSample < 32 ? (short) 1 /*WAVE_FORMAT_PCM*/
1762  : (short) 3 /*WAVE_FORMAT_IEEE_FLOAT*/);
1763  }
1764 
1765  output->writeShort ((short) numChannels);
1766  output->writeInt ((int) sampleRate);
1767  output->writeInt ((int) ((double) bytesPerFrame * sampleRate)); // nAvgBytesPerSec
1768  output->writeShort ((short) bytesPerFrame); // nBlockAlign
1769  output->writeShort ((short) bitsPerSample); // wBitsPerSample
1770 
1771  if (isWaveFmtEx)
1772  {
1773  output->writeShort (22); // cbSize (size of the extension)
1774  output->writeShort ((short) bitsPerSample); // wValidBitsPerSample
1775  output->writeInt (channelMask);
1776 
1777  const ExtensibleWavSubFormat& subFormat = bitsPerSample < 32 ? pcmFormat : IEEEFloatFormat;
1778 
1779  output->writeInt ((int) subFormat.data1);
1780  output->writeShort ((short) subFormat.data2);
1781  output->writeShort ((short) subFormat.data3);
1782  output->write (subFormat.data4, sizeof (subFormat.data4));
1783  }
1784 
1785  writeChunk (bwavChunk, chunkName ("bext"));
1786  writeChunk (ixmlChunk, chunkName ("iXML"));
1787  writeChunk (axmlChunk, chunkName ("axml"));
1788  writeChunk (smplChunk, chunkName ("smpl"));
1789  writeChunk (instChunk, chunkName ("inst"), 7);
1790  writeChunk (cueChunk, chunkName ("cue "));
1791  writeChunk (listChunk, chunkName ("LIST"));
1792  writeChunk (listInfoChunk, chunkName ("LIST"));
1793  writeChunk (acidChunk, chunkName ("acid"));
1794  writeChunk (trckChunk, chunkName ("Trkn"));
1795 
1796  writeChunkHeader (chunkName ("data"), isRF64 ? -1 : (int) (lengthInSamples * bytesPerFrame));
1797 
1798  usesFloatingPointData = (bitsPerSample == 32);
1799  }
1800 
1801  static size_t chunkSize (const MemoryBlock& data) noexcept { return data.isEmpty() ? 0 : (8 + data.getSize()); }
1802 
1803  void writeChunkHeader (int chunkType, int size) const
1804  {
1805  output->writeInt (chunkType);
1806  output->writeInt (size);
1807  }
1808 
1809  void writeChunk (const MemoryBlock& data, int chunkType, int size = 0) const
1810  {
1811  if (! data.isEmpty())
1812  {
1813  writeChunkHeader (chunkType, size != 0 ? size : (int) data.getSize());
1814  *output << data;
1815  }
1816  }
1817 
1818  static int getChannelMaskFromChannelLayout (const AudioChannelSet& layout)
1819  {
1820  if (layout.isDiscreteLayout())
1821  return 0;
1822 
1823  // Don't add an extended format chunk for mono and stereo. Basically, all wav players
1824  // interpret a wav file with only one or two channels to be mono or stereo anyway.
1825  if (layout == AudioChannelSet::mono() || layout == AudioChannelSet::stereo())
1826  return 0;
1827 
1828  auto channels = layout.getChannelTypes();
1829  auto wavChannelMask = 0;
1830 
1831  for (auto channel : channels)
1832  {
1833  int wavChannelBit = static_cast<int> (channel) - 1;
1834  jassert (wavChannelBit >= 0 && wavChannelBit <= 31);
1835 
1836  wavChannelMask |= (1 << wavChannelBit);
1837  }
1838 
1839  return wavChannelMask;
1840  }
1841 
1842  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatWriter)
1843 };
1844 
1845 //==============================================================================
1846 class MemoryMappedWavReader final : public MemoryMappedAudioFormatReader
1847 {
1848 public:
1849  MemoryMappedWavReader (const File& wavFile, const WavAudioFormatReader& reader)
1850  : MemoryMappedAudioFormatReader (wavFile, reader, reader.dataChunkStart,
1851  reader.dataLength, reader.bytesPerFrame)
1852  {
1853  }
1854 
1855  bool readSamples (int* const* destSamples, int numDestChannels, int startOffsetInDestBuffer,
1856  int64 startSampleInFile, int numSamples) override
1857  {
1858  clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
1859  startSampleInFile, numSamples, lengthInSamples);
1860 
1861  if (numSamples <= 0)
1862  return true;
1863 
1864  if (map == nullptr || ! mappedSection.contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
1865  {
1866  jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read.
1867  return false;
1868  }
1869 
1870  WavAudioFormatReader::copySampleData (bitsPerSample, usesFloatingPointData,
1871  destSamples, startOffsetInDestBuffer, numDestChannels,
1872  sampleToPointer (startSampleInFile), (int) numChannels, numSamples);
1873  return true;
1874  }
1875 
1876  void getSample (int64 sample, float* result) const noexcept override
1877  {
1878  auto num = (int) numChannels;
1879 
1880  if (map == nullptr || ! mappedSection.contains (sample))
1881  {
1882  jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read.
1883 
1884  zeromem (result, (size_t) num * sizeof (float));
1885  return;
1886  }
1887 
1888  auto dest = &result;
1889  auto source = sampleToPointer (sample);
1890 
1891  switch (bitsPerSample)
1892  {
1893  case 8: ReadHelper<AudioData::Float32, AudioData::UInt8, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num); break;
1894  case 16: ReadHelper<AudioData::Float32, AudioData::Int16, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num); break;
1895  case 24: ReadHelper<AudioData::Float32, AudioData::Int24, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num); break;
1896  case 32: if (usesFloatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
1897  else ReadHelper<AudioData::Float32, AudioData::Int32, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
1898  break;
1899  default: jassertfalse; break;
1900  }
1901  }
1902 
1903  void readMaxLevels (int64 startSampleInFile, int64 numSamples, Range<float>* results, int numChannelsToRead) override
1904  {
1905  numSamples = jmin (numSamples, lengthInSamples - startSampleInFile);
1906 
1907  if (map == nullptr || numSamples <= 0 || ! mappedSection.contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
1908  {
1909  jassert (numSamples <= 0); // you must make sure that the window contains all the samples you're going to attempt to read.
1910 
1911  for (int i = 0; i < numChannelsToRead; ++i)
1912  results[i] = {};
1913 
1914  return;
1915  }
1916 
1917  switch (bitsPerSample)
1918  {
1919  case 8: scanMinAndMax<AudioData::UInt8> (startSampleInFile, numSamples, results, numChannelsToRead); break;
1920  case 16: scanMinAndMax<AudioData::Int16> (startSampleInFile, numSamples, results, numChannelsToRead); break;
1921  case 24: scanMinAndMax<AudioData::Int24> (startSampleInFile, numSamples, results, numChannelsToRead); break;
1922  case 32: if (usesFloatingPointData) scanMinAndMax<AudioData::Float32> (startSampleInFile, numSamples, results, numChannelsToRead);
1923  else scanMinAndMax<AudioData::Int32> (startSampleInFile, numSamples, results, numChannelsToRead);
1924  break;
1925  default: jassertfalse; break;
1926  }
1927  }
1928 
1930 
1931 private:
1932  template <typename SampleType>
1933  void scanMinAndMax (int64 startSampleInFile, int64 numSamples, Range<float>* results, int numChannelsToRead) const noexcept
1934  {
1935  for (int i = 0; i < numChannelsToRead; ++i)
1936  results[i] = scanMinAndMaxInterleaved<SampleType, AudioData::LittleEndian> (i, startSampleInFile, numSamples);
1937  }
1938 
1939  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedWavReader)
1940 };
1941 
1942 //==============================================================================
1943 WavAudioFormat::WavAudioFormat() : AudioFormat (wavFormatName, ".wav .bwf") {}
1945 
1947 {
1948  return { 8000, 11025, 12000, 16000, 22050, 32000, 44100,
1949  48000, 88200, 96000, 176400, 192000, 352800, 384000 };
1950 }
1951 
1953 {
1954  return { 8, 16, 24, 32 };
1955 }
1956 
1957 bool WavAudioFormat::canDoStereo() { return true; }
1958 bool WavAudioFormat::canDoMono() { return true; }
1959 
1961 {
1962  auto channelTypes = channelSet.getChannelTypes();
1963 
1964  // When
1965  if (channelSet.isDiscreteLayout())
1966  return true;
1967 
1968  // WAV supports all channel types from left ... topRearRight
1969  for (auto channel : channelTypes)
1970  if (channel < AudioChannelSet::left || channel > AudioChannelSet::topRearRight)
1971  return false;
1972 
1973  return true;
1974 }
1975 
1976 AudioFormatReader* WavAudioFormat::createReaderFor (InputStream* sourceStream, bool deleteStreamIfOpeningFails)
1977 {
1978  std::unique_ptr<WavAudioFormatReader> r (new WavAudioFormatReader (sourceStream));
1979 
1980  #if JUCE_USE_OGGVORBIS
1981  if (r->isSubformatOggVorbis)
1982  {
1983  r->input = nullptr;
1984  return OggVorbisAudioFormat().createReaderFor (sourceStream, deleteStreamIfOpeningFails);
1985  }
1986  #endif
1987 
1988  if (r->sampleRate > 0 && r->numChannels > 0 && r->bytesPerFrame > 0 && r->bitsPerSample <= 32)
1989  return r.release();
1990 
1991  if (! deleteStreamIfOpeningFails)
1992  r->input = nullptr;
1993 
1994  return nullptr;
1995 }
1996 
1998 {
1999  return createMemoryMappedReader (file.createInputStream().release());
2000 }
2001 
2003 {
2004  if (fin != nullptr)
2005  {
2006  WavAudioFormatReader reader (fin);
2007 
2008  if (reader.lengthInSamples > 0)
2009  return new MemoryMappedWavReader (fin->getFile(), reader);
2010  }
2011 
2012  return nullptr;
2013 }
2014 
2016  unsigned int numChannels, int bitsPerSample,
2017  const StringPairArray& metadataValues, int qualityOptionIndex)
2018 {
2019  return createWriterFor (out, sampleRate, WavFileHelpers::canonicalWavChannelSet (static_cast<int> (numChannels)),
2020  bitsPerSample, metadataValues, qualityOptionIndex);
2021 }
2022 
2024  double sampleRate,
2025  const AudioChannelSet& channelLayout,
2026  int bitsPerSample,
2027  const StringPairArray& metadataValues,
2028  int /*qualityOptionIndex*/)
2029 {
2030  if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample) && isChannelLayoutSupported (channelLayout))
2031  return new WavAudioFormatWriter (out, sampleRate, channelLayout,
2032  (unsigned int) bitsPerSample, metadataValues);
2033 
2034  return nullptr;
2035 }
2036 
2037 namespace WavFileHelpers
2038 {
2039  static bool slowCopyWavFileWithNewMetadata (const File& file, const StringPairArray& metadata)
2040  {
2041  TemporaryFile tempFile (file);
2042  WavAudioFormat wav;
2043 
2044  std::unique_ptr<AudioFormatReader> reader (wav.createReaderFor (file.createInputStream().release(), true));
2045 
2046  if (reader != nullptr)
2047  {
2048  std::unique_ptr<OutputStream> outStream (tempFile.getFile().createOutputStream());
2049 
2050  if (outStream != nullptr)
2051  {
2052  std::unique_ptr<AudioFormatWriter> writer (wav.createWriterFor (outStream.get(), reader->sampleRate,
2053  reader->numChannels, (int) reader->bitsPerSample,
2054  metadata, 0));
2055 
2056  if (writer != nullptr)
2057  {
2058  outStream.release();
2059 
2060  bool ok = writer->writeFromAudioReader (*reader, 0, -1);
2061  writer.reset();
2062  reader.reset();
2063 
2064  return ok && tempFile.overwriteTargetFileWithTemporary();
2065  }
2066  }
2067  }
2068 
2069  return false;
2070  }
2071 }
2072 
2073 bool WavAudioFormat::replaceMetadataInFile (const File& wavFile, const StringPairArray& newMetadata)
2074 {
2075  using namespace WavFileHelpers;
2076 
2077  std::unique_ptr<WavAudioFormatReader> reader (static_cast<WavAudioFormatReader*> (createReaderFor (wavFile.createInputStream().release(), true)));
2078 
2079  if (reader != nullptr)
2080  {
2081  auto bwavPos = reader->bwavChunkStart;
2082  auto bwavSize = reader->bwavSize;
2083  reader.reset();
2084 
2085  if (bwavSize > 0)
2086  {
2087  auto chunk = BWAVChunk::createFrom (toMap (newMetadata));
2088 
2089  if (chunk.getSize() <= (size_t) bwavSize)
2090  {
2091  // the new one will fit in the space available, so write it directly..
2092  auto oldSize = wavFile.getSize();
2093 
2094  {
2095  FileOutputStream out (wavFile);
2096 
2097  if (out.openedOk())
2098  {
2099  out.setPosition (bwavPos);
2100  out << chunk;
2101  out.setPosition (oldSize);
2102  }
2103  }
2104 
2105  jassert (wavFile.getSize() == oldSize);
2106  return true;
2107  }
2108  }
2109  }
2110 
2111  return slowCopyWavFileWithNewMetadata (wavFile, newMetadata);
2112 }
2113 
2114 
2115 //==============================================================================
2116 //==============================================================================
2117 #if JUCE_UNIT_TESTS
2118 
2119 struct WaveAudioFormatTests final : public UnitTest
2120 {
2121  WaveAudioFormatTests()
2122  : UnitTest ("Wave audio format tests", UnitTestCategories::audio)
2123  {}
2124 
2125  void runTest() override
2126  {
2127  beginTest ("Setting up metadata");
2128 
2129  auto metadataValues = toMap (WavAudioFormat::createBWAVMetadata ("description",
2130  "originator",
2131  "originatorRef",
2133  numTestAudioBufferSamples,
2134  "codingHistory"));
2135 
2136  for (int i = numElementsInArray (WavFileHelpers::ListInfoChunk::types); --i >= 0;)
2137  metadataValues[WavFileHelpers::ListInfoChunk::types[i]] = WavFileHelpers::ListInfoChunk::types[i];
2138 
2140 
2141  if (metadataValues.size() > 0)
2142  metadataValues["MetaDataSource"] = "WAV";
2143 
2144  const auto smplMetadata = createDefaultSMPLMetadata();
2145  metadataValues.insert (smplMetadata.cbegin(), smplMetadata.cend());
2146 
2147  WavAudioFormat format;
2148  MemoryBlock memoryBlock;
2149 
2150  StringPairArray metadataArray;
2151  metadataArray.addUnorderedMap (metadataValues);
2152 
2153  {
2154  beginTest ("Metadata can be written and read");
2155 
2156  const auto newMetadata = getMetadataAfterReading (format, writeToBlock (format, metadataArray));
2157  expect (newMetadata == metadataArray, "Somehow, the metadata is different!");
2158  }
2159 
2160  {
2161  beginTest ("Files containing a riff info source and an empty ISRC associate the source with the riffInfoSource key");
2162  StringPairArray meta;
2163  meta.addMap ({ { WavAudioFormat::riffInfoSource, "customsource" },
2165  const auto mb = writeToBlock (format, meta);
2166  checkPatternsPresent (mb, { "INFOISRC" });
2167  checkPatternsNotPresent (mb, { "ISRC:", "<ebucore" });
2168  const auto a = getMetadataAfterReading (format, mb);
2169  expect (a[WavAudioFormat::riffInfoSource] == "customsource");
2171  }
2172 
2173  {
2174  beginTest ("Files containing a riff info source and no ISRC associate the source with both keys "
2175  "for backwards compatibility");
2176  StringPairArray meta;
2177  meta.addMap ({ { WavAudioFormat::riffInfoSource, "customsource" } });
2178  const auto mb = writeToBlock (format, meta);
2179  checkPatternsPresent (mb, { "INFOISRC", "ISRC:customsource", "<ebucore" });
2180  const auto a = getMetadataAfterReading (format, mb);
2181  expect (a[WavAudioFormat::riffInfoSource] == "customsource");
2182  expect (a[WavAudioFormat::internationalStandardRecordingCode] == "customsource");
2183  }
2184 
2185  {
2186  beginTest ("Files containing an ISRC associate the value with the internationalStandardRecordingCode key "
2187  "and the riffInfoSource key for backwards compatibility");
2188  StringPairArray meta;
2189  meta.addMap ({ { WavAudioFormat::internationalStandardRecordingCode, "AABBBCCDDDDD" } });
2190  const auto mb = writeToBlock (format, meta);
2191  checkPatternsPresent (mb, { "ISRC:AABBBCCDDDDD", "<ebucore" });
2192  checkPatternsNotPresent (mb, { "INFOISRC" });
2193  const auto a = getMetadataAfterReading (format, mb);
2194  expect (a[WavAudioFormat::riffInfoSource] == "AABBBCCDDDDD");
2195  expect (a[WavAudioFormat::internationalStandardRecordingCode] == "AABBBCCDDDDD");
2196  }
2197 
2198  {
2199  beginTest ("Files containing an ISRC and a riff info source associate the values with the appropriate keys");
2200  StringPairArray meta;
2201  meta.addMap ({ { WavAudioFormat::riffInfoSource, "source" } });
2202  meta.addMap ({ { WavAudioFormat::internationalStandardRecordingCode, "UUVVVXXYYYYY" } });
2203  const auto mb = writeToBlock (format, meta);
2204  checkPatternsPresent (mb, { "INFOISRC", "ISRC:UUVVVXXYYYYY", "<ebucore" });
2205  const auto a = getMetadataAfterReading (format, mb);
2206  expect (a[WavAudioFormat::riffInfoSource] == "source");
2207  expect (a[WavAudioFormat::internationalStandardRecordingCode] == "UUVVVXXYYYYY");
2208  }
2209 
2210  {
2211  beginTest ("Files containing ASWG metadata read and write correctly");
2212  MemoryBlock block;
2213  StringPairArray meta;
2214 
2215  for (const auto& key : WavFileHelpers::IXMLChunk::aswgMetadataKeys)
2216  meta.set (key, "Test123&<>");
2217 
2218  {
2219  auto writer = rawToUniquePtr (WavAudioFormat().createWriterFor (new MemoryOutputStream (block, false), 48000, 1, 32, meta, 0));
2220  expect (writer != nullptr);
2221  }
2222 
2223  expect ([&]
2224  {
2225  auto input = std::make_unique<MemoryInputStream> (block, false);
2226 
2227  while (! input->isExhausted())
2228  {
2229  char chunkType[4] {};
2230  auto pos = input->getPosition();
2231 
2232  input->read (chunkType, 4);
2233 
2234  if (memcmp (chunkType, "iXML", 4) == 0)
2235  {
2236  auto length = (uint32) input->readInt();
2237 
2238  MemoryBlock xmlBlock;
2239  input->readIntoMemoryBlock (xmlBlock, (ssize_t) length);
2240 
2241  return parseXML (xmlBlock.toString()) != nullptr;
2242  }
2243 
2244  input->setPosition (pos + 1);
2245  }
2246 
2247  return false;
2248  }());
2249 
2250  {
2251  auto reader = rawToUniquePtr (WavAudioFormat().createReaderFor (new MemoryInputStream (block, false), true));
2252  expect (reader != nullptr);
2253 
2254  for (const auto& key : meta.getAllKeys())
2255  {
2256  const auto oldValue = meta.getValue (key, "!");
2257  const auto newValue = reader->metadataValues.getValue (key, "");
2258  expectEquals (oldValue, newValue);
2259  }
2260 
2261  expect (reader->metadataValues.getValue (WavAudioFormat::aswgVersion, "") == "3.01");
2262  }
2263  }
2264  }
2265 
2266 private:
2267  MemoryBlock writeToBlock (WavAudioFormat& format, StringPairArray meta)
2268  {
2269  MemoryBlock mb;
2270 
2271  {
2272  // The destructor of the writer will modify the block, so make sure that we've
2273  // destroyed the writer before returning the block!
2274  auto writer = rawToUniquePtr (format.createWriterFor (new MemoryOutputStream (mb, false),
2275  44100.0,
2276  numTestAudioBufferChannels,
2277  16,
2278  meta,
2279  0));
2280  expect (writer != nullptr);
2281  AudioBuffer<float> buffer (numTestAudioBufferChannels, numTestAudioBufferSamples);
2282  expect (writer->writeFromAudioSampleBuffer (buffer, 0, numTestAudioBufferSamples));
2283  }
2284 
2285  return mb;
2286  }
2287 
2288  StringPairArray getMetadataAfterReading (WavAudioFormat& format, const MemoryBlock& mb)
2289  {
2290  auto reader = rawToUniquePtr (format.createReaderFor (new MemoryInputStream (mb, false), true));
2291  expect (reader != nullptr);
2292  return reader->metadataValues;
2293  }
2294 
2295  template <typename Fn>
2296  void checkPatterns (const MemoryBlock& mb, const std::vector<std::string>& patterns, Fn&& fn)
2297  {
2298  for (const auto& pattern : patterns)
2299  {
2300  const auto begin = static_cast<const char*> (mb.getData());
2301  const auto end = begin + mb.getSize();
2302  expect (fn (std::search (begin, end, pattern.begin(), pattern.end()), end));
2303  }
2304  }
2305 
2306  void checkPatternsPresent (const MemoryBlock& mb, const std::vector<std::string>& patterns)
2307  {
2308  checkPatterns (mb, patterns, std::not_equal_to<>{});
2309  }
2310 
2311  void checkPatternsNotPresent (const MemoryBlock& mb, const std::vector<std::string>& patterns)
2312  {
2313  checkPatterns (mb, patterns, std::equal_to<>{});
2314  }
2315 
2316  enum
2317  {
2318  numTestAudioBufferChannels = 2,
2319  numTestAudioBufferSamples = 256
2320  };
2321 
2322  static StringMap createDefaultSMPLMetadata()
2323  {
2324  StringMap m;
2325 
2326  m["Manufacturer"] = "0";
2327  m["Product"] = "0";
2328  m["SamplePeriod"] = "0";
2329  m["MidiUnityNote"] = "60";
2330  m["MidiPitchFraction"] = "0";
2331  m["SmpteFormat"] = "0";
2332  m["SmpteOffset"] = "0";
2333  m["NumSampleLoops"] = "0";
2334  m["SamplerData"] = "0";
2335 
2336  return m;
2337  }
2338 
2339  JUCE_DECLARE_NON_COPYABLE (WaveAudioFormatTests)
2340 };
2341 
2342 static const WaveAudioFormatTests waveAudioFormatTests;
2343 
2344 #endif
2345 
2346 } // 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 *const *destChannels, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int &numSamples, int64 fileLengthInSamples)
AudioFormatReader(InputStream *sourceStream, const String &formatName)
virtual void readMaxLevels(int64 startSample, int64 numSamples, Range< float > *results, int numChannelsToRead)
constexpr static uint32 littleEndianInt(const void *bytes) noexcept
constexpr static uint16 swap(uint16 value) noexcept
static Type swapIfBigEndian(Type value) 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
std::unique_ptr< FileInputStream > createInputStream() const
Definition: juce_File.cpp:732
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
size_t getSize() const noexcept
void setSize(size_t newSize, bool initialiseNewSpaceToZero=false)
size_t getDataSize() const noexcept
bool write(const void *, size_t) override
virtual bool writeByte(char byte)
virtual bool writeInt(int value)
void set(const String &key, const String &value)
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:233
String formatted(const String &format) const
Definition: juce_Time.cpp:341
static const char *const aswgRecStudio
static const char *const aswgBillingCode
static const char *const aswgMicDistance
static const char *const riffInfoCopyright
static const char *const acidRootSet
static const char *const aswgUserData
static const char *const riffInfoDirectory
static const char *const bwavCodingHistory
static const char *const bwavTimeReference
static const char *const acidDiskBased
static const char *const aswgEfforts
static const char *const aswgMusicSup
static const char *const aswgTimeSig
static const char *const acidOneShot
static const char *const aswgIsDesigned
static const char *const riffInfoOrganisation
static const char *const aswgCharacterName
static const char *const aswgIsUnion
static const char *const aswgLibrary
static const char *const riffInfoFirstLanguage
static const char *const aswgAmbisonicChnOrder
static const char *const aswgRecordingLoc
static const char *const aswgCreatorId
static const char *const riffInfoEncodedBy
static const char *const riffInfoCommissioned
static const char *const riffInfoMusicBy
static const char *const riffInfoSharpness
static const char *const aswgProducer
static const char *const aswgFxUsed
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 aswgProjection
static const char *const riffInfoVegasVersionMajor
static const char *const aswgContentType
static const char *const aswgOriginator
static const char *const riffInfoLocation
static const char *const aswgIsOst
AudioFormatReader * createReaderFor(InputStream *sourceStream, bool deleteStreamIfOpeningFails) override
static const char *const aswgRmsPower
static const char *const riffInfoRate
static const char *const riffInfoCostumeDesigner
static const char *const riffInfoVersion
static const char *const aswgActorGender
static const char *const riffInfoLightness
static const char *const riffInfoProductionStudio
static const char *const aswgIsLoop
static const char *const aswgState
static const char *const aswgSongTitle
static const char *const riffInfoProducedBy
static const char *const aswgSpecDensity
static const char *const riffInfoEighthLanguage
static const char *const riffInfoCropped
static const char *const aswgCharacterAge
static const char *const riffInfoRating
static const char *const aswgMicType
static const char *const aswgIsDiegetic
static const char *const riffInfoURL
static const char *const aswgVersion
static const char *const ISRC
static const char *const riffInfoMoreInfoBannerURL
static const char *const riffInfoStartTimecode
static const char *const aswgLoudnessRange
static const char *const bwavOriginatorRef
static const char *const aswgChannelConfig
static const char *const riffInfoTitle
static const char *const aswgIsFinal
static const char *const riffInfoArtist
static const char *const aswgMusicPublisher
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 aswgAccent
static const char *const riffInfoProductName
static const char *const riffInfoKeywords
static const char *const aswgInstrument
static const char *const aswgSession
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 aswgArtist
static const char *const riffInfoProductionDesigner
static const char *const acidDenominator
static const char *const aswgCharacterRole
static const char *const aswgDirection
static const char *const aswgIsSource
static const char *const aswgFxChainName
static const char *const aswgFxName
static const char *const aswgOriginatorStudio
static const char *const riffInfoVegasVersionMinor
static const char *const riffInfoLength
bool replaceMetadataInFile(const File &wavFile, const StringPairArray &newMetadata)
static const char *const aswgUsageRights
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 aswgActorName
static const char *const aswgAmbisonicFormat
static const char *const acidBeats
static const char *const aswgMusicVersion
AudioFormatWriter * createWriterFor(OutputStream *streamToWriteTo, double sampleRateToUse, unsigned int numberOfChannels, int bitsPerSample, const StringPairArray &metadataValues, int qualityOptionIndex) override
static const char *const aswgMixer
static const char *const riffInfoLogoIconURL
static const char *const tracktionLoopInfo
static const char *const acidNumerator
static const char *const bwavOriginationDate
static const char *const internationalStandardRecordingCode
static const char *const aswgSourceId
static const char *const riffInfoComments
static const char *const riffInfoNumberOfParts
static const char *const aswgVendorCategory
static const char *const bwavDescription
static const char *const riffInfoSoundSchemeTitle
static const char *const aswgPapr
Array< int > getPossibleSampleRates() override
static const char *const aswgEditor
static const char *const aswgComposor
static const char *const aswgSubGenre
static const char *const aswgEffortType
static const char *const riffInfoWatermarkURL
static const char *const aswgLoudness
static const char *const riffInfoTrackNo
static const char *const riffInfoMedium
static const char *const acidStretch
Array< int > getPossibleBitDepths() override
static const char *const aswgRightsOwner
static const char *const aswgImpulseLocation
static const char *const riffInfoThirdLanguage
static const char *const bwavOriginationTime
static const char *const aswgLanguage
static const char *const riffInfoArchivalLocation
static const char *const aswgTimingRestriction
static const char *const aswgIsrcId
static const char *const aswgIsLicensed
static const char *const aswgCategory
static const char *const aswgZeroCrossRate
static const char *const aswgGenre
static const char *const aswgMaxPeak
static const char *const riffInfoMoreInfoText
static const char *const aswgCharacterGender
static const char *const riffInfoCinematographer
static const char *const riffInfoFourthLanguage
static const char *const riffInfoSubject
static const char *const aswgEmotion
static const char *const aswgInKey
static const char *const aswgMicConfig
static const char *const riffInfoRated
static const char *const riffInfoDimension
static const char *const aswgProject
static const char *const aswgIntensity
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 aswgSubCategory
static const char *const aswgText
static const char *const riffInfoTimeCode
static const char *const riffInfoSourceFrom
static const char *const riffInfoSource
static const char *const riffInfoLogoURL
static const char *const aswgOrderRef
static const char *const riffInfoCountry
static const char *const aswgAmbisonicNorm
static const char *const aswgUserCategory
static const char *const aswgNotes
static const char *const riffInfoSecondLanguage
static const char *const aswgTempo
static const char *const riffInfoComment
static const char *const riffInfoTapeName
static const char *const aswgDirector
static const char *const aswgRecEngineer
static const char *const aswgCatId
static const char *const riffInfoEndTimecode
static const char *const aswgIsCinematic
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