OpenShot Audio Library | OpenShotAudio 0.4.0
Loading...
Searching...
No Matches
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
26namespace juce
27{
28
29using StringMap = std::unordered_map<String, String>;
30
31static 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
41static 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
47static const char* const wavFormatName = "WAV file";
48
49//==============================================================================
50const char* const WavAudioFormat::bwavDescription = "bwav description";
51const char* const WavAudioFormat::bwavOriginator = "bwav originator";
52const char* const WavAudioFormat::bwavOriginatorRef = "bwav originator ref";
53const char* const WavAudioFormat::bwavOriginationDate = "bwav origination date";
54const char* const WavAudioFormat::bwavOriginationTime = "bwav origination time";
55const char* const WavAudioFormat::bwavTimeReference = "bwav time reference";
56const char* const WavAudioFormat::bwavCodingHistory = "bwav coding history";
57
59 const String& originator,
60 const String& originatorRef,
61 Time date,
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"));
73 m.set (bwavCodingHistory, codingHistory);
74
75 return m;
76}
77
78const char* const WavAudioFormat::acidOneShot = "acid one shot";
79const char* const WavAudioFormat::acidRootSet = "acid root set";
80const char* const WavAudioFormat::acidStretch = "acid stretch";
81const char* const WavAudioFormat::acidDiskBased = "acid disk based";
82const char* const WavAudioFormat::acidizerFlag = "acidizer flag";
83const char* const WavAudioFormat::acidRootNote = "acid root note";
84const char* const WavAudioFormat::acidBeats = "acid beats";
85const char* const WavAudioFormat::acidDenominator = "acid denominator";
86const char* const WavAudioFormat::acidNumerator = "acid numerator";
87const char* const WavAudioFormat::acidTempo = "acid tempo";
88
89const char* const WavAudioFormat::riffInfoArchivalLocation = "IARL";
90const char* const WavAudioFormat::riffInfoArtist = "IART";
91const char* const WavAudioFormat::riffInfoBaseURL = "IBSU";
92const char* const WavAudioFormat::riffInfoCinematographer = "ICNM";
93const char* const WavAudioFormat::riffInfoComment = "CMNT";
94const char* const WavAudioFormat::riffInfoComment2 = "ICMT";
95const char* const WavAudioFormat::riffInfoComments = "COMM";
96const char* const WavAudioFormat::riffInfoCommissioned = "ICMS";
97const char* const WavAudioFormat::riffInfoCopyright = "ICOP";
98const char* const WavAudioFormat::riffInfoCostumeDesigner = "ICDS";
99const char* const WavAudioFormat::riffInfoCountry = "ICNT";
100const char* const WavAudioFormat::riffInfoCropped = "ICRP";
101const char* const WavAudioFormat::riffInfoDateCreated = "ICRD";
102const char* const WavAudioFormat::riffInfoDateTimeOriginal = "IDIT";
103const char* const WavAudioFormat::riffInfoDefaultAudioStream = "ICAS";
104const char* const WavAudioFormat::riffInfoDimension = "IDIM";
105const char* const WavAudioFormat::riffInfoDirectory = "DIRC";
106const char* const WavAudioFormat::riffInfoDistributedBy = "IDST";
107const char* const WavAudioFormat::riffInfoDotsPerInch = "IDPI";
108const char* const WavAudioFormat::riffInfoEditedBy = "IEDT";
109const char* const WavAudioFormat::riffInfoEighthLanguage = "IAS8";
110const char* const WavAudioFormat::riffInfoEncodedBy = "CODE";
111const char* const WavAudioFormat::riffInfoEndTimecode = "TCDO";
112const char* const WavAudioFormat::riffInfoEngineer = "IENG";
113const char* const WavAudioFormat::riffInfoFifthLanguage = "IAS5";
114const char* const WavAudioFormat::riffInfoFirstLanguage = "IAS1";
115const char* const WavAudioFormat::riffInfoFourthLanguage = "IAS4";
116const char* const WavAudioFormat::riffInfoGenre = "GENR";
117const char* const WavAudioFormat::riffInfoKeywords = "IKEY";
118const char* const WavAudioFormat::riffInfoLanguage = "LANG";
119const char* const WavAudioFormat::riffInfoLength = "TLEN";
120const char* const WavAudioFormat::riffInfoLightness = "ILGT";
121const char* const WavAudioFormat::riffInfoLocation = "LOCA";
122const char* const WavAudioFormat::riffInfoLogoIconURL = "ILIU";
123const char* const WavAudioFormat::riffInfoLogoURL = "ILGU";
124const char* const WavAudioFormat::riffInfoMedium = "IMED";
125const char* const WavAudioFormat::riffInfoMoreInfoBannerImage = "IMBI";
126const char* const WavAudioFormat::riffInfoMoreInfoBannerURL = "IMBU";
127const char* const WavAudioFormat::riffInfoMoreInfoText = "IMIT";
128const char* const WavAudioFormat::riffInfoMoreInfoURL = "IMIU";
129const char* const WavAudioFormat::riffInfoMusicBy = "IMUS";
130const char* const WavAudioFormat::riffInfoNinthLanguage = "IAS9";
131const char* const WavAudioFormat::riffInfoNumberOfParts = "PRT2";
132const char* const WavAudioFormat::riffInfoOrganisation = "TORG";
133const char* const WavAudioFormat::riffInfoPart = "PRT1";
134const char* const WavAudioFormat::riffInfoProducedBy = "IPRO";
135const char* const WavAudioFormat::riffInfoProductName = "IPRD";
136const char* const WavAudioFormat::riffInfoProductionDesigner = "IPDS";
137const char* const WavAudioFormat::riffInfoProductionStudio = "ISDT";
138const char* const WavAudioFormat::riffInfoRate = "RATE";
139const char* const WavAudioFormat::riffInfoRated = "AGES";
140const char* const WavAudioFormat::riffInfoRating = "IRTD";
141const char* const WavAudioFormat::riffInfoRippedBy = "IRIP";
142const char* const WavAudioFormat::riffInfoSecondaryGenre = "ISGN";
143const char* const WavAudioFormat::riffInfoSecondLanguage = "IAS2";
144const char* const WavAudioFormat::riffInfoSeventhLanguage = "IAS7";
145const char* const WavAudioFormat::riffInfoSharpness = "ISHP";
146const char* const WavAudioFormat::riffInfoSixthLanguage = "IAS6";
147const char* const WavAudioFormat::riffInfoSoftware = "ISFT";
148const char* const WavAudioFormat::riffInfoSoundSchemeTitle = "DISP";
149const char* const WavAudioFormat::riffInfoSource = "ISRC";
150const char* const WavAudioFormat::riffInfoSourceFrom = "ISRF";
151const char* const WavAudioFormat::riffInfoStarring_ISTR = "ISTR";
152const char* const WavAudioFormat::riffInfoStarring_STAR = "STAR";
153const char* const WavAudioFormat::riffInfoStartTimecode = "TCOD";
154const char* const WavAudioFormat::riffInfoStatistics = "STAT";
155const char* const WavAudioFormat::riffInfoSubject = "ISBJ";
156const char* const WavAudioFormat::riffInfoTapeName = "TAPE";
157const char* const WavAudioFormat::riffInfoTechnician = "ITCH";
158const char* const WavAudioFormat::riffInfoThirdLanguage = "IAS3";
159const char* const WavAudioFormat::riffInfoTimeCode = "ISMP";
160const char* const WavAudioFormat::riffInfoTitle = "INAM";
161const char* const WavAudioFormat::riffInfoTrackNo = "IPRT";
162const char* const WavAudioFormat::riffInfoTrackNumber = "TRCK";
163const char* const WavAudioFormat::riffInfoURL = "TURL";
164const char* const WavAudioFormat::riffInfoVegasVersionMajor = "VMAJ";
165const char* const WavAudioFormat::riffInfoVegasVersionMinor = "VMIN";
166const char* const WavAudioFormat::riffInfoVersion = "TVER";
167const char* const WavAudioFormat::riffInfoWatermarkURL = "IWMU";
168const char* const WavAudioFormat::riffInfoWrittenBy = "IWRI";
169const char* const WavAudioFormat::riffInfoYear = "YEAR";
170
171const char* const WavAudioFormat::aswgContentType = "contentType";
172const char* const WavAudioFormat::aswgProject = "project";
173const char* const WavAudioFormat::aswgOriginator = "originator";
174const char* const WavAudioFormat::aswgOriginatorStudio = "originatorStudio";
175const char* const WavAudioFormat::aswgNotes = "notes";
176const char* const WavAudioFormat::aswgSession = "session";
177const char* const WavAudioFormat::aswgState = "state";
178const char* const WavAudioFormat::aswgEditor = "editor";
179const char* const WavAudioFormat::aswgMixer = "mixer";
180const char* const WavAudioFormat::aswgFxChainName = "fxChainName";
181const char* const WavAudioFormat::aswgChannelConfig = "channelConfig";
182const char* const WavAudioFormat::aswgAmbisonicFormat = "ambisonicFormat";
183const char* const WavAudioFormat::aswgAmbisonicChnOrder = "ambisonicChnOrder";
184const char* const WavAudioFormat::aswgAmbisonicNorm = "ambisonicNorm";
185const char* const WavAudioFormat::aswgMicType = "micType";
186const char* const WavAudioFormat::aswgMicConfig = "micConfig";
187const char* const WavAudioFormat::aswgMicDistance = "micDistance";
188const char* const WavAudioFormat::aswgRecordingLoc = "recordingLoc";
189const char* const WavAudioFormat::aswgIsDesigned = "isDesigned";
190const char* const WavAudioFormat::aswgRecEngineer = "recEngineer";
191const char* const WavAudioFormat::aswgRecStudio = "recStudio";
192const char* const WavAudioFormat::aswgImpulseLocation = "impulseLocation";
193const char* const WavAudioFormat::aswgCategory = "category";
194const char* const WavAudioFormat::aswgSubCategory = "subCategory";
195const char* const WavAudioFormat::aswgCatId = "catId";
196const char* const WavAudioFormat::aswgUserCategory = "userCategory";
197const char* const WavAudioFormat::aswgUserData = "userData";
198const char* const WavAudioFormat::aswgVendorCategory = "vendorCategory";
199const char* const WavAudioFormat::aswgFxName = "fxName";
200const char* const WavAudioFormat::aswgLibrary = "library";
201const char* const WavAudioFormat::aswgCreatorId = "creatorId";
202const char* const WavAudioFormat::aswgSourceId = "sourceId";
203const char* const WavAudioFormat::aswgRmsPower = "rmsPower";
204const char* const WavAudioFormat::aswgLoudness = "loudness";
205const char* const WavAudioFormat::aswgLoudnessRange = "loudnessRange";
206const char* const WavAudioFormat::aswgMaxPeak = "maxPeak";
207const char* const WavAudioFormat::aswgSpecDensity = "specDensity";
208const char* const WavAudioFormat::aswgZeroCrossRate = "zeroCrossRate";
209const char* const WavAudioFormat::aswgPapr = "papr";
210const char* const WavAudioFormat::aswgText = "text";
211const char* const WavAudioFormat::aswgEfforts = "efforts";
212const char* const WavAudioFormat::aswgEffortType = "effortType";
213const char* const WavAudioFormat::aswgProjection = "projection";
214const char* const WavAudioFormat::aswgLanguage = "language";
215const char* const WavAudioFormat::aswgTimingRestriction = "timingRestriction";
216const char* const WavAudioFormat::aswgCharacterName = "characterName";
217const char* const WavAudioFormat::aswgCharacterGender = "characterGender";
218const char* const WavAudioFormat::aswgCharacterAge = "characterAge";
219const char* const WavAudioFormat::aswgCharacterRole = "characterRole";
220const char* const WavAudioFormat::aswgActorName = "actorName";
221const char* const WavAudioFormat::aswgActorGender = "actorGender";
222const char* const WavAudioFormat::aswgDirector = "director";
223const char* const WavAudioFormat::aswgDirection = "direction";
224const char* const WavAudioFormat::aswgFxUsed = "fxUsed";
225const char* const WavAudioFormat::aswgUsageRights = "usageRights";
226const char* const WavAudioFormat::aswgIsUnion = "isUnion";
227const char* const WavAudioFormat::aswgAccent = "accent";
228const char* const WavAudioFormat::aswgEmotion = "emotion";
229const char* const WavAudioFormat::aswgComposor = "composor";
230const char* const WavAudioFormat::aswgArtist = "artist";
231const char* const WavAudioFormat::aswgSongTitle = "songTitle";
232const char* const WavAudioFormat::aswgGenre = "genre";
233const char* const WavAudioFormat::aswgSubGenre = "subGenre";
234const char* const WavAudioFormat::aswgProducer = "producer";
235const char* const WavAudioFormat::aswgMusicSup = "musicSup";
236const char* const WavAudioFormat::aswgInstrument = "instrument";
237const char* const WavAudioFormat::aswgMusicPublisher = "musicPublisher";
238const char* const WavAudioFormat::aswgRightsOwner = "rightsOwner";
239const char* const WavAudioFormat::aswgIsSource = "isSource";
240const char* const WavAudioFormat::aswgIsLoop = "isLoop";
241const char* const WavAudioFormat::aswgIntensity = "intensity";
242const char* const WavAudioFormat::aswgIsFinal = "isFinal";
243const char* const WavAudioFormat::aswgOrderRef = "orderRef";
244const char* const WavAudioFormat::aswgIsOst = "isOst";
245const char* const WavAudioFormat::aswgIsCinematic = "isCinematic";
246const char* const WavAudioFormat::aswgIsLicensed = "isLicensed";
247const char* const WavAudioFormat::aswgIsDiegetic = "isDiegetic";
248const char* const WavAudioFormat::aswgMusicVersion = "musicVersion";
249const char* const WavAudioFormat::aswgIsrcId = "isrcId";
250const char* const WavAudioFormat::aswgTempo = "tempo";
251const char* const WavAudioFormat::aswgTimeSig = "timeSig";
252const char* const WavAudioFormat::aswgInKey = "inKey";
253const char* const WavAudioFormat::aswgBillingCode = "billingCode";
254const char* const WavAudioFormat::aswgVersion = "IXML_VERSION";
255
256const char* const WavAudioFormat::ISRC = "ISRC";
257const char* const WavAudioFormat::internationalStandardRecordingCode = "international standard recording code";
258const char* const WavAudioFormat::tracktionLoopInfo = "tracktion loop info";
259
260//==============================================================================
261namespace 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 {
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 {
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//==============================================================================
1207class WavAudioFormatReader final : public AudioFormatReader
1208{
1209public:
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();
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
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
1577private:
1578 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatReader)
1579};
1580
1581//==============================================================================
1582class WavAudioFormatWriter final : public AudioFormatWriter
1583{
1584public:
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
1672private:
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
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//==============================================================================
1846class MemoryMappedWavReader final : public MemoryMappedAudioFormatReader
1847{
1848public:
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
1931private:
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//==============================================================================
1943WavAudioFormat::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
1957bool WavAudioFormat::canDoStereo() { return true; }
1958bool 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)
1971 return false;
1972
1973 return true;
1974}
1975
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
1992 r->input = nullptr;
1993
1994 return nullptr;
1995}
1996
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
2037namespace WavFileHelpers
2038{
2039 static bool slowCopyWavFileWithNewMetadata (const File& file, const StringPairArray& metadata)
2040 {
2041 TemporaryFile tempFile (file);
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
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 {
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
2119struct WaveAudioFormatTests final : public UnitTest
2120{
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
2266private:
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
2342static const WaveAudioFormatTests waveAudioFormatTests;
2343
2344#endif
2345
2346} // namespace juce
static AudioChannelSet JUCE_CALLTYPE quadraphonic()
static AudioChannelSet JUCE_CALLTYPE create5point0()
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()
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)
AudioFormatWriter(OutputStream *destStream, const String &formatName, double sampleRate, unsigned int numberOfChannels, unsigned int bitsPerSample)
static constexpr uint32 littleEndianInt(const void *bytes) noexcept
static constexpr uint16 swap(uint16 value) noexcept
static Type swapIfBigEndian(Type value) noexcept
static juce_wchar toUpperCase(juce_wchar character) noexcept
std::unique_ptr< FileInputStream > createInputStream() const
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
void * getData() noexcept
void ensureSize(size_t minimumSize, bool initialiseNewSpaceToZero=false)
MemoryMappedAudioFormatReader(const File &file, const AudioFormatReader &details, int64 dataChunkStart, int64 dataChunkLength, int bytesPerFrame)
const void * sampleToPointer(int64 sample) const noexcept
virtual bool write(const void *dataToWrite, size_t numberOfBytes)=0
virtual bool writeRepeatedByte(uint8 byte, size_t numTimesToRepeat)
virtual int64 getPosition()=0
virtual bool writeByte(char byte)
virtual bool writeShort(short value)
virtual bool writeInt64(int64 value)
virtual bool setPosition(int64 newPosition)=0
virtual bool writeInt(int value)
constexpr bool contains(const ValueType position) const noexcept
Definition juce_Range.h:214
void set(const String &key, const String &value)
void addUnorderedMap(const std::unordered_map< String, String > &mapToAdd)
static String createStringFromData(const void *data, int size)
static String fromUTF8(const char *utf8buffer, int bufferSizeBytes=-1)
static Time JUCE_CALLTYPE getCurrentTime() noexcept
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
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