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