OpenShot Library | libopenshot-audio 0.2.0
juce_WavAudioFormat.cpp
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2017 - ROLI Ltd.
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 5 End-User License
11 Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
12 27th April 2017).
13
14 End User License Agreement: www.juce.com/juce-5-licence
15 Privacy Policy: www.juce.com/juce-5-privacy-policy
16
17 Or: You may also use this code under the terms of the GPL v3 (see
18 www.gnu.org/licenses).
19
20 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
21 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
22 DISCLAIMED.
23
24 ==============================================================================
25*/
26
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
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
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 //==============================================================================
251 {
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 //==============================================================================
357 {
358 int8 baseNote;
359 int8 detune;
360 int8 gain;
361 int8 lowNote;
362 int8 highNote;
363 int8 lowVelocity;
364 int8 highVelocity;
365
366 static void setValue (StringPairArray& values, const char* name, int val)
367 {
368 values.set (name, String (val));
369 }
370
371 void copyTo (StringPairArray& values) const
372 {
373 setValue (values, "MidiUnityNote", baseNote);
374 setValue (values, "Detune", detune);
375 setValue (values, "Gain", gain);
376 setValue (values, "LowNote", lowNote);
377 setValue (values, "HighNote", highNote);
378 setValue (values, "LowVelocity", lowVelocity);
379 setValue (values, "HighVelocity", highVelocity);
380 }
381
382 static int8 getValue (const StringPairArray& values, const char* name, const char* def)
383 {
384 return (int8) values.getValue (name, def).getIntValue();
385 }
386
387 static MemoryBlock createFrom (const StringPairArray& values)
388 {
389 MemoryBlock data;
390 auto& keys = values.getAllKeys();
391
392 if (keys.contains ("LowNote", true) && keys.contains ("HighNote", true))
393 {
394 data.setSize (8, true);
395 auto* inst = static_cast<InstChunk*> (data.getData());
396
397 inst->baseNote = getValue (values, "MidiUnityNote", "60");
398 inst->detune = getValue (values, "Detune", "0");
399 inst->gain = getValue (values, "Gain", "0");
400 inst->lowNote = getValue (values, "LowNote", "0");
401 inst->highNote = getValue (values, "HighNote", "127");
402 inst->lowVelocity = getValue (values, "LowVelocity", "1");
403 inst->highVelocity = getValue (values, "HighVelocity", "127");
404 }
405
406 return data;
407 }
408 } JUCE_PACKED;
409
410 //==============================================================================
411 struct CueChunk
412 {
413 struct Cue
414 {
415 uint32 identifier;
416 uint32 order;
417 uint32 chunkID;
418 uint32 chunkStart;
419 uint32 blockStart;
420 uint32 offset;
421 } JUCE_PACKED;
422
423 uint32 numCues;
424 Cue cues[1];
425
426 static void setValue (StringPairArray& values, int prefix, const char* name, uint32 val)
427 {
428 values.set ("Cue" + String (prefix) + name, String (ByteOrder::swapIfBigEndian (val)));
429 }
430
431 void copyTo (StringPairArray& values, const int totalSize) const
432 {
433 values.set ("NumCuePoints", String (ByteOrder::swapIfBigEndian (numCues)));
434
435 for (int i = 0; i < (int) numCues; ++i)
436 {
437 if ((uint8*) (cues + (i + 1)) > ((uint8*) this) + totalSize)
438 break;
439
440 setValue (values, i, "Identifier", cues[i].identifier);
441 setValue (values, i, "Order", cues[i].order);
442 setValue (values, i, "ChunkID", cues[i].chunkID);
443 setValue (values, i, "ChunkStart", cues[i].chunkStart);
444 setValue (values, i, "BlockStart", cues[i].blockStart);
445 setValue (values, i, "Offset", cues[i].offset);
446 }
447 }
448
449 static MemoryBlock createFrom (const StringPairArray& values)
450 {
451 MemoryBlock data;
452 const int numCues = values.getValue ("NumCuePoints", "0").getIntValue();
453
454 if (numCues > 0)
455 {
456 data.setSize (roundUpSize (sizeof (CueChunk) + (size_t) (numCues - 1) * sizeof (Cue)), true);
457
458 auto c = static_cast<CueChunk*> (data.getData());
459
460 c->numCues = ByteOrder::swapIfBigEndian ((uint32) numCues);
461
462 const String dataChunkID (chunkName ("data"));
463 int nextOrder = 0;
464
465 #if JUCE_DEBUG
466 Array<uint32> identifiers;
467 #endif
468
469 for (int i = 0; i < numCues; ++i)
470 {
471 auto prefix = "Cue" + String (i);
472 auto identifier = (uint32) values.getValue (prefix + "Identifier", "0").getIntValue();
473
474 #if JUCE_DEBUG
475 jassert (! identifiers.contains (identifier));
476 identifiers.add (identifier);
477 #endif
478
479 auto order = values.getValue (prefix + "Order", String (nextOrder)).getIntValue();
480 nextOrder = jmax (nextOrder, order) + 1;
481
482 auto& cue = c->cues[i];
483 cue.identifier = ByteOrder::swapIfBigEndian ((uint32) identifier);
484 cue.order = ByteOrder::swapIfBigEndian ((uint32) order);
485 cue.chunkID = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "ChunkID", dataChunkID).getIntValue());
486 cue.chunkStart = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "ChunkStart", "0").getIntValue());
487 cue.blockStart = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "BlockStart", "0").getIntValue());
488 cue.offset = ByteOrder::swapIfBigEndian ((uint32) values.getValue (prefix + "Offset", "0").getIntValue());
489 }
490 }
491
492 return data;
493 }
494
495 } JUCE_PACKED;
496
497 //==============================================================================
498 namespace ListChunk
499 {
500 static int getValue (const StringPairArray& values, const String& name)
501 {
502 return values.getValue (name, "0").getIntValue();
503 }
504
505 static int getValue (const StringPairArray& values, const String& prefix, const char* name)
506 {
507 return getValue (values, prefix + name);
508 }
509
510 static void appendLabelOrNoteChunk (const StringPairArray& values, const String& prefix,
511 const int chunkType, MemoryOutputStream& out)
512 {
513 auto label = values.getValue (prefix + "Text", prefix);
514 auto labelLength = (int) label.getNumBytesAsUTF8() + 1;
515 auto chunkLength = 4 + labelLength + (labelLength & 1);
516
517 out.writeInt (chunkType);
518 out.writeInt (chunkLength);
519 out.writeInt (getValue (values, prefix, "Identifier"));
520 out.write (label.toUTF8(), (size_t) labelLength);
521
522 if ((out.getDataSize() & 1) != 0)
523 out.writeByte (0);
524 }
525
526 static void appendExtraChunk (const StringPairArray& values, const String& prefix, MemoryOutputStream& out)
527 {
528 auto text = values.getValue (prefix + "Text", prefix);
529
530 auto textLength = (int) text.getNumBytesAsUTF8() + 1; // include null terminator
531 auto chunkLength = textLength + 20 + (textLength & 1);
532
533 out.writeInt (chunkName ("ltxt"));
534 out.writeInt (chunkLength);
535 out.writeInt (getValue (values, prefix, "Identifier"));
536 out.writeInt (getValue (values, prefix, "SampleLength"));
537 out.writeInt (getValue (values, prefix, "Purpose"));
538 out.writeShort ((short) getValue (values, prefix, "Country"));
539 out.writeShort ((short) getValue (values, prefix, "Language"));
540 out.writeShort ((short) getValue (values, prefix, "Dialect"));
541 out.writeShort ((short) getValue (values, prefix, "CodePage"));
542 out.write (text.toUTF8(), (size_t) textLength);
543
544 if ((out.getDataSize() & 1) != 0)
545 out.writeByte (0);
546 }
547
548 static MemoryBlock createFrom (const StringPairArray& values)
549 {
550 auto numCueLabels = getValue (values, "NumCueLabels");
551 auto numCueNotes = getValue (values, "NumCueNotes");
552 auto numCueRegions = getValue (values, "NumCueRegions");
553
554 MemoryOutputStream out;
555
556 if (numCueLabels + numCueNotes + numCueRegions > 0)
557 {
558 out.writeInt (chunkName ("adtl"));
559
560 for (int i = 0; i < numCueLabels; ++i)
561 appendLabelOrNoteChunk (values, "CueLabel" + String (i), chunkName ("labl"), out);
562
563 for (int i = 0; i < numCueNotes; ++i)
564 appendLabelOrNoteChunk (values, "CueNote" + String (i), chunkName ("note"), out);
565
566 for (int i = 0; i < numCueRegions; ++i)
567 appendExtraChunk (values, "CueRegion" + String (i), out);
568 }
569
570 return out.getMemoryBlock();
571 }
572 }
573
574 //==============================================================================
575 /** Reads a RIFF List Info chunk from a stream positioned just after the size byte. */
576 namespace ListInfoChunk
577 {
578 static const char* const types[] =
579 {
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 //==============================================================================
737 {
738 /** Reads an acid RIFF chunk from a stream positioned just after the size byte. */
739 AcidChunk (InputStream& input, size_t length)
740 {
741 zerostruct (*this);
742 input.read (this, (int) jmin (sizeof (*this), length));
743 }
744
745 AcidChunk (const StringPairArray& values)
746 {
747 zerostruct (*this);
748
749 flags = getFlagIfPresent (values, WavAudioFormat::acidOneShot, 0x01)
750 | getFlagIfPresent (values, WavAudioFormat::acidRootSet, 0x02)
751 | getFlagIfPresent (values, WavAudioFormat::acidStretch, 0x04)
752 | getFlagIfPresent (values, WavAudioFormat::acidDiskBased, 0x08)
753 | getFlagIfPresent (values, WavAudioFormat::acidizerFlag, 0x10);
754
755 if (values[WavAudioFormat::acidRootSet].getIntValue() != 0)
756 rootNote = ByteOrder::swapIfBigEndian ((uint16) values[WavAudioFormat::acidRootNote].getIntValue());
757
758 numBeats = ByteOrder::swapIfBigEndian ((uint32) values[WavAudioFormat::acidBeats].getIntValue());
759 meterDenominator = ByteOrder::swapIfBigEndian ((uint16) values[WavAudioFormat::acidDenominator].getIntValue());
760 meterNumerator = ByteOrder::swapIfBigEndian ((uint16) values[WavAudioFormat::acidNumerator].getIntValue());
761
763 tempo = swapFloatByteOrder (values[WavAudioFormat::acidTempo].getFloatValue());
764 }
765
766 static MemoryBlock createFrom (const StringPairArray& values)
767 {
768 return AcidChunk (values).toMemoryBlock();
769 }
770
771 MemoryBlock toMemoryBlock() const
772 {
773 return (flags != 0 || rootNote != 0 || numBeats != 0 || meterDenominator != 0 || meterNumerator != 0)
774 ? MemoryBlock (this, sizeof (*this)) : MemoryBlock();
775 }
776
777 void addToMetadata (StringPairArray& values) const
778 {
779 setBoolFlag (values, WavAudioFormat::acidOneShot, 0x01);
780 setBoolFlag (values, WavAudioFormat::acidRootSet, 0x02);
781 setBoolFlag (values, WavAudioFormat::acidStretch, 0x04);
782 setBoolFlag (values, WavAudioFormat::acidDiskBased, 0x08);
783 setBoolFlag (values, WavAudioFormat::acidizerFlag, 0x10);
784
785 if (flags & 0x02) // root note set
786 values.set (WavAudioFormat::acidRootNote, String (ByteOrder::swapIfBigEndian (rootNote)));
787
788 values.set (WavAudioFormat::acidBeats, String (ByteOrder::swapIfBigEndian (numBeats)));
789 values.set (WavAudioFormat::acidDenominator, String (ByteOrder::swapIfBigEndian (meterDenominator)));
790 values.set (WavAudioFormat::acidNumerator, String (ByteOrder::swapIfBigEndian (meterNumerator)));
791 values.set (WavAudioFormat::acidTempo, String (swapFloatByteOrder (tempo)));
792 }
793
794 void setBoolFlag (StringPairArray& values, const char* name, uint32 mask) const
795 {
796 values.set (name, (flags & ByteOrder::swapIfBigEndian (mask)) ? "1" : "0");
797 }
798
799 static uint32 getFlagIfPresent (const StringPairArray& values, const char* name, uint32 flag)
800 {
801 return values[name].getIntValue() != 0 ? ByteOrder::swapIfBigEndian (flag) : 0;
802 }
803
804 static float swapFloatByteOrder (const float x) noexcept
805 {
806 #ifdef JUCE_BIG_ENDIAN
807 union { uint32 asInt; float asFloat; } n;
808 n.asFloat = x;
809 n.asInt = ByteOrder::swap (n.asInt);
810 return n.asFloat;
811 #else
812 return x;
813 #endif
814 }
815
816 uint32 flags;
817 uint16 rootNote;
818 uint16 reserved1;
819 float reserved2;
820 uint32 numBeats;
821 uint16 meterDenominator;
822 uint16 meterNumerator;
823 float tempo;
824
825 } JUCE_PACKED;
826
827 //==============================================================================
829 {
830 static MemoryBlock createFrom (const StringPairArray& values)
831 {
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())
866 }
867 }
868 }
869 }
870 }
871 }
872
873 static MemoryBlock createFrom (const StringPairArray& values)
874 {
875 auto ISRC = values.getValue (WavAudioFormat::ISRC, {});
876 MemoryOutputStream xml;
877
878 if (ISRC.isNotEmpty())
879 {
880 xml << "<ebucore:ebuCoreMain xmlns:dc=\" http://purl.org/dc/elements/1.1/\" "
881 "xmlns:ebucore=\"urn:ebu:metadata-schema:ebuCore_2012\">"
882 "<ebucore:coreMetadata>"
883 "<ebucore:identifier typeLabel=\"GUID\" "
884 "typeDefinition=\"Globally Unique Identifier\" "
885 "formatLabel=\"ISRC\" "
886 "formatDefinition=\"International Standard Recording Code\" "
887 "formatLink=\"http://www.ebu.ch/metadata/cs/ebu_IdentifierTypeCodeCS.xml#3.7\">"
888 "<dc:identifier>ISRC:" << ISRC << "</dc:identifier>"
889 "</ebucore:identifier>"
890 "</ebucore:coreMetadata>"
891 "</ebucore:ebuCoreMain>";
892
893 xml.writeRepeatedByte (0, xml.getDataSize()); // ensures even size, null termination and room for future growing
894 }
895
896 return xml.getMemoryBlock();
897 }
898 }
899
900 //==============================================================================
902 {
903 uint32 data1;
904 uint16 data2;
905 uint16 data3;
906 uint8 data4[8];
907
908 bool operator== (const ExtensibleWavSubFormat& other) const noexcept { return memcmp (this, &other, sizeof (*this)) == 0; }
909 bool operator!= (const ExtensibleWavSubFormat& other) const noexcept { return ! operator== (other); }
910
911 } JUCE_PACKED;
912
913 static const ExtensibleWavSubFormat pcmFormat = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
914 static const ExtensibleWavSubFormat IEEEFloatFormat = { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
915 static const ExtensibleWavSubFormat ambisonicFormat = { 0x00000001, 0x0721, 0x11d3, { 0x86, 0x44, 0xC8, 0xC1, 0xCA, 0x00, 0x00, 0x00 } };
916
917 struct DataSize64Chunk // chunk ID = 'ds64' if data size > 0xffffffff, 'JUNK' otherwise
918 {
919 uint32 riffSizeLow; // low 4 byte size of RF64 block
920 uint32 riffSizeHigh; // high 4 byte size of RF64 block
921 uint32 dataSizeLow; // low 4 byte size of data chunk
922 uint32 dataSizeHigh; // high 4 byte size of data chunk
923 uint32 sampleCountLow; // low 4 byte sample count of fact chunk
924 uint32 sampleCountHigh; // high 4 byte sample count of fact chunk
925 uint32 tableLength; // number of valid entries in array 'table'
926 } JUCE_PACKED;
927
928 #if JUCE_MSVC
929 #pragma pack (pop)
930 #endif
931}
932
933//==============================================================================
935{
936public:
938 {
939 using namespace WavFileHelpers;
940 uint64 len = 0, end = 0;
941 int cueNoteIndex = 0;
942 int cueLabelIndex = 0;
943 int cueRegionIndex = 0;
944
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
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();
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 = 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
1046 return;
1047 }
1048 else if (format != 1)
1049 {
1050 bytesPerFrame = 0;
1051 }
1052 }
1053 else if (chunkType == chunkName ("data"))
1054 {
1055 if (! isRF64) // data size is expected to be -1, actual data size is in ds64 chunk
1056 dataLength = length;
1057
1058 dataChunkStart = input->getPosition();
1059 lengthInSamples = (bytesPerFrame > 0) ? (dataLength / bytesPerFrame) : 0;
1060 }
1061 else if (chunkType == chunkName ("bext"))
1062 {
1063 bwavChunkStart = input->getPosition();
1064 bwavSize = length;
1065
1067 bwav.calloc (jmax ((size_t) length + 1, sizeof (BWAVChunk)), 1);
1068 input->read (bwav, (int) length);
1069 bwav->copyTo (metadataValues, (int) length);
1070 }
1071 else if (chunkType == chunkName ("smpl"))
1072 {
1074 smpl.calloc (jmax ((size_t) length + 1, sizeof (SMPLChunk)), 1);
1075 input->read (smpl, (int) length);
1076 smpl->copyTo (metadataValues, (int) length);
1077 }
1078 else if (chunkType == chunkName ("inst") || chunkType == chunkName ("INST")) // need to check which...
1079 {
1081 inst.calloc (jmax ((size_t) length + 1, sizeof (InstChunk)), 1);
1082 input->read (inst, (int) length);
1083 inst->copyTo (metadataValues);
1084 }
1085 else if (chunkType == chunkName ("cue "))
1086 {
1088 cue.calloc (jmax ((size_t) length + 1, sizeof (CueChunk)), 1);
1089 input->read (cue, (int) length);
1090 cue->copyTo (metadataValues, (int) length);
1091 }
1092 else if (chunkType == chunkName ("axml"))
1093 {
1096 AXMLChunk::addToMetadata (metadataValues, axml.toString());
1097 }
1098 else if (chunkType == chunkName ("LIST"))
1099 {
1100 auto subChunkType = input->readInt();
1101
1102 if (subChunkType == chunkName ("info") || subChunkType == chunkName ("INFO"))
1103 {
1104 ListInfoChunk::addToMetadata (metadataValues, *input, chunkEnd);
1105 }
1106 else if (subChunkType == chunkName ("adtl"))
1107 {
1108 while (input->getPosition() < chunkEnd)
1109 {
1110 auto adtlChunkType = input->readInt();
1111 auto adtlLength = (uint32) input->readInt();
1112 auto adtlChunkEnd = input->getPosition() + (adtlLength + (adtlLength & 1));
1113
1114 if (adtlChunkType == chunkName ("labl") || adtlChunkType == chunkName ("note"))
1115 {
1116 String prefix;
1117
1118 if (adtlChunkType == chunkName ("labl"))
1119 prefix << "CueLabel" << cueLabelIndex++;
1120 else if (adtlChunkType == chunkName ("note"))
1121 prefix << "CueNote" << cueNoteIndex++;
1122
1123 auto identifier = (uint32) input->readInt();
1124 auto stringLength = (int) adtlLength - 4;
1125
1128
1129 metadataValues.set (prefix + "Identifier", String (identifier));
1130 metadataValues.set (prefix + "Text", textBlock.toString());
1131 }
1132 else if (adtlChunkType == chunkName ("ltxt"))
1133 {
1134 auto prefix = "CueRegion" + String (cueRegionIndex++);
1135 auto identifier = (uint32) input->readInt();
1136 auto sampleLength = (uint32) input->readInt();
1137 auto purpose = (uint32) input->readInt();
1138 auto country = (uint16) input->readShort();
1139 auto language = (uint16) input->readShort();
1140 auto dialect = (uint16) input->readShort();
1141 auto codePage = (uint16) input->readShort();
1142 auto stringLength = adtlLength - 20;
1143
1146
1147 metadataValues.set (prefix + "Identifier", String (identifier));
1148 metadataValues.set (prefix + "SampleLength", String (sampleLength));
1149 metadataValues.set (prefix + "Purpose", String (purpose));
1150 metadataValues.set (prefix + "Country", String (country));
1151 metadataValues.set (prefix + "Language", String (language));
1152 metadataValues.set (prefix + "Dialect", String (dialect));
1153 metadataValues.set (prefix + "CodePage", String (codePage));
1154 metadataValues.set (prefix + "Text", textBlock.toString());
1155 }
1156
1158 }
1159 }
1160 }
1161 else if (chunkType == chunkName ("acid"))
1162 {
1163 AcidChunk (*input, length).addToMetadata (metadataValues);
1164 }
1165 else if (chunkType == chunkName ("Trkn"))
1166 {
1170 }
1171 else if (chunkEnd <= input->getPosition())
1172 {
1173 break;
1174 }
1175
1177 }
1178 }
1179
1180 if (cueLabelIndex > 0) metadataValues.set ("NumCueLabels", String (cueLabelIndex));
1181 if (cueNoteIndex > 0) metadataValues.set ("NumCueNotes", String (cueNoteIndex));
1182 if (cueRegionIndex > 0) metadataValues.set ("NumCueRegions", String (cueRegionIndex));
1183 if (metadataValues.size() > 0) metadataValues.set ("MetaDataSource", "WAV");
1184 }
1185
1186 //==============================================================================
1188 int64 startSampleInFile, int numSamples) override
1189 {
1191 startSampleInFile, numSamples, lengthInSamples);
1192
1193 if (numSamples <= 0)
1194 return true;
1195
1196 input->setPosition (dataChunkStart + startSampleInFile * bytesPerFrame);
1197
1198 while (numSamples > 0)
1199 {
1200 const int tempBufSize = 480 * 3 * 4; // (keep this a multiple of 3)
1201 char tempBuffer[tempBufSize];
1202
1203 auto numThisTime = jmin (tempBufSize / bytesPerFrame, numSamples);
1204 auto bytesRead = input->read (tempBuffer, numThisTime * bytesPerFrame);
1205
1206 if (bytesRead < numThisTime * bytesPerFrame)
1207 {
1208 jassert (bytesRead >= 0);
1209 zeromem (tempBuffer + bytesRead, (size_t) (numThisTime * bytesPerFrame - bytesRead));
1210 }
1211
1212 copySampleData (bitsPerSample, usesFloatingPointData,
1214 tempBuffer, (int) numChannels, numThisTime);
1215
1217 numSamples -= numThisTime;
1218 }
1219
1220 return true;
1221 }
1222
1223 static void copySampleData (unsigned int bitsPerSample, const bool usesFloatingPointData,
1225 const void* sourceData, int numChannels, int numSamples) noexcept
1226 {
1227 switch (bitsPerSample)
1228 {
1229 case 8: ReadHelper<AudioData::Int32, AudioData::UInt8, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break;
1230 case 16: ReadHelper<AudioData::Int32, AudioData::Int16, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break;
1231 case 24: ReadHelper<AudioData::Int32, AudioData::Int24, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples); break;
1232 case 32: if (usesFloatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples);
1233 else ReadHelper<AudioData::Int32, AudioData::Int32, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numChannels, numSamples);
1234 break;
1235 default: jassertfalse; break;
1236 }
1237 }
1238
1239 //==============================================================================
1241 {
1242 if (channelLayout.size() == static_cast<int> (numChannels))
1243 return channelLayout;
1244
1245 return WavFileHelpers::canonicalWavChannelSet (static_cast<int> (numChannels));
1246 }
1247
1248 static AudioChannelSet getChannelLayoutFromMask (int dwChannelMask, size_t totalNumChannels)
1249 {
1251
1252 // AudioChannelSet and wav's dwChannelMask are compatible
1254
1255 for (auto bit = channelBits.findNextSetBit (0); bit >= 0; bit = channelBits.findNextSetBit (bit + 1))
1256 wavFileChannelLayout.addChannel (static_cast<AudioChannelSet::ChannelType> (bit + 1));
1257
1258 // channel layout and number of channels do not match
1259 if (wavFileChannelLayout.size() != static_cast<int> (totalNumChannels))
1260 {
1261 // for backward compatibility with old wav files, assume 1 or 2
1262 // channel wav files are mono/stereo respectively
1263 if (totalNumChannels <= 2 && dwChannelMask == 0)
1265 else
1266 {
1267 auto discreteSpeaker = static_cast<int> (AudioChannelSet::discreteChannel0);
1268
1269 while (wavFileChannelLayout.size() < static_cast<int> (totalNumChannels))
1271 }
1272 }
1273
1274 return wavFileChannelLayout;
1275 }
1276
1277 int64 bwavChunkStart = 0, bwavSize = 0;
1278 int64 dataChunkStart = 0, dataLength = 0;
1279 int bytesPerFrame = 0;
1280 bool isRF64 = false;
1281 bool isSubformatOggVorbis = false;
1282
1283 AudioChannelSet channelLayout;
1284
1285private:
1286 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatReader)
1287};
1288
1289//==============================================================================
1291{
1292public:
1293 WavAudioFormatWriter (OutputStream* const out, const double rate,
1294 const AudioChannelSet& channelLayoutToUse, const unsigned int bits,
1295 const StringPairArray& metadataValues)
1296 : AudioFormatWriter (out, wavFormatName, rate, channelLayoutToUse, bits)
1297 {
1298 using namespace WavFileHelpers;
1299
1300 if (metadataValues.size() > 0)
1301 {
1302 // The meta data should have been sanitised for the WAV format.
1303 // If it was originally sourced from an AIFF file the MetaDataSource
1304 // key should be removed (or set to "WAV") once this has been done
1305 jassert (metadataValues.getValue ("MetaDataSource", "None") != "AIFF");
1306
1307 bwavChunk = BWAVChunk::createFrom (metadataValues);
1308 axmlChunk = AXMLChunk::createFrom (metadataValues);
1309 smplChunk = SMPLChunk::createFrom (metadataValues);
1310 instChunk = InstChunk::createFrom (metadataValues);
1311 cueChunk = CueChunk ::createFrom (metadataValues);
1312 listChunk = ListChunk::createFrom (metadataValues);
1313 listInfoChunk = ListInfoChunk::createFrom (metadataValues);
1314 acidChunk = AcidChunk::createFrom (metadataValues);
1315 trckChunk = TracktionChunk::createFrom (metadataValues);
1316 }
1317
1318 headerPosition = out->getPosition();
1319 writeHeader();
1320 }
1321
1322 ~WavAudioFormatWriter() override
1323 {
1324 writeHeader();
1325 }
1326
1327 //==============================================================================
1328 bool write (const int** data, int numSamples) override
1329 {
1330 jassert (numSamples >= 0);
1331 jassert (data != nullptr && *data != nullptr); // the input must contain at least one channel!
1332
1333 if (writeFailed)
1334 return false;
1335
1336 auto bytes = numChannels * (size_t) numSamples * bitsPerSample / 8;
1337 tempBlock.ensureSize (bytes, false);
1338
1339 switch (bitsPerSample)
1340 {
1345 default: jassertfalse; break;
1346 }
1347
1348 if (! output->write (tempBlock.getData(), bytes))
1349 {
1350 // failed to write to disk, so let's try writing the header.
1351 // If it's just run out of disk space, then if it does manage
1352 // to write the header, we'll still have a useable file..
1353 writeHeader();
1354 writeFailed = true;
1355 return false;
1356 }
1357
1358 bytesWritten += bytes;
1359 lengthInSamples += (uint64) numSamples;
1360 return true;
1361 }
1362
1363 bool flush() override
1364 {
1366 writeHeader();
1367
1369 return true;
1370
1371 // if this fails, you've given it an output stream that can't seek! It needs
1372 // to be able to seek back to write the header
1373 jassertfalse;
1374 return false;
1375 }
1376
1377private:
1378 MemoryBlock tempBlock, bwavChunk, axmlChunk, smplChunk, instChunk, cueChunk, listChunk, listInfoChunk, acidChunk, trckChunk;
1379 uint64 lengthInSamples = 0, bytesWritten = 0;
1380 int64 headerPosition = 0;
1381 bool writeFailed = false;
1382
1383 void writeHeader()
1384 {
1385 if ((bytesWritten & 1) != 0) // pad to an even length
1386 output->writeByte (0);
1387
1388 using namespace WavFileHelpers;
1389
1390 if (headerPosition != output->getPosition() && ! output->setPosition (headerPosition))
1391 {
1392 // if this fails, you've given it an output stream that can't seek! It needs to be
1393 // able to seek back to go back and write the header after the data has been written.
1394 jassertfalse;
1395 return;
1396 }
1397
1398 const size_t bytesPerFrame = numChannels * bitsPerSample / 8;
1399 uint64 audioDataSize = bytesPerFrame * lengthInSamples;
1400 auto channelMask = getChannelMaskFromChannelLayout (channelLayout);
1401
1402 const bool isRF64 = (bytesWritten >= 0x100000000LL);
1403 const bool isWaveFmtEx = isRF64 || (channelMask != 0);
1404
1405 int64 riffChunkSize = (int64) (4 /* 'RIFF' */ + 8 + 40 /* WAVEFORMATEX */
1406 + 8 + audioDataSize + (audioDataSize & 1)
1407 + chunkSize (bwavChunk)
1408 + chunkSize (axmlChunk)
1409 + chunkSize (smplChunk)
1410 + chunkSize (instChunk)
1411 + chunkSize (cueChunk)
1412 + chunkSize (listChunk)
1413 + chunkSize (listInfoChunk)
1414 + chunkSize (acidChunk)
1415 + chunkSize (trckChunk)
1416 + (8 + 28)); // (ds64 chunk)
1417
1418 riffChunkSize += (riffChunkSize & 1);
1419
1420 if (isRF64)
1421 writeChunkHeader (chunkName ("RF64"), -1);
1422 else
1423 writeChunkHeader (chunkName ("RIFF"), (int) riffChunkSize);
1424
1425 output->writeInt (chunkName ("WAVE"));
1426
1427 if (! isRF64)
1428 {
1429 #if ! JUCE_WAV_DO_NOT_PAD_HEADER_SIZE
1430 /* NB: This junk chunk is added for padding, so that the header is a fixed size
1431 regardless of whether it's RF64 or not. That way, we can begin recording a file,
1432 and when it's finished, can go back and write either a RIFF or RF64 header,
1433 depending on whether more than 2^32 samples were written.
1434
1435 The JUCE_WAV_DO_NOT_PAD_HEADER_SIZE macro allows you to disable this feature in case
1436 you need to create files for crappy WAV players with bugs that stop them skipping chunks
1437 which they don't recognise. But DO NOT USE THIS option unless you really have no choice,
1438 because it means that if you write more than 2^32 samples to the file, you'll corrupt it.
1439 */
1440 writeChunkHeader (chunkName ("JUNK"), 28 + (isWaveFmtEx? 0 : 24));
1441 output->writeRepeatedByte (0, 28 /* ds64 */ + (isWaveFmtEx? 0 : 24));
1442 #endif
1443 }
1444 else
1445 {
1446 #if JUCE_WAV_DO_NOT_PAD_HEADER_SIZE
1447 // If you disable padding, then you MUST NOT write more than 2^32 samples to a file.
1448 jassertfalse;
1449 #endif
1450
1451 writeChunkHeader (chunkName ("ds64"), 28); // chunk size for uncompressed data (no table)
1452 output->writeInt64 (riffChunkSize);
1453 output->writeInt64 ((int64) audioDataSize);
1454 output->writeRepeatedByte (0, 12);
1455 }
1456
1457 if (isWaveFmtEx)
1458 {
1459 writeChunkHeader (chunkName ("fmt "), 40);
1460 output->writeShort ((short) (uint16) 0xfffe); // WAVE_FORMAT_EXTENSIBLE
1461 }
1462 else
1463 {
1464 writeChunkHeader (chunkName ("fmt "), 16);
1465 output->writeShort (bitsPerSample < 32 ? (short) 1 /*WAVE_FORMAT_PCM*/
1466 : (short) 3 /*WAVE_FORMAT_IEEE_FLOAT*/);
1467 }
1468
1469 output->writeShort ((short) numChannels);
1470 output->writeInt ((int) sampleRate);
1471 output->writeInt ((int) (bytesPerFrame * sampleRate)); // nAvgBytesPerSec
1472 output->writeShort ((short) bytesPerFrame); // nBlockAlign
1473 output->writeShort ((short) bitsPerSample); // wBitsPerSample
1474
1475 if (isWaveFmtEx)
1476 {
1477 output->writeShort (22); // cbSize (size of the extension)
1478 output->writeShort ((short) bitsPerSample); // wValidBitsPerSample
1479 output->writeInt (channelMask);
1480
1481 const ExtensibleWavSubFormat& subFormat = bitsPerSample < 32 ? pcmFormat : IEEEFloatFormat;
1482
1483 output->writeInt ((int) subFormat.data1);
1484 output->writeShort ((short) subFormat.data2);
1485 output->writeShort ((short) subFormat.data3);
1486 output->write (subFormat.data4, sizeof (subFormat.data4));
1487 }
1488
1489 writeChunk (bwavChunk, chunkName ("bext"));
1490 writeChunk (axmlChunk, chunkName ("axml"));
1491 writeChunk (smplChunk, chunkName ("smpl"));
1492 writeChunk (instChunk, chunkName ("inst"), 7);
1493 writeChunk (cueChunk, chunkName ("cue "));
1494 writeChunk (listChunk, chunkName ("LIST"));
1495 writeChunk (listInfoChunk, chunkName ("LIST"));
1496 writeChunk (acidChunk, chunkName ("acid"));
1497 writeChunk (trckChunk, chunkName ("Trkn"));
1498
1499 writeChunkHeader (chunkName ("data"), isRF64 ? -1 : (int) (lengthInSamples * bytesPerFrame));
1500
1502 }
1503
1504 static size_t chunkSize (const MemoryBlock& data) noexcept { return data.getSize() > 0 ? (8 + data.getSize()) : 0; }
1505
1506 void writeChunkHeader (int chunkType, int size) const
1507 {
1508 output->writeInt (chunkType);
1509 output->writeInt (size);
1510 }
1511
1512 void writeChunk (const MemoryBlock& data, int chunkType, int size = 0) const
1513 {
1514 if (data.getSize() > 0)
1515 {
1516 writeChunkHeader (chunkType, size != 0 ? size : (int) data.getSize());
1517 *output << data;
1518 }
1519 }
1520
1521 static int getChannelMaskFromChannelLayout (const AudioChannelSet& channelLayout)
1522 {
1524 return 0;
1525
1526 // Don't add an extended format chunk for mono and stereo. Basically, all wav players
1527 // interpret a wav file with only one or two channels to be mono or stereo anyway.
1529 return 0;
1530
1531 auto channels = channelLayout.getChannelTypes();
1532 auto wavChannelMask = 0;
1533
1534 for (auto channel : channels)
1535 {
1536 int wavChannelBit = static_cast<int> (channel) - 1;
1537 jassert (wavChannelBit >= 0 && wavChannelBit <= 31);
1538
1539 wavChannelMask |= (1 << wavChannelBit);
1540 }
1541
1542 return wavChannelMask;
1543 }
1544
1545 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatWriter)
1546};
1547
1548//==============================================================================
1550{
1551public:
1553 : MemoryMappedAudioFormatReader (wavFile, reader, reader.dataChunkStart,
1554 reader.dataLength, reader.bytesPerFrame)
1555 {
1556 }
1557
1559 int64 startSampleInFile, int numSamples) override
1560 {
1562 startSampleInFile, numSamples, lengthInSamples);
1563
1564 if (map == nullptr || ! mappedSection.contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
1565 {
1566 jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read.
1567 return false;
1568 }
1569
1570 WavAudioFormatReader::copySampleData (bitsPerSample, usesFloatingPointData,
1572 sampleToPointer (startSampleInFile), (int) numChannels, numSamples);
1573 return true;
1574 }
1575
1576 void getSample (int64 sample, float* result) const noexcept override
1577 {
1578 auto num = (int) numChannels;
1579
1580 if (map == nullptr || ! mappedSection.contains (sample))
1581 {
1582 jassertfalse; // you must make sure that the window contains all the samples you're going to attempt to read.
1583
1584 zeromem (result, sizeof (float) * (size_t) num);
1585 return;
1586 }
1587
1588 auto dest = &result;
1589 auto source = sampleToPointer (sample);
1590
1591 switch (bitsPerSample)
1592 {
1598 break;
1599 default: jassertfalse; break;
1600 }
1601 }
1602
1603 void readMaxLevels (int64 startSampleInFile, int64 numSamples, Range<float>* results, int numChannelsToRead) override
1604 {
1605 numSamples = jmin (numSamples, lengthInSamples - startSampleInFile);
1606
1607 if (map == nullptr || numSamples <= 0 || ! mappedSection.contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
1608 {
1609 jassert (numSamples <= 0); // you must make sure that the window contains all the samples you're going to attempt to read.
1610
1611 for (int i = 0; i < numChannelsToRead; ++i)
1612 results[i] = {};
1613
1614 return;
1615 }
1616
1617 switch (bitsPerSample)
1618 {
1619 case 8: scanMinAndMax<AudioData::UInt8> (startSampleInFile, numSamples, results, numChannelsToRead); break;
1620 case 16: scanMinAndMax<AudioData::Int16> (startSampleInFile, numSamples, results, numChannelsToRead); break;
1621 case 24: scanMinAndMax<AudioData::Int24> (startSampleInFile, numSamples, results, numChannelsToRead); break;
1624 break;
1625 default: jassertfalse; break;
1626 }
1627 }
1628
1629private:
1630 template <typename SampleType>
1631 void scanMinAndMax (int64 startSampleInFile, int64 numSamples, Range<float>* results, int numChannelsToRead) const noexcept
1632 {
1633 for (int i = 0; i < numChannelsToRead; ++i)
1635 }
1636
1637 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedWavReader)
1638};
1639
1640//==============================================================================
1641WavAudioFormat::WavAudioFormat() : AudioFormat (wavFormatName, ".wav .bwf") {}
1643
1645{
1646 return { 8000, 11025, 12000, 16000, 22050, 32000, 44100,
1647 48000, 88200, 96000, 176400, 192000, 352800, 384000 };
1648}
1649
1651{
1652 return { 8, 16, 24, 32 };
1653}
1654
1655bool WavAudioFormat::canDoStereo() { return true; }
1656bool WavAudioFormat::canDoMono() { return true; }
1657
1659{
1660 auto channelTypes = channelSet.getChannelTypes();
1661
1662 // When
1663 if (channelSet.isDiscreteLayout())
1664 return true;
1665
1666 // WAV supports all channel types from left ... topRearRight
1667 for (auto channel : channelTypes)
1669 return false;
1670
1671 return true;
1672}
1673
1675{
1676 std::unique_ptr<WavAudioFormatReader> r (new WavAudioFormatReader (sourceStream));
1677
1678 #if JUCE_USE_OGGVORBIS
1679 if (r->isSubformatOggVorbis)
1680 {
1681 r->input = nullptr;
1683 }
1684 #endif
1685
1686 if (r->sampleRate > 0 && r->numChannels > 0 && r->bytesPerFrame > 0 && r->bitsPerSample <= 32)
1687 return r.release();
1688
1690 r->input = nullptr;
1691
1692 return nullptr;
1693}
1694
1699
1701{
1702 if (fin != nullptr)
1703 {
1704 WavAudioFormatReader reader (fin);
1705
1706 if (reader.lengthInSamples > 0)
1707 return new MemoryMappedWavReader (fin->getFile(), reader);
1708 }
1709
1710 return nullptr;
1711}
1712
1714 unsigned int numChannels, int bitsPerSample,
1715 const StringPairArray& metadataValues, int qualityOptionIndex)
1716{
1717 return createWriterFor (out, sampleRate, WavFileHelpers::canonicalWavChannelSet (static_cast<int> (numChannels)),
1718 bitsPerSample, metadataValues, qualityOptionIndex);
1719}
1720
1722 double sampleRate,
1723 const AudioChannelSet& channelLayout,
1724 int bitsPerSample,
1725 const StringPairArray& metadataValues,
1726 int /*qualityOptionIndex*/)
1727{
1728 if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample) && isChannelLayoutSupported (channelLayout))
1729 return new WavAudioFormatWriter (out, sampleRate, channelLayout,
1730 (unsigned int) bitsPerSample, metadataValues);
1731
1732 return nullptr;
1733}
1734
1735namespace WavFileHelpers
1736{
1737 static bool slowCopyWavFileWithNewMetadata (const File& file, const StringPairArray& metadata)
1738 {
1739 TemporaryFile tempFile (file);
1741
1742 std::unique_ptr<AudioFormatReader> reader (wav.createReaderFor (file.createInputStream(), true));
1743
1744 if (reader != nullptr)
1745 {
1746 std::unique_ptr<OutputStream> outStream (tempFile.getFile().createOutputStream());
1747
1748 if (outStream != nullptr)
1749 {
1750 std::unique_ptr<AudioFormatWriter> writer (wav.createWriterFor (outStream.get(), reader->sampleRate,
1751 reader->numChannels, (int) reader->bitsPerSample,
1752 metadata, 0));
1753
1754 if (writer != nullptr)
1755 {
1756 outStream.release();
1757
1758 bool ok = writer->writeFromAudioReader (*reader, 0, -1);
1759 writer.reset();
1760 reader.reset();
1761
1762 return ok && tempFile.overwriteTargetFileWithTemporary();
1763 }
1764 }
1765 }
1766
1767 return false;
1768 }
1769}
1770
1772{
1773 using namespace WavFileHelpers;
1774
1775 std::unique_ptr<WavAudioFormatReader> reader (static_cast<WavAudioFormatReader*> (createReaderFor (wavFile.createInputStream(), true)));
1776
1777 if (reader != nullptr)
1778 {
1779 auto bwavPos = reader->bwavChunkStart;
1780 auto bwavSize = reader->bwavSize;
1781 reader.reset();
1782
1783 if (bwavSize > 0)
1784 {
1785 auto chunk = BWAVChunk::createFrom (newMetadata);
1786
1787 if (chunk.getSize() <= (size_t) bwavSize)
1788 {
1789 // the new one will fit in the space available, so write it directly..
1790 auto oldSize = wavFile.getSize();
1791
1792 {
1794
1795 if (out.openedOk())
1796 {
1797 out.setPosition (bwavPos);
1798 out << chunk;
1799 out.setPosition (oldSize);
1800 }
1801 }
1802
1803 jassert (wavFile.getSize() == oldSize);
1804 return true;
1805 }
1806 }
1807 }
1808
1809 return slowCopyWavFileWithNewMetadata (wavFile, newMetadata);
1810}
1811
1812//==============================================================================
1813#if JUCE_UNIT_TESTS
1814
1815struct WaveAudioFormatTests : public UnitTest
1816{
1817 WaveAudioFormatTests() : UnitTest ("Wave audio format tests") {}
1818
1819 void runTest() override
1820 {
1821 beginTest ("Setting up metadata");
1822
1823 StringPairArray metadataValues = WavAudioFormat::createBWAVMetadata ("description",
1824 "originator",
1825 "originatorRef",
1828 "codingHistory");
1829
1830 for (int i = numElementsInArray (WavFileHelpers::ListInfoChunk::types); --i >= 0;)
1831 metadataValues.set (WavFileHelpers::ListInfoChunk::types[i],
1832 WavFileHelpers::ListInfoChunk::types[i]);
1833
1834 if (metadataValues.size() > 0)
1835 metadataValues.set ("MetaDataSource", "WAV");
1836
1837 metadataValues.addArray (createDefaultSMPLMetadata());
1838
1839 WavAudioFormat format;
1840 MemoryBlock memoryBlock;
1841
1842 {
1843 beginTest ("Creating a basic wave writer");
1844
1845 std::unique_ptr<AudioFormatWriter> writer (format.createWriterFor (new MemoryOutputStream (memoryBlock, false),
1847 32, metadataValues, 0));
1848 expect (writer != nullptr);
1849
1850 AudioBuffer<float> buffer (numTestAudioBufferChannels, numTestAudioBufferSamples);
1851 buffer.clear();
1852
1853 beginTest ("Writing audio data to the basic wave writer");
1854 expect (writer->writeFromAudioSampleBuffer (buffer, 0, numTestAudioBufferSamples));
1855 }
1856
1857 {
1858 beginTest ("Creating a basic wave reader");
1859
1860 std::unique_ptr<AudioFormatReader> reader (format.createReaderFor (new MemoryInputStream (memoryBlock, false), false));
1861 expect (reader != nullptr);
1862 expect (reader->metadataValues == metadataValues, "Somehow, the metadata is different!");
1863 }
1864 }
1865
1866private:
1867 enum
1868 {
1871 };
1872
1873 StringPairArray createDefaultSMPLMetadata() const
1874 {
1875 StringPairArray m;
1876
1877 m.set ("Manufacturer", "0");
1878 m.set ("Product", "0");
1879 m.set ("SamplePeriod", "0");
1880 m.set ("MidiUnityNote", "60");
1881 m.set ("MidiPitchFraction", "0");
1882 m.set ("SmpteFormat", "0");
1883 m.set ("SmpteOffset", "0");
1884 m.set ("NumSampleLoops", "0");
1885 m.set ("SamplerData", "0");
1886
1887 return m;
1888 }
1889
1890 JUCE_DECLARE_NON_COPYABLE (WaveAudioFormatTests)
1891};
1892
1893static const WaveAudioFormatTests waveAudioFormatTests;
1894
1895#endif
1896
1897} // namespace juce
Holds a resizable array of primitive or copy-by-value objects.
Definition juce_Array.h:60
bool isEmpty() const noexcept
Returns true if the array is empty, false otherwise.
Definition juce_Array.h:226
int size() const noexcept
Returns the current number of elements in the array.
Definition juce_Array.h:219
Array()=default
Creates an empty array.
void set(int indexToChange, ParameterType newValue)
Replaces an element with a new value.
Definition juce_Array.h:499
Represents a set of audio channel types.
static AudioChannelSet JUCE_CALLTYPE quadraphonic()
Creates a set for quadraphonic surround setup (left, right, leftSurround, rightSurround)
static AudioChannelSet JUCE_CALLTYPE create5point0()
Creates a set for a 5.0 surround setup (left, right, centre, leftSurround, rightSurround).
int size() const noexcept
Returns the number of channels in the set.
bool isDiscreteLayout() const noexcept
Returns if this is a channel layout made-up of discrete channels.
static AudioChannelSet JUCE_CALLTYPE mono()
Creates a one-channel mono set (centre).
static AudioChannelSet JUCE_CALLTYPE stereo()
Creates a set containing a stereo set (left, right).
ChannelType
Represents different audio channel types.
@ topRearRight
Top Rear Right channel.
@ discreteChannel0
Non-typed individual channels are indexed upwards from this value.
static AudioChannelSet JUCE_CALLTYPE create5point1()
Creates a set for a 5.1 surround setup (left, right, centre, leftSurround, rightSurround,...
static AudioChannelSet JUCE_CALLTYPE create7point0SDDS()
Creates a set for a SDDS 7.0 surround setup (left, right, centre, leftSurround, rightSurround,...
static AudioChannelSet JUCE_CALLTYPE create7point1SDDS()
Creates a set for a 7.1 surround setup (left, right, centre, leftSurround, rightSurround,...
static AudioChannelSet JUCE_CALLTYPE canonicalChannelSet(int numChannels)
Create a canonical channel set for a given number of channels.
static AudioChannelSet JUCE_CALLTYPE discreteChannels(int numChannels)
Creates a set of untyped discrete channels.
static AudioChannelSet JUCE_CALLTYPE createLCR()
Creates a set containing an LCR set (left, right, centre).
Array< ChannelType > getChannelTypes() const
Returns an array of all the types in this channel set.
Reads samples from an audio file stream.
InputStream * input
The input stream, for use by subclasses.
bool usesFloatingPointData
Indicates whether the data is floating-point or fixed.
StringPairArray metadataValues
A set of metadata values that the reader has pulled out of the stream.
static void clearSamplesBeyondAvailableLength(int **destChannels, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int &numSamples, int64 fileLengthInSamples)
Used by AudioFormatReader subclasses to clear any parts of the data blocks that lie beyond the end of...
int64 lengthInSamples
The total number of samples in the audio stream.
double sampleRate
The sample-rate of the stream.
unsigned int bitsPerSample
The number of bits per sample, e.g.
unsigned int numChannels
The total number of channels in the audio stream.
Writes samples to an audio file stream.
unsigned int numChannels
The number of channels being written to the stream.
double sampleRate
The sample rate of the stream.
bool usesFloatingPointData
True if it's a floating-point format, false if it's fixed-point.
unsigned int bitsPerSample
The bit depth of the file.
AudioChannelSet channelLayout
The audio channel layout that the writer should use.
OutputStream * output
The output stream for use by subclasses.
Subclasses of AudioFormat are used to read and write different audio file formats.
An arbitrarily large integer class.
static JUCE_CONSTEXPR uint16 swap(uint16 value) noexcept
Swaps the upper and lower bytes of a 16-bit integer.
static Type swapIfBigEndian(Type value) noexcept
Swaps the byte order of a signed or unsigned integer if the CPU is big-endian.
static JUCE_CONSTEXPR uint32 littleEndianInt(const void *bytes) noexcept
Turns 4 bytes into a little-endian integer.
static juce_wchar toUpperCase(juce_wchar character) noexcept
Converts a character to upper-case.
An input stream that reads from a local file.
An output stream that writes into a local file.
Represents a local file or directory.
Definition juce_File.h:45
FileInputStream * createInputStream() const
Creates a stream to read from this file.
The base class for streams that read data.
virtual int64 getPosition()=0
Returns the offset of the next byte that will be read from the stream.
virtual int64 readInt64()
Reads eight bytes from the stream as a little-endian 64-bit value.
virtual bool setPosition(int64 newPosition)=0
Tries to move the current read position of the stream.
virtual bool isExhausted()=0
Returns true if the stream has no more data to read.
virtual short readShort()
Reads two bytes from the stream as a little-endian 16-bit value.
virtual void skipNextBytes(int64 numBytesToSkip)
Reads and discards a number of bytes from the stream.
virtual size_t readIntoMemoryBlock(MemoryBlock &destBlock, ssize_t maxNumBytesToRead=-1)
Reads from the stream and appends the data to a MemoryBlock.
virtual int read(void *destBuffer, int maxBytesToRead)=0
Reads some data from the stream into a memory buffer.
virtual int readInt()
Reads four bytes from the stream as a little-endian 32-bit value.
A class to hold a resizable block of raw data.
void ensureSize(const size_t minimumSize, bool initialiseNewSpaceToZero=false)
Increases the block's size only if it's smaller than a given size.
void setSize(const size_t newSize, bool initialiseNewSpaceToZero=false)
Resizes the memory block.
void fillWith(uint8 valueToUse) noexcept
Fills the entire memory block with a repeated byte value.
void * getData() const noexcept
Returns a void pointer to the data.
A specialised type of AudioFormatReader that uses a MemoryMappedFile to read directly from an audio f...
const void * sampleToPointer(int64 sample) const noexcept
Converts a sample index to a pointer to the mapped file memory.
void readMaxLevels(int64 startSampleInFile, int64 numSamples, Range< float > *results, int numChannelsToRead) override
Finds the highest and lowest sample levels from a section of the audio stream.
void getSample(int64 sample, float *result) const noexcept override
Returns the samples for all channels at a given sample position.
bool readSamples(int **destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) override
Subclasses must implement this method to perform the low-level read operation.
Writes data to an internal memory buffer, which grows as required.
Reads and writes the Ogg-Vorbis audio format.
AudioFormatReader * createReaderFor(InputStream *sourceStream, bool deleteStreamIfOpeningFails) override
Tries to create an object that can read from a stream containing audio data in this format.
The base class for streams that write data to some kind of destination.
virtual bool write(const void *dataToWrite, size_t numberOfBytes)=0
Writes a block of data to the stream.
virtual bool writeRepeatedByte(uint8 byte, size_t numTimesToRepeat)
Writes a byte to the output stream a given number of times.
virtual int64 getPosition()=0
Returns the stream's current position.
virtual bool writeByte(char byte)
Writes a single byte to the stream.
virtual bool writeShort(short value)
Writes a 16-bit integer to the stream in a little-endian byte order.
virtual bool writeInt64(int64 value)
Writes a 64-bit integer to the stream in a little-endian byte order.
virtual bool setPosition(int64 newPosition)=0
Tries to move the stream's output position.
virtual bool writeInt(int value)
Writes a 32-bit integer to the stream in a little-endian byte order.
JUCE_CONSTEXPR bool contains(const ValueType position) const noexcept
Returns true if the given position lies inside this range.
Definition juce_Range.h:213
A container for holding a set of strings which are keyed by another string.
String getValue(StringRef, const String &defaultReturnValue) const
Finds the value corresponding to a key string.
bool containsKey(StringRef key) const noexcept
Returns true if the given key exists.
void set(const String &key, const String &value)
Adds or amends a key/value pair.
int size() const noexcept
Returns the number of strings in the array.
const StringArray & getAllKeys() const noexcept
Returns a list of all keys in the array.
The JUCE String class!
Definition juce_String.h:43
static String createStringFromData(const void *data, int size)
Creates a string from data in an unknown format.
static String fromUTF8(const char *utf8buffer, int bufferSizeBytes=-1)
Creates a String from a UTF-8 encoded buffer.
int getIntValue() const noexcept
Reads the value of the string as a decimal number (up to 32 bits in size).
Manages a temporary file, which will be deleted when this object is deleted.
Holds an absolute date and time.
Definition juce_Time.h:41
static Time JUCE_CALLTYPE getCurrentTime() noexcept
Returns a Time object that is set to the current system time.
This is a base class for classes that perform a unit test.
bool readSamples(int **destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) override
Subclasses must implement this method to perform the low-level read operation.
AudioChannelSet getChannelLayout() override
Get the channel layout of the audio stream.
bool flush() override
Some formats may support a flush operation that makes sure the file is in a valid state before carryi...
bool write(const int **data, int numSamples) override
Writes a set of samples to the audio stream.
Reads and Writes WAV format audio files.
static const char *const riffInfoCopyright
Metadata property name used in INFO chunks.
static const char *const acidRootSet
Metadata property name used in acid chunks.
static const char *const riffInfoDirectory
Metadata property name used in INFO chunks.
static const char *const bwavCodingHistory
Metadata property name used in BWAV chunks.
static const char *const bwavTimeReference
Metadata property name used in BWAV chunks.
static const char *const acidDiskBased
Metadata property name used in acid chunks.
static const char *const acidOneShot
Metadata property name used in acid chunks.
static const char *const riffInfoOrganisation
Metadata property name used in INFO chunks.
static const char *const riffInfoFirstLanguage
Metadata property name used in INFO chunks.
static const char *const riffInfoEncodedBy
Metadata property name used in INFO chunks.
static const char *const riffInfoCommissioned
Metadata property name used in INFO chunks.
static const char *const riffInfoMusicBy
Metadata property name used in INFO chunks.
static const char *const riffInfoSharpness
Metadata property name used in INFO chunks.
static const char *const riffInfoStatistics
Metadata property name used in INFO chunks.
static const char *const riffInfoNinthLanguage
Metadata property name used in INFO chunks.
static const char *const riffInfoDefaultAudioStream
Metadata property name used in INFO chunks.
static const char *const riffInfoGenre
Metadata property name used in INFO chunks.
bool canDoMono() override
Returns true if the format can do 1-channel audio.
static const char *const riffInfoMoreInfoBannerImage
Metadata property name used in INFO chunks.
static const char *const riffInfoVegasVersionMajor
Metadata property name used in INFO chunks.
static const char *const riffInfoLocation
Metadata property name used in INFO chunks.
AudioFormatReader * createReaderFor(InputStream *sourceStream, bool deleteStreamIfOpeningFails) override
Tries to create an object that can read from a stream containing audio data in this format.
static const char *const riffInfoRate
Metadata property name used in INFO chunks.
static const char *const riffInfoCostumeDesigner
Metadata property name used in INFO chunks.
static const char *const riffInfoVersion
Metadata property name used in INFO chunks.
static const char *const riffInfoLightness
Metadata property name used in INFO chunks.
static const char *const riffInfoProductionStudio
Metadata property name used in INFO chunks.
static const char *const riffInfoProducedBy
Metadata property name used in INFO chunks.
static const char *const riffInfoEighthLanguage
Metadata property name used in INFO chunks.
static const char *const riffInfoCropped
Metadata property name used in INFO chunks.
bool canDoStereo() override
Returns true if the format can do 2-channel audio.
static const char *const riffInfoRating
Metadata property name used in INFO chunks.
static const char *const riffInfoURL
Metadata property name used in INFO chunks.
static const char *const ISRC
Metadata property name used when reading an ISRC code from an AXML chunk.
static const char *const riffInfoMoreInfoBannerURL
Metadata property name used in INFO chunks.
static const char *const riffInfoStartTimecode
Metadata property name used in INFO chunks.
static const char *const bwavOriginatorRef
Metadata property name used in BWAV chunks.
static const char *const riffInfoTitle
Metadata property name used in INFO chunks.
static const char *const riffInfoArtist
Metadata property name used in INFO chunks.
static const char *const riffInfoSixthLanguage
Metadata property name used in INFO chunks.
static const char *const riffInfoSecondaryGenre
Metadata property name used in INFO chunks.
static const char *const riffInfoFifthLanguage
Metadata property name used in INFO chunks.
static const char *const riffInfoDotsPerInch
Metadata property name used in INFO chunks.
static const char *const riffInfoDistributedBy
Metadata property name used in INFO chunks.
static const char *const riffInfoStarring_ISTR
Metadata property name used in INFO chunks.
static const char *const riffInfoProductName
Metadata property name used in INFO chunks.
static const char *const riffInfoKeywords
Metadata property name used in INFO chunks.
static const char *const riffInfoRippedBy
Metadata property name used in INFO chunks.
static const char *const riffInfoLanguage
Metadata property name used in INFO chunks.
static const char *const riffInfoDateTimeOriginal
Metadata property name used in INFO chunks.
static const char *const acidizerFlag
Metadata property name used in acid chunks.
static const char *const riffInfoBaseURL
Metadata property name used in INFO chunks.
MemoryMappedAudioFormatReader * createMemoryMappedReader(const File &) override
Attempts to create a MemoryMappedAudioFormatReader, if possible for this format.
bool isChannelLayoutSupported(const AudioChannelSet &channelSet) override
Returns true if the channel layout is supported by this format.
static const char *const riffInfoProductionDesigner
Metadata property name used in INFO chunks.
static const char *const acidDenominator
Metadata property name used in acid chunks.
static const char *const riffInfoVegasVersionMinor
Metadata property name used in INFO chunks.
static const char *const riffInfoLength
Metadata property name used in INFO chunks.
bool replaceMetadataInFile(const File &wavFile, const StringPairArray &newMetadata)
Utility function to replace the metadata in a wav file with a new set of values.
~WavAudioFormat() override
Destructor.
static const char *const riffInfoTechnician
Metadata property name used in INFO chunks.
static const char *const riffInfoSoftware
Metadata property name used in INFO chunks.
static const char *const riffInfoStarring_STAR
Metadata property name used in INFO chunks.
static const char *const riffInfoDateCreated
Metadata property name used in INFO chunks.
static const char *const riffInfoSeventhLanguage
Metadata property name used in INFO chunks.
static const char *const acidBeats
Metadata property name used in acid chunks.
AudioFormatWriter * createWriterFor(OutputStream *streamToWriteTo, double sampleRateToUse, unsigned int numberOfChannels, int bitsPerSample, const StringPairArray &metadataValues, int qualityOptionIndex) override
Tries to create an object that can write to a stream with this audio format.
static const char *const riffInfoLogoIconURL
Metadata property name used in INFO chunks.
static const char *const tracktionLoopInfo
Metadata property name used when reading a WAV file with a Tracktion chunk.
static const char *const acidNumerator
Metadata property name used in acid chunks.
static const char *const bwavOriginationDate
Metadata property name used in BWAV chunks.
static const char *const riffInfoComments
Metadata property name used in INFO chunks.
static const char *const riffInfoNumberOfParts
Metadata property name used in INFO chunks.
static const char *const bwavDescription
Metadata property name used in BWAV chunks.
static const char *const riffInfoSoundSchemeTitle
Metadata property name used in INFO chunks.
Array< int > getPossibleSampleRates() override
Returns a set of sample rates that the format can read and write.
static const char *const riffInfoWatermarkURL
Metadata property name used in INFO chunks.
static const char *const riffInfoTrackNo
Metadata property name used in INFO chunks.
static const char *const riffInfoMedium
Metadata property name used in INFO chunks.
static const char *const acidStretch
Metadata property name used in acid chunks.
Array< int > getPossibleBitDepths() override
Returns a set of bit depths that the format can read and write.
static const char *const riffInfoThirdLanguage
Metadata property name used in INFO chunks.
static const char *const bwavOriginationTime
Metadata property name used in BWAV chunks.
static const char *const riffInfoArchivalLocation
Metadata property name used in INFO chunks.
static const char *const riffInfoMoreInfoText
Metadata property name used in INFO chunks.
static const char *const riffInfoCinematographer
Metadata property name used in INFO chunks.
static const char *const riffInfoFourthLanguage
Metadata property name used in INFO chunks.
static const char *const riffInfoSubject
Metadata property name used in INFO chunks.
static const char *const riffInfoRated
Metadata property name used in INFO chunks.
static const char *const riffInfoDimension
Metadata property name used in INFO chunks.
static const char *const riffInfoEditedBy
Metadata property name used in INFO chunks.
static const char *const riffInfoYear
Metadata property name used in INFO chunks.
static const char *const riffInfoComment2
Metadata property name used in INFO chunks.
static StringPairArray createBWAVMetadata(const String &description, const String &originator, const String &originatorRef, Time dateAndTime, int64 timeReferenceSamples, const String &codingHistory)
Utility function to fill out the appropriate metadata for a BWAV file.
static const char *const riffInfoTrackNumber
Metadata property name used in INFO chunks.
static const char *const riffInfoEngineer
Metadata property name used in INFO chunks.
static const char *const riffInfoWrittenBy
Metadata property name used in INFO chunks.
static const char *const riffInfoTimeCode
Metadata property name used in INFO chunks.
static const char *const riffInfoSourceFrom
Metadata property name used in INFO chunks.
static const char *const riffInfoSource
Metadata property name used in INFO chunks.
static const char *const riffInfoLogoURL
Metadata property name used in INFO chunks.
static const char *const riffInfoCountry
Metadata property name used in INFO chunks.
static const char *const riffInfoSecondLanguage
Metadata property name used in INFO chunks.
static const char *const riffInfoComment
Metadata property name used in INFO chunks.
static const char *const riffInfoTapeName
Metadata property name used in INFO chunks.
static const char *const riffInfoEndTimecode
Metadata property name used in INFO chunks.
WavAudioFormat()
Creates a format object.
static const char *const riffInfoPart
Metadata property name used in INFO chunks.
static const char *const bwavOriginator
Metadata property name used in BWAV chunks.
static const char *const acidTempo
Metadata property name used in acid chunks.
static const char *const acidRootNote
Metadata property name used in acid chunks.
static const char *const riffInfoMoreInfoURL
Metadata property name used in INFO chunks.
Used by AudioFormatReader subclasses to copy data to different formats.
Used by AudioFormatWriter subclasses to copy data to different formats.
AcidChunk(InputStream &input, size_t length)
Reads an acid RIFF chunk from a stream positioned just after the size byte.