29using StringMap = std::unordered_map<String, String>;
31static auto toMap (
const StringPairArray& array)
35 for (
auto i = 0; i < array.size(); ++i)
36 result[array.getAllKeys()[i]] = array.getAllValues()[i];
41static auto getValueWithDefault (
const StringMap& m,
const String& key,
const String& fallback = {})
43 const auto iter = m.find (key);
44 return iter != m.cend() ? iter->second : fallback;
47static const char*
const wavFormatName =
"WAV file";
60 const String& originatorRef,
62 int64 timeReferenceSamples,
63 const String& codingHistory)
261namespace WavFileHelpers
264 constexpr inline size_t roundUpSize (
size_t sz)
noexcept {
return (sz + 3) & ~3u; }
267 #pragma pack (push, 1)
272 char description[256];
274 char originatorRef[32];
275 char originationDate[10];
276 char originationTime[8];
282 char codingHistory[1];
284 void copyTo (StringMap& values,
const int totalSize)
const
294 auto time = (((int64) timeHigh) << 32) + timeLow;
300 static MemoryBlock createFrom (
const StringMap& values)
305 auto* b = (BWAVChunk*) data.getData();
321 if (b->description[0] != 0
322 || b->originator[0] != 0
323 || b->originationDate[0] != 0
324 || b->originationTime[0] != 0
325 || b->codingHistory[0] != 0
337 inline AudioChannelSet canonicalWavChannelSet (
int numChannels)
367 uint32 midiUnityNote;
368 uint32 midiPitchFraction;
371 uint32 numSampleLoops;
375 template <
typename NameType>
376 static void setValue (StringMap& values, NameType name, uint32 val)
381 static void setValue (StringMap& values,
int prefix,
const char* name, uint32 val)
383 setValue (values,
"Loop" + String (prefix) + name, val);
386 void copyTo (StringMap& values,
const int totalSize)
const
388 setValue (values,
"Manufacturer", manufacturer);
389 setValue (values,
"Product", product);
390 setValue (values,
"SamplePeriod", samplePeriod);
391 setValue (values,
"MidiUnityNote", midiUnityNote);
392 setValue (values,
"MidiPitchFraction", midiPitchFraction);
393 setValue (values,
"SmpteFormat", smpteFormat);
394 setValue (values,
"SmpteOffset", smpteOffset);
395 setValue (values,
"NumSampleLoops", numSampleLoops);
396 setValue (values,
"SamplerData", samplerData);
398 for (
int i = 0; i < (int) numSampleLoops; ++i)
400 if ((uint8*) (loops + (i + 1)) > ((uint8*)
this) + totalSize)
403 setValue (values, i,
"Identifier", loops[i].identifier);
404 setValue (values, i,
"Type", loops[i].type);
405 setValue (values, i,
"Start", loops[i].start);
406 setValue (values, i,
"End", loops[i].end);
407 setValue (values, i,
"Fraction", loops[i].fraction);
408 setValue (values, i,
"PlayCount", loops[i].playCount);
412 template <
typename NameType>
413 static uint32 getValue (
const StringMap& values, NameType name,
const char* def)
418 static uint32 getValue (
const StringMap& values,
int prefix,
const char* name,
const char* def)
420 return getValue (values,
"Loop" + String (prefix) + name, def);
423 static MemoryBlock createFrom (
const StringMap& values)
426 auto numLoops = jmin (64, getValueWithDefault (values,
"NumSampleLoops",
"0").getIntValue());
428 data.setSize (roundUpSize (
sizeof (SMPLChunk) + (
size_t) (jmax (0, numLoops - 1)) *
sizeof (SampleLoop)),
true);
430 auto s =
static_cast<SMPLChunk*
> (data.getData());
432 s->manufacturer = getValue (values,
"Manufacturer",
"0");
433 s->product = getValue (values,
"Product",
"0");
434 s->samplePeriod = getValue (values,
"SamplePeriod",
"0");
435 s->midiUnityNote = getValue (values,
"MidiUnityNote",
"60");
436 s->midiPitchFraction = getValue (values,
"MidiPitchFraction",
"0");
437 s->smpteFormat = getValue (values,
"SmpteFormat",
"0");
438 s->smpteOffset = getValue (values,
"SmpteOffset",
"0");
440 s->samplerData = getValue (values,
"SamplerData",
"0");
442 for (
int i = 0; i < numLoops; ++i)
444 auto& loop = s->loops[i];
445 loop.identifier = getValue (values, i,
"Identifier",
"0");
446 loop.type = getValue (values, i,
"Type",
"0");
447 loop.start = getValue (values, i,
"Start",
"0");
448 loop.end = getValue (values, i,
"End",
"0");
449 loop.fraction = getValue (values, i,
"Fraction",
"0");
450 loop.playCount = getValue (values, i,
"PlayCount",
"0");
468 static void setValue (StringMap& values,
const char* name,
int val)
470 values[name] = String (val);
473 void copyTo (StringMap& values)
const
475 setValue (values,
"MidiUnityNote", baseNote);
476 setValue (values,
"Detune", detune);
477 setValue (values,
"Gain", gain);
478 setValue (values,
"LowNote", lowNote);
479 setValue (values,
"HighNote", highNote);
480 setValue (values,
"LowVelocity", lowVelocity);
481 setValue (values,
"HighVelocity", highVelocity);
484 static int8 getValue (
const StringMap& values,
const char* name,
const char* def)
486 return (int8) getValueWithDefault (values, name, def).getIntValue();
489 static MemoryBlock createFrom (
const StringMap& values)
493 if ( values.find (
"LowNote") != values.cend()
494 && values.find (
"HighNote") != values.cend())
496 data.setSize (8,
true);
497 auto* inst =
static_cast<InstChunk*
> (data.getData());
499 inst->baseNote = getValue (values,
"MidiUnityNote",
"60");
500 inst->detune = getValue (values,
"Detune",
"0");
501 inst->gain = getValue (values,
"Gain",
"0");
502 inst->lowNote = getValue (values,
"LowNote",
"0");
503 inst->highNote = getValue (values,
"HighNote",
"127");
504 inst->lowVelocity = getValue (values,
"LowVelocity",
"1");
505 inst->highVelocity = getValue (values,
"HighVelocity",
"127");
528 static void setValue (StringMap& values,
int prefix,
const char* name, uint32 val)
533 void copyTo (StringMap& values,
const int totalSize)
const
537 for (
int i = 0; i < (int) numCues; ++i)
539 if ((uint8*) (cues + (i + 1)) > ((uint8*)
this) + totalSize)
542 setValue (values, i,
"Identifier", cues[i].identifier);
543 setValue (values, i,
"Order", cues[i].order);
544 setValue (values, i,
"ChunkID", cues[i].chunkID);
545 setValue (values, i,
"ChunkStart", cues[i].chunkStart);
546 setValue (values, i,
"BlockStart", cues[i].blockStart);
547 setValue (values, i,
"Offset", cues[i].offset);
551 static MemoryBlock createFrom (
const StringMap& values)
554 const int numCues = getValueWithDefault (values,
"NumCuePoints",
"0").getIntValue();
558 data.setSize (roundUpSize (
sizeof (CueChunk) + (
size_t) (numCues - 1) *
sizeof (Cue)),
true);
560 auto c =
static_cast<CueChunk*
> (data.getData());
564 const String dataChunkID (chunkName (
"data"));
568 Array<uint32> identifiers;
571 for (
int i = 0; i < numCues; ++i)
573 auto prefix =
"Cue" + String (i);
574 auto identifier = (uint32) getValueWithDefault (values, prefix +
"Identifier",
"0").getIntValue();
577 jassert (! identifiers.contains (identifier));
578 identifiers.add (identifier);
581 auto order = getValueWithDefault (values, prefix +
"Order", String (nextOrder)).getIntValue();
582 nextOrder = jmax (nextOrder, order) + 1;
584 auto& cue = c->cues[i];
587 cue.chunkID =
ByteOrder::swapIfBigEndian ((uint32) getValueWithDefault (values, prefix +
"ChunkID", dataChunkID).getIntValue());
588 cue.chunkStart =
ByteOrder::swapIfBigEndian ((uint32) getValueWithDefault (values, prefix +
"ChunkStart",
"0").getIntValue());
589 cue.blockStart =
ByteOrder::swapIfBigEndian ((uint32) getValueWithDefault (values, prefix +
"BlockStart",
"0").getIntValue());
602 static int getValue (
const StringMap& values,
const String& name)
604 return getValueWithDefault (values, name,
"0").getIntValue();
607 static int getValue (
const StringMap& values,
const String& prefix,
const char* name)
609 return getValue (values, prefix + name);
612 static void appendLabelOrNoteChunk (
const StringMap& values,
const String& prefix,
613 const int chunkType, MemoryOutputStream& out)
615 auto label = getValueWithDefault (values, prefix +
"Text", prefix);
616 auto labelLength = (int) label.getNumBytesAsUTF8() + 1;
617 auto chunkLength = 4 + labelLength + (labelLength & 1);
619 out.writeInt (chunkType);
620 out.writeInt (chunkLength);
621 out.writeInt (getValue (values, prefix,
"Identifier"));
622 out.write (label.toUTF8(), (
size_t) labelLength);
624 if ((out.getDataSize() & 1) != 0)
628 static void appendExtraChunk (
const StringMap& values,
const String& prefix, MemoryOutputStream& out)
630 auto text = getValueWithDefault (values, prefix +
"Text", prefix);
632 auto textLength = (int) text.getNumBytesAsUTF8() + 1;
633 auto chunkLength = textLength + 20 + (textLength & 1);
635 out.writeInt (chunkName (
"ltxt"));
636 out.writeInt (chunkLength);
637 out.writeInt (getValue (values, prefix,
"Identifier"));
638 out.writeInt (getValue (values, prefix,
"SampleLength"));
639 out.writeInt (getValue (values, prefix,
"Purpose"));
640 out.writeShort ((
short) getValue (values, prefix,
"Country"));
641 out.writeShort ((
short) getValue (values, prefix,
"Language"));
642 out.writeShort ((
short) getValue (values, prefix,
"Dialect"));
643 out.writeShort ((
short) getValue (values, prefix,
"CodePage"));
644 out.write (text.toUTF8(), (
size_t) textLength);
646 if ((out.getDataSize() & 1) != 0)
650 static MemoryBlock createFrom (
const StringMap& values)
652 auto numCueLabels = getValue (values,
"NumCueLabels");
653 auto numCueNotes = getValue (values,
"NumCueNotes");
654 auto numCueRegions = getValue (values,
"NumCueRegions");
656 MemoryOutputStream out;
658 if (numCueLabels + numCueNotes + numCueRegions > 0)
660 out.writeInt (chunkName (
"adtl"));
662 for (
int i = 0; i < numCueLabels; ++i)
663 appendLabelOrNoteChunk (values,
"CueLabel" + String (i), chunkName (
"labl"), out);
665 for (
int i = 0; i < numCueNotes; ++i)
666 appendLabelOrNoteChunk (values,
"CueNote" + String (i), chunkName (
"note"), out);
668 for (
int i = 0; i < numCueRegions; ++i)
669 appendExtraChunk (values,
"CueRegion" + String (i), out);
672 return out.getMemoryBlock();
678 namespace ListInfoChunk
680 static const char*
const types[] =
765 static bool isMatchingTypeIgnoringCase (
const int value,
const char*
const name)
noexcept
767 for (
int i = 0; i < 4; ++i)
774 static void addToMetadata (StringMap& values,
InputStream& input, int64 chunkEnd)
778 auto infoType = input.
readInt();
783 infoLength = jmin (infoLength, (int64) input.
readInt());
788 for (
auto& type : types)
790 if (isMatchingTypeIgnoringCase (infoType, type))
803 static bool writeValue (
const StringMap& values,
MemoryOutputStream& out,
const char* paramName)
805 auto value = getValueWithDefault (values, paramName, {});
810 auto valueLength = (int) value.getNumBytesAsUTF8() + 1;
811 auto chunkLength = valueLength + (valueLength & 1);
813 out.
writeInt (chunkName (paramName));
815 out.
write (value.toUTF8(), (
size_t) valueLength);
823 static MemoryBlock createFrom (
const StringMap& values)
827 bool anyParamsDefined =
false;
829 for (
auto& type : types)
830 if (writeValue (values, out, type))
831 anyParamsDefined =
true;
844 input.
read (
this, (
int) jmin (
sizeof (*
this), length));
847 AcidChunk (
const StringMap& values)
866 if (iter != values.cend())
867 tempo = swapFloatByteOrder (iter->second.getFloatValue());
870 static MemoryBlock createFrom (
const StringMap& values)
872 return AcidChunk (values).toMemoryBlock();
875 MemoryBlock toMemoryBlock()
const
877 return (flags != 0 || rootNote != 0 || numBeats != 0 || meterDenominator != 0 || meterNumerator != 0)
878 ? MemoryBlock (
this,
sizeof (*
this)) : MemoryBlock();
881 void addToMetadata (StringMap& values)
const
898 void setBoolFlag (StringMap& values,
const char* name, uint32 mask)
const
903 static uint32 getFlagIfPresent (
const StringMap& values,
const char* name, uint32 flag)
908 static float swapFloatByteOrder (
const float x)
noexcept
910 #ifdef JUCE_BIG_ENDIAN
911 union { uint32 asInt;
float asFloat; } n;
925 uint16 meterDenominator;
926 uint16 meterNumerator;
932 struct TracktionChunk
934 static MemoryBlock createFrom (
const StringMap& values)
936 MemoryOutputStream out;
943 if ((out.getDataSize() & 1) != 0)
947 return out.getMemoryBlock();
954 static const std::unordered_set<String> aswgMetadataKeys
1041 static void addToMetadata (StringMap& destValues,
const String& source)
1043 if (
auto xml = parseXML (source))
1045 if (xml->hasTagName (
"BWFXML"))
1050 if (
const auto* aswgElement = xml->getChildByName (
"ASWG"))
1052 for (
const auto* entry : aswgElement->getChildIterator())
1054 const auto& tag = entry->getTagName();
1056 if (aswgMetadataKeys.find (tag) != aswgMetadataKeys.end())
1057 destValues[tag] = entry->getAllSubText();
1064 static MemoryBlock createFrom (
const StringMap& values)
1066 auto createTextElement = [] (
const StringRef& key,
const StringRef& value)
1068 auto* elem =
new XmlElement (key);
1069 elem->addTextElement (value);
1073 std::unique_ptr<XmlElement> aswgElement;
1075 for (
const auto& pair : values)
1077 if (aswgMetadataKeys.find (pair.first) != aswgMetadataKeys.end())
1079 if (aswgElement ==
nullptr)
1080 aswgElement = std::make_unique<XmlElement> (
"ASWG");
1082 aswgElement->addChildElement (createTextElement (pair.first, pair.second));
1086 MemoryOutputStream outputStream;
1088 if (aswgElement !=
nullptr)
1090 XmlElement xml (
"BWFXML");
1093 xml.addChildElement (aswgElement.release());
1094 xml.writeTo (outputStream);
1095 outputStream.writeRepeatedByte (0, outputStream.getDataSize());
1098 return outputStream.getMemoryBlock();
1105 static void addToMetadata (StringMap& destValues,
const String& source)
1107 if (
auto xml = parseXML (source))
1109 if (xml->hasTagName (
"ebucore:ebuCoreMain"))
1111 if (
auto xml2 = xml->getChildByName (
"ebucore:coreMetadata"))
1113 if (
auto xml3 = xml2->getChildByName (
"ebucore:identifier"))
1115 if (
auto xml4 = xml3->getChildByName (
"dc:identifier"))
1117 auto ISRCCode = xml4->getAllSubText().fromFirstOccurrenceOf (
"ISRC:",
false,
true);
1119 if (ISRCCode.isNotEmpty())
1133 static MemoryBlock createFrom (
const StringMap& values)
1137 auto ISRC = getValueWithDefault (values,
1141 MemoryOutputStream xml;
1143 if (ISRC.isNotEmpty())
1151 jassert (ISRC.length() == 12);
1153 xml <<
"<ebucore:ebuCoreMain xmlns:dc=\" http://purl.org/dc/elements/1.1/\" "
1154 "xmlns:ebucore=\"urn:ebu:metadata-schema:ebuCore_2012\">"
1155 "<ebucore:coreMetadata>"
1156 "<ebucore:identifier typeLabel=\"GUID\" "
1157 "typeDefinition=\"Globally Unique Identifier\" "
1158 "formatLabel=\"ISRC\" "
1159 "formatDefinition=\"International Standard Recording Code\" "
1160 "formatLink=\"http://www.ebu.ch/metadata/cs/ebu_IdentifierTypeCodeCS.xml#3.7\">"
1161 "<dc:identifier>ISRC:" << ISRC <<
"</dc:identifier>"
1162 "</ebucore:identifier>"
1163 "</ebucore:coreMetadata>"
1164 "</ebucore:ebuCoreMain>";
1166 xml.writeRepeatedByte (0, xml.getDataSize());
1169 return xml.getMemoryBlock();
1174 struct ExtensibleWavSubFormat
1181 bool operator== (
const ExtensibleWavSubFormat& other)
const noexcept {
return memcmp (
this, &other,
sizeof (*
this)) == 0; }
1182 bool operator!= (
const ExtensibleWavSubFormat& other)
const noexcept {
return ! operator== (other); }
1186 static const ExtensibleWavSubFormat pcmFormat = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
1187 static const ExtensibleWavSubFormat IEEEFloatFormat = { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
1188 static const ExtensibleWavSubFormat ambisonicFormat = { 0x00000001, 0x0721, 0x11d3, { 0x86, 0x44, 0xC8, 0xC1, 0xCA, 0x00, 0x00, 0x00 } };
1190 struct DataSize64Chunk
1193 uint32 riffSizeHigh;
1195 uint32 dataSizeHigh;
1196 uint32 sampleCountLow;
1197 uint32 sampleCountHigh;
1207class WavAudioFormatReader final :
public AudioFormatReader
1212 using namespace WavFileHelpers;
1213 uint64 len = 0, end = 0;
1214 int cueNoteIndex = 0;
1215 int cueLabelIndex = 0;
1216 int cueRegionIndex = 0;
1223 if (firstChunkType == chunkName (
"RF64"))
1228 else if (firstChunkType == chunkName (
"RIFF"))
1251 end = len + (uint64) startOfRIFFChunk;
1262 if (chunkType == chunkName (
"fmt "))
1274 bytesPerFrame = bytesPerSec / (int)
sampleRate;
1288 else if (format == 0xfffe)
1298 dict[
"ChannelMask"] = String (channelMask);
1299 channelLayout = getChannelLayoutFromMask (channelMask,
numChannels);
1301 ExtensibleWavSubFormat subFormat;
1305 input->
read (subFormat.data4, sizeof (subFormat.data4));
1307 if (subFormat == IEEEFloatFormat)
1309 else if (subFormat != pcmFormat && subFormat != ambisonicFormat)
1313 else if (format == 0x674f
1318 || format == 0x6771)
1320 isSubformatOggVorbis =
true;
1325 else if (format != 1)
1330 else if (chunkType == chunkName (
"data"))
1339 dataLength = length;
1343 lengthInSamples = (bytesPerFrame > 0) ? (dataLength / bytesPerFrame) : 0;
1345 else if (chunkType == chunkName (
"bext"))
1350 HeapBlock<BWAVChunk> bwav;
1351 bwav.calloc (jmax ((
size_t) length + 1,
sizeof (BWAVChunk)), 1);
1353 bwav->copyTo (dict, (
int) length);
1355 else if (chunkType == chunkName (
"smpl"))
1357 HeapBlock<SMPLChunk> smpl;
1358 smpl.calloc (jmax ((
size_t) length + 1,
sizeof (SMPLChunk)), 1);
1360 smpl->copyTo (dict, (
int) length);
1362 else if (chunkType == chunkName (
"inst") || chunkType == chunkName (
"INST"))
1364 HeapBlock<InstChunk> inst;
1365 inst.calloc (jmax ((
size_t) length + 1,
sizeof (InstChunk)), 1);
1367 inst->copyTo (dict);
1369 else if (chunkType == chunkName (
"cue "))
1371 HeapBlock<CueChunk> cue;
1372 cue.calloc (jmax ((
size_t) length + 1,
sizeof (CueChunk)), 1);
1374 cue->copyTo (dict, (
int) length);
1376 else if (chunkType == chunkName (
"axml"))
1380 AXMLChunk::addToMetadata (dict, axml.toString());
1382 else if (chunkType == chunkName (
"iXML"))
1386 IXMLChunk::addToMetadata (dict, ixml.toString());
1388 else if (chunkType == chunkName (
"LIST"))
1392 if (subChunkType == chunkName (
"info") || subChunkType == chunkName (
"INFO"))
1394 ListInfoChunk::addToMetadata (dict, *
input, chunkEnd);
1396 else if (subChunkType == chunkName (
"adtl"))
1402 auto adtlChunkEnd =
input->
getPosition() + (adtlLength + (adtlLength & 1));
1404 if (adtlChunkType == chunkName (
"labl") || adtlChunkType == chunkName (
"note"))
1408 if (adtlChunkType == chunkName (
"labl"))
1409 prefix <<
"CueLabel" << cueLabelIndex++;
1410 else if (adtlChunkType == chunkName (
"note"))
1411 prefix <<
"CueNote" << cueNoteIndex++;
1414 auto stringLength = (int) adtlLength - 4;
1416 MemoryBlock textBlock;
1419 dict[prefix +
"Identifier"] = String (identifier);
1420 dict[prefix +
"Text"] = textBlock.toString();
1422 else if (adtlChunkType == chunkName (
"ltxt"))
1424 auto prefix =
"CueRegion" + String (cueRegionIndex++);
1432 auto stringLength = adtlLength - 20;
1434 MemoryBlock textBlock;
1437 dict[prefix +
"Identifier"] = String (identifier);
1438 dict[prefix +
"SampleLength"] = String (sampleLength);
1439 dict[prefix +
"Purpose"] = String (purpose);
1440 dict[prefix +
"Country"] = String (country);
1441 dict[prefix +
"Language"] = String (language);
1442 dict[prefix +
"Dialect"] = String (dialect);
1443 dict[prefix +
"CodePage"] = String (codePage);
1444 dict[prefix +
"Text"] = textBlock.toString();
1451 else if (chunkType == chunkName (
"acid"))
1453 AcidChunk (*
input, length).addToMetadata (dict);
1455 else if (chunkType == chunkName (
"Trkn"))
1457 MemoryBlock tracktion;
1461 else if (chunkEnd <= input->getPosition())
1470 if (cueLabelIndex > 0) dict[
"NumCueLabels"] = String (cueLabelIndex);
1471 if (cueNoteIndex > 0) dict[
"NumCueNotes"] = String (cueNoteIndex);
1472 if (cueRegionIndex > 0) dict[
"NumCueRegions"] = String (cueRegionIndex);
1473 if (dict.size() > 0) dict[
"MetaDataSource"] =
"WAV";
1479 bool readSamples (
int*
const* destSamples,
int numDestChannels,
int startOffsetInDestBuffer,
1480 int64 startSampleInFile,
int numSamples)
override
1485 if (numSamples <= 0)
1490 while (numSamples > 0)
1492 const int tempBufSize = 480 * 3 * 4;
1493 char tempBuffer[tempBufSize];
1495 auto numThisTime = jmin (tempBufSize / bytesPerFrame, numSamples);
1496 auto bytesRead =
input->
read (tempBuffer, numThisTime * bytesPerFrame);
1498 if (bytesRead < numThisTime * bytesPerFrame)
1500 jassert (bytesRead >= 0);
1501 zeromem (tempBuffer + bytesRead, (
size_t) (numThisTime * bytesPerFrame - bytesRead));
1505 destSamples, startOffsetInDestBuffer, numDestChannels,
1508 startOffsetInDestBuffer += numThisTime;
1509 numSamples -= numThisTime;
1515 static void copySampleData (
unsigned int numBitsPerSample,
const bool floatingPointData,
1516 int*
const* destSamples,
int startOffsetInDestBuffer,
int numDestChannels,
1517 const void* sourceData,
int numberOfChannels,
int numSamples)
noexcept
1519 switch (numBitsPerSample)
1521 case 8: ReadHelper<AudioData::Int32, AudioData::UInt8, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
break;
1522 case 16: ReadHelper<AudioData::Int32, AudioData::Int16, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
break;
1523 case 24: ReadHelper<AudioData::Int32, AudioData::Int24, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
break;
1524 case 32:
if (floatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
1525 else ReadHelper<AudioData::Int32, AudioData::Int32, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
1527 default: jassertfalse;
break;
1532 AudioChannelSet getChannelLayout()
override
1535 return channelLayout;
1537 return WavFileHelpers::canonicalWavChannelSet (
static_cast<int> (
numChannels));
1540 static AudioChannelSet getChannelLayoutFromMask (
int dwChannelMask,
size_t totalNumChannels)
1542 AudioChannelSet wavFileChannelLayout;
1545 BigInteger channelBits (dwChannelMask);
1547 for (
auto bit = channelBits.findNextSetBit (0); bit >= 0; bit = channelBits.findNextSetBit (bit + 1))
1551 if (wavFileChannelLayout.size() !=
static_cast<int> (totalNumChannels))
1555 if (totalNumChannels <= 2 && dwChannelMask == 0)
1561 while (wavFileChannelLayout.size() <
static_cast<int> (totalNumChannels))
1566 return wavFileChannelLayout;
1569 int64 bwavChunkStart = 0, bwavSize = 0;
1570 int64 dataChunkStart = 0, dataLength = 0;
1571 int bytesPerFrame = 0;
1572 bool isRF64 =
false;
1573 bool isSubformatOggVorbis =
false;
1575 AudioChannelSet channelLayout;
1578 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatReader)
1582class WavAudioFormatWriter final :
public AudioFormatWriter
1585 WavAudioFormatWriter (OutputStream*
const out,
const double rate,
1586 const AudioChannelSet& channelLayoutToUse,
const unsigned int bits,
1587 const StringPairArray& metadataValues)
1590 using namespace WavFileHelpers;
1592 if (metadataValues.size() > 0)
1597 jassert (metadataValues.getValue (
"MetaDataSource",
"None") !=
"AIFF");
1599 const auto map = toMap (metadataValues);
1601 bwavChunk = BWAVChunk::createFrom (map);
1602 ixmlChunk = IXMLChunk::createFrom (map);
1603 axmlChunk = AXMLChunk::createFrom (map);
1604 smplChunk = SMPLChunk::createFrom (map);
1605 instChunk = InstChunk::createFrom (map);
1606 cueChunk = CueChunk ::createFrom (map);
1607 listChunk = ListChunk::createFrom (map);
1608 listInfoChunk = ListInfoChunk::createFrom (map);
1609 acidChunk = AcidChunk::createFrom (map);
1610 trckChunk = TracktionChunk::createFrom (map);
1613 headerPosition = out->getPosition();
1617 ~WavAudioFormatWriter()
override
1623 bool write (
const int** data,
int numSamples)
override
1625 jassert (numSamples >= 0);
1626 jassert (data !=
nullptr && *data !=
nullptr);
1636 case 8: WriteHelper<AudioData::UInt8, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.
getData(), (
int)
numChannels, data, numSamples);
break;
1637 case 16: WriteHelper<AudioData::Int16, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.
getData(), (
int)
numChannels, data, numSamples);
break;
1638 case 24: WriteHelper<AudioData::Int24, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.
getData(), (
int)
numChannels, data, numSamples);
break;
1639 case 32: WriteHelper<AudioData::Int32, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.
getData(), (
int)
numChannels, data, numSamples);
break;
1640 default: jassertfalse;
break;
1653 bytesWritten += bytes;
1654 lengthInSamples += (uint64) numSamples;
1658 bool flush()
override
1673 MemoryBlock tempBlock, bwavChunk, ixmlChunk, axmlChunk, smplChunk, instChunk, cueChunk, listChunk, listInfoChunk, acidChunk, trckChunk;
1674 uint64 lengthInSamples = 0, bytesWritten = 0;
1675 int64 headerPosition = 0;
1676 bool writeFailed =
false;
1680 if ((bytesWritten & 1) != 0)
1683 using namespace WavFileHelpers;
1694 uint64 audioDataSize = bytesPerFrame * lengthInSamples;
1695 auto channelMask = getChannelMaskFromChannelLayout (
channelLayout);
1697 const bool isRF64 = (bytesWritten >= 0x100000000LL);
1698 const bool isWaveFmtEx = isRF64 || (channelMask != 0);
1700 int64 riffChunkSize = (int64) (4 + 8 + 40
1701 + 8 + audioDataSize + (audioDataSize & 1)
1702 + chunkSize (bwavChunk)
1703 + chunkSize (ixmlChunk)
1704 + chunkSize (axmlChunk)
1705 + chunkSize (smplChunk)
1706 + chunkSize (instChunk)
1707 + chunkSize (cueChunk)
1708 + chunkSize (listChunk)
1709 + chunkSize (listInfoChunk)
1710 + chunkSize (acidChunk)
1711 + chunkSize (trckChunk)
1714 riffChunkSize += (riffChunkSize & 1);
1717 writeChunkHeader (chunkName (
"RF64"), -1);
1719 writeChunkHeader (chunkName (
"RIFF"), (
int) riffChunkSize);
1725 #if ! JUCE_WAV_DO_NOT_PAD_HEADER_SIZE
1736 writeChunkHeader (chunkName (
"JUNK"), 28 + (isWaveFmtEx? 0 : 24));
1742 #if JUCE_WAV_DO_NOT_PAD_HEADER_SIZE
1747 writeChunkHeader (chunkName (
"ds64"), 28);
1755 writeChunkHeader (chunkName (
"fmt "), 40);
1760 writeChunkHeader (chunkName (
"fmt "), 16);
1777 const ExtensibleWavSubFormat& subFormat =
bitsPerSample < 32 ? pcmFormat : IEEEFloatFormat;
1782 output->
write (subFormat.data4, sizeof (subFormat.data4));
1785 writeChunk (bwavChunk, chunkName (
"bext"));
1786 writeChunk (ixmlChunk, chunkName (
"iXML"));
1787 writeChunk (axmlChunk, chunkName (
"axml"));
1788 writeChunk (smplChunk, chunkName (
"smpl"));
1789 writeChunk (instChunk, chunkName (
"inst"), 7);
1790 writeChunk (cueChunk, chunkName (
"cue "));
1791 writeChunk (listChunk, chunkName (
"LIST"));
1792 writeChunk (listInfoChunk, chunkName (
"LIST"));
1793 writeChunk (acidChunk, chunkName (
"acid"));
1794 writeChunk (trckChunk, chunkName (
"Trkn"));
1796 writeChunkHeader (chunkName (
"data"), isRF64 ? -1 : (
int) (lengthInSamples * bytesPerFrame));
1801 static size_t chunkSize (
const MemoryBlock& data)
noexcept {
return data.isEmpty() ? 0 : (8 + data.getSize()); }
1803 void writeChunkHeader (
int chunkType,
int size)
const
1809 void writeChunk (
const MemoryBlock& data,
int chunkType,
int size = 0)
const
1811 if (! data.isEmpty())
1813 writeChunkHeader (chunkType, size != 0 ? size : (
int) data.getSize());
1818 static int getChannelMaskFromChannelLayout (
const AudioChannelSet& layout)
1820 if (layout.isDiscreteLayout())
1828 auto channels = layout.getChannelTypes();
1829 auto wavChannelMask = 0;
1831 for (
auto channel : channels)
1833 int wavChannelBit =
static_cast<int> (channel) - 1;
1834 jassert (wavChannelBit >= 0 && wavChannelBit <= 31);
1836 wavChannelMask |= (1 << wavChannelBit);
1839 return wavChannelMask;
1842 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatWriter)
1846class MemoryMappedWavReader final :
public MemoryMappedAudioFormatReader
1849 MemoryMappedWavReader (
const File& wavFile,
const WavAudioFormatReader& reader)
1851 reader.dataLength, reader.bytesPerFrame)
1855 bool readSamples (
int*
const* destSamples,
int numDestChannels,
int startOffsetInDestBuffer,
1856 int64 startSampleInFile,
int numSamples)
override
1861 if (numSamples <= 0)
1864 if (map ==
nullptr || ! mappedSection.
contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
1871 destSamples, startOffsetInDestBuffer, numDestChannels,
1876 void getSample (int64 sample,
float* result)
const noexcept override
1880 if (map ==
nullptr || ! mappedSection.
contains (sample))
1884 zeromem (result, (
size_t) num *
sizeof (
float));
1888 auto dest = &result;
1893 case 8: ReadHelper<AudioData::Float32, AudioData::UInt8, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
break;
1894 case 16: ReadHelper<AudioData::Float32, AudioData::Int16, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
break;
1895 case 24: ReadHelper<AudioData::Float32, AudioData::Int24, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
break;
1896 case 32:
if (
usesFloatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
1897 else ReadHelper<AudioData::Float32, AudioData::Int32, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
1899 default: jassertfalse;
break;
1903 void readMaxLevels (int64 startSampleInFile, int64 numSamples, Range<float>* results,
int numChannelsToRead)
override
1907 if (map ==
nullptr || numSamples <= 0 || ! mappedSection.
contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
1909 jassert (numSamples <= 0);
1911 for (
int i = 0; i < numChannelsToRead; ++i)
1919 case 8: scanMinAndMax<AudioData::UInt8> (startSampleInFile, numSamples, results, numChannelsToRead);
break;
1920 case 16: scanMinAndMax<AudioData::Int16> (startSampleInFile, numSamples, results, numChannelsToRead);
break;
1921 case 24: scanMinAndMax<AudioData::Int24> (startSampleInFile, numSamples, results, numChannelsToRead);
break;
1922 case 32:
if (
usesFloatingPointData) scanMinAndMax<AudioData::Float32> (startSampleInFile, numSamples, results, numChannelsToRead);
1923 else scanMinAndMax<AudioData::Int32> (startSampleInFile, numSamples, results, numChannelsToRead);
1925 default: jassertfalse;
break;
1932 template <
typename SampleType>
1933 void scanMinAndMax (int64 startSampleInFile, int64 numSamples, Range<float>* results,
int numChannelsToRead)
const noexcept
1935 for (
int i = 0; i < numChannelsToRead; ++i)
1939 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedWavReader)
1948 return { 8000, 11025, 12000, 16000, 22050, 32000, 44100,
1949 48000, 88200, 96000, 176400, 192000, 352800, 384000 };
1954 return { 8, 16, 24, 32 };
1969 for (
auto channel : channelTypes)
1978 std::unique_ptr<WavAudioFormatReader> r (
new WavAudioFormatReader (sourceStream));
1980 #if JUCE_USE_OGGVORBIS
1981 if (r->isSubformatOggVorbis)
1984 return OggVorbisAudioFormat().createReaderFor (sourceStream, deleteStreamIfOpeningFails);
1988 if (r->sampleRate > 0 && r->numChannels > 0 && r->bytesPerFrame > 0 && r->bitsPerSample <= 32)
1991 if (! deleteStreamIfOpeningFails)
2006 WavAudioFormatReader reader (fin);
2008 if (reader.lengthInSamples > 0)
2009 return new MemoryMappedWavReader (fin->
getFile(), reader);
2016 unsigned int numChannels,
int bitsPerSample,
2019 return createWriterFor (out, sampleRate, WavFileHelpers::canonicalWavChannelSet (
static_cast<int> (numChannels)),
2020 bitsPerSample, metadataValues, qualityOptionIndex);
2031 return new WavAudioFormatWriter (out, sampleRate, channelLayout,
2032 (
unsigned int) bitsPerSample, metadataValues);
2037namespace WavFileHelpers
2039 static bool slowCopyWavFileWithNewMetadata (
const File& file,
const StringPairArray& metadata)
2046 if (reader !=
nullptr)
2048 std::unique_ptr<OutputStream> outStream (tempFile.getFile().createOutputStream());
2050 if (outStream !=
nullptr)
2052 std::unique_ptr<AudioFormatWriter> writer (wav.
createWriterFor (outStream.get(), reader->sampleRate,
2053 reader->numChannels, (
int) reader->bitsPerSample,
2056 if (writer !=
nullptr)
2058 outStream.release();
2060 bool ok = writer->writeFromAudioReader (*reader, 0, -1);
2064 return ok && tempFile.overwriteTargetFileWithTemporary();
2075 using namespace WavFileHelpers;
2079 if (reader !=
nullptr)
2081 auto bwavPos = reader->bwavChunkStart;
2082 auto bwavSize = reader->bwavSize;
2087 auto chunk = BWAVChunk::createFrom (toMap (newMetadata));
2089 if (chunk.getSize() <= (
size_t) bwavSize)
2092 auto oldSize = wavFile.
getSize();
2105 jassert (wavFile.
getSize() == oldSize);
2111 return slowCopyWavFileWithNewMetadata (wavFile, newMetadata);
2119struct WaveAudioFormatTests final :
public UnitTest
2121 WaveAudioFormatTests()
2122 :
UnitTest (
"Wave audio format tests", UnitTestCategories::audio)
2125 void runTest()
override
2127 beginTest (
"Setting up metadata");
2129 auto metadataValues = toMap (WavAudioFormat::createBWAVMetadata (
"description",
2132 Time::getCurrentTime(),
2133 numTestAudioBufferSamples,
2136 for (
int i = numElementsInArray (WavFileHelpers::ListInfoChunk::types); --i >= 0;)
2137 metadataValues[WavFileHelpers::ListInfoChunk::types[i]] = WavFileHelpers::ListInfoChunk::types[i];
2139 metadataValues[WavAudioFormat::internationalStandardRecordingCode] = WavAudioFormat::internationalStandardRecordingCode;
2141 if (metadataValues.size() > 0)
2142 metadataValues[
"MetaDataSource"] =
"WAV";
2144 const auto smplMetadata = createDefaultSMPLMetadata();
2145 metadataValues.insert (smplMetadata.cbegin(), smplMetadata.cend());
2147 WavAudioFormat format;
2148 MemoryBlock memoryBlock;
2150 StringPairArray metadataArray;
2151 metadataArray.addUnorderedMap (metadataValues);
2154 beginTest (
"Metadata can be written and read");
2156 const auto newMetadata = getMetadataAfterReading (format, writeToBlock (format, metadataArray));
2157 expect (newMetadata == metadataArray,
"Somehow, the metadata is different!");
2161 beginTest (
"Files containing a riff info source and an empty ISRC associate the source with the riffInfoSource key");
2162 StringPairArray meta;
2163 meta.addMap ({ { WavAudioFormat::riffInfoSource,
"customsource" },
2164 { WavAudioFormat::internationalStandardRecordingCode,
"" } });
2165 const auto mb = writeToBlock (format, meta);
2166 checkPatternsPresent (mb, {
"INFOISRC" });
2167 checkPatternsNotPresent (mb, {
"ISRC:",
"<ebucore" });
2168 const auto a = getMetadataAfterReading (format, mb);
2169 expect (a[WavAudioFormat::riffInfoSource] ==
"customsource");
2170 expect (a[WavAudioFormat::internationalStandardRecordingCode] ==
"");
2174 beginTest (
"Files containing a riff info source and no ISRC associate the source with both keys "
2175 "for backwards compatibility");
2176 StringPairArray meta;
2177 meta.addMap ({ { WavAudioFormat::riffInfoSource,
"customsource" } });
2178 const auto mb = writeToBlock (format, meta);
2179 checkPatternsPresent (mb, {
"INFOISRC",
"ISRC:customsource",
"<ebucore" });
2180 const auto a = getMetadataAfterReading (format, mb);
2181 expect (a[WavAudioFormat::riffInfoSource] ==
"customsource");
2182 expect (a[WavAudioFormat::internationalStandardRecordingCode] ==
"customsource");
2186 beginTest (
"Files containing an ISRC associate the value with the internationalStandardRecordingCode key "
2187 "and the riffInfoSource key for backwards compatibility");
2188 StringPairArray meta;
2189 meta.addMap ({ { WavAudioFormat::internationalStandardRecordingCode,
"AABBBCCDDDDD" } });
2190 const auto mb = writeToBlock (format, meta);
2191 checkPatternsPresent (mb, {
"ISRC:AABBBCCDDDDD",
"<ebucore" });
2192 checkPatternsNotPresent (mb, {
"INFOISRC" });
2193 const auto a = getMetadataAfterReading (format, mb);
2194 expect (a[WavAudioFormat::riffInfoSource] ==
"AABBBCCDDDDD");
2195 expect (a[WavAudioFormat::internationalStandardRecordingCode] ==
"AABBBCCDDDDD");
2199 beginTest (
"Files containing an ISRC and a riff info source associate the values with the appropriate keys");
2200 StringPairArray meta;
2201 meta.addMap ({ { WavAudioFormat::riffInfoSource,
"source" } });
2202 meta.addMap ({ { WavAudioFormat::internationalStandardRecordingCode,
"UUVVVXXYYYYY" } });
2203 const auto mb = writeToBlock (format, meta);
2204 checkPatternsPresent (mb, {
"INFOISRC",
"ISRC:UUVVVXXYYYYY",
"<ebucore" });
2205 const auto a = getMetadataAfterReading (format, mb);
2206 expect (a[WavAudioFormat::riffInfoSource] ==
"source");
2207 expect (a[WavAudioFormat::internationalStandardRecordingCode] ==
"UUVVVXXYYYYY");
2211 beginTest (
"Files containing ASWG metadata read and write correctly");
2213 StringPairArray meta;
2215 for (
const auto& key : WavFileHelpers::IXMLChunk::aswgMetadataKeys)
2216 meta.set (key,
"Test123&<>");
2219 auto writer = rawToUniquePtr (WavAudioFormat().createWriterFor (
new MemoryOutputStream (block,
false), 48000, 1, 32, meta, 0));
2220 expect (writer !=
nullptr);
2225 auto input = std::make_unique<MemoryInputStream> (block,
false);
2229 char chunkType[4] {};
2232 input->
read (chunkType, 4);
2234 if (memcmp (chunkType,
"iXML", 4) == 0)
2236 auto length = (uint32) input->
readInt();
2238 MemoryBlock xmlBlock;
2241 return parseXML (xmlBlock.toString()) !=
nullptr;
2251 auto reader = rawToUniquePtr (WavAudioFormat().createReaderFor (
new MemoryInputStream (block,
false),
true));
2252 expect (reader !=
nullptr);
2254 for (
const auto& key : meta.getAllKeys())
2256 const auto oldValue = meta.getValue (key,
"!");
2257 const auto newValue = reader->metadataValues.getValue (key,
"");
2258 expectEquals (oldValue, newValue);
2261 expect (reader->metadataValues.getValue (WavAudioFormat::aswgVersion,
"") ==
"3.01");
2267 MemoryBlock writeToBlock (WavAudioFormat& format, StringPairArray meta)
2274 auto writer = rawToUniquePtr (format.createWriterFor (
new MemoryOutputStream (mb,
false),
2276 numTestAudioBufferChannels,
2280 expect (writer !=
nullptr);
2281 AudioBuffer<float> buffer (numTestAudioBufferChannels, numTestAudioBufferSamples);
2282 expect (writer->writeFromAudioSampleBuffer (buffer, 0, numTestAudioBufferSamples));
2288 StringPairArray getMetadataAfterReading (WavAudioFormat& format,
const MemoryBlock& mb)
2290 auto reader = rawToUniquePtr (format.createReaderFor (
new MemoryInputStream (mb,
false),
true));
2291 expect (reader !=
nullptr);
2292 return reader->metadataValues;
2295 template <
typename Fn>
2296 void checkPatterns (
const MemoryBlock& mb,
const std::vector<std::string>& patterns, Fn&& fn)
2298 for (
const auto& pattern : patterns)
2300 const auto begin =
static_cast<const char*
> (mb.getData());
2301 const auto end = begin + mb.getSize();
2302 expect (fn (std::search (begin, end, pattern.begin(), pattern.end()), end));
2306 void checkPatternsPresent (
const MemoryBlock& mb,
const std::vector<std::string>& patterns)
2308 checkPatterns (mb, patterns, std::not_equal_to<>{});
2311 void checkPatternsNotPresent (
const MemoryBlock& mb,
const std::vector<std::string>& patterns)
2313 checkPatterns (mb, patterns, std::equal_to<>{});
2318 numTestAudioBufferChannels = 2,
2319 numTestAudioBufferSamples = 256
2322 static StringMap createDefaultSMPLMetadata()
2326 m[
"Manufacturer"] =
"0";
2328 m[
"SamplePeriod"] =
"0";
2329 m[
"MidiUnityNote"] =
"60";
2330 m[
"MidiPitchFraction"] =
"0";
2331 m[
"SmpteFormat"] =
"0";
2332 m[
"SmpteOffset"] =
"0";
2333 m[
"NumSampleLoops"] =
"0";
2334 m[
"SamplerData"] =
"0";
2339 JUCE_DECLARE_NON_COPYABLE (WaveAudioFormatTests)
2342static const WaveAudioFormatTests waveAudioFormatTests;
static AudioChannelSet JUCE_CALLTYPE quadraphonic()
static AudioChannelSet JUCE_CALLTYPE create5point0()
int size() const noexcept
bool isDiscreteLayout() const noexcept
static AudioChannelSet JUCE_CALLTYPE mono()
static AudioChannelSet JUCE_CALLTYPE stereo()
static AudioChannelSet JUCE_CALLTYPE create5point1()
static AudioChannelSet JUCE_CALLTYPE create7point0SDDS()
static AudioChannelSet JUCE_CALLTYPE create7point1SDDS()
static AudioChannelSet JUCE_CALLTYPE canonicalChannelSet(int numChannels)
static AudioChannelSet JUCE_CALLTYPE discreteChannels(int numChannels)
static AudioChannelSet JUCE_CALLTYPE createLCR()
Array< ChannelType > getChannelTypes() const
static constexpr uint32 littleEndianInt(const void *bytes) noexcept
static constexpr uint16 swap(uint16 value) noexcept
static Type swapIfBigEndian(Type value) noexcept
static juce_wchar toUpperCase(juce_wchar character) noexcept
bool setPosition(int64) override
bool openedOk() const noexcept
std::unique_ptr< FileInputStream > createInputStream() const
void * getData() noexcept
size_t getSize() const noexcept
void ensureSize(size_t minimumSize, bool initialiseNewSpaceToZero=false)
size_t getDataSize() const noexcept
bool write(const void *, size_t) override
MemoryBlock getMemoryBlock() const
virtual bool write(const void *dataToWrite, size_t numberOfBytes)=0
virtual bool writeRepeatedByte(uint8 byte, size_t numTimesToRepeat)
virtual int64 getPosition()=0
virtual bool writeByte(char byte)
virtual bool writeShort(short value)
virtual bool writeInt64(int64 value)
virtual bool setPosition(int64 newPosition)=0
virtual bool writeInt(int value)
constexpr bool contains(const ValueType position) const noexcept
void set(const String &key, const String &value)
void addUnorderedMap(const std::unordered_map< String, String > &mapToAdd)
static String createStringFromData(const void *data, int size)
static String fromUTF8(const char *utf8buffer, int bufferSizeBytes=-1)
String formatted(const String &format) const