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;
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
375 template <
typename NameType>
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);
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();
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];
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)
778 auto infoType = input.
readInt();
788 for (
auto& type : types)
790 if (isMatchingTypeIgnoringCase (infoType, type))
805 auto value = getValueWithDefault (values, paramName, {});
810 auto valueLength = (
int) value.getNumBytesAsUTF8() + 1;
811 auto chunkLength = valueLength + (valueLength & 1);
813 out.
writeInt (chunkName (paramName));
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));
866 if (iter != values.
cend())
867 tempo = swapFloatByteOrder (iter->second.getFloatValue());
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;
1041 static void addToMetadata (
StringMap& destValues,
const String& 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);
1075 for (
const auto& pair : values)
1077 if (aswgMetadataKeys.find (pair.first) != aswgMetadataKeys.end())
1079 if (aswgElement ==
nullptr)
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)
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();
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 } };
1212 using namespace WavFileHelpers;
1214 int cueNoteIndex = 0;
1215 int cueLabelIndex = 0;
1216 int cueRegionIndex = 0;
1223 if (firstChunkType == chunkName (
"RF64"))
1228 else if (firstChunkType == chunkName (
"RIFF"))
1262 if (chunkType == chunkName (
"fmt "))
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"))
1351 bwav.
calloc (
jmax ((
size_t) length + 1,
sizeof (BWAVChunk)), 1);
1353 bwav->copyTo (dict, (
int) length);
1355 else if (chunkType == chunkName (
"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"))
1365 inst.
calloc (
jmax ((
size_t) length + 1,
sizeof (InstChunk)), 1);
1367 inst->copyTo (dict);
1369 else if (chunkType == chunkName (
"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;
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;
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"))
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)
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);
1535 return channelLayout;
1537 return WavFileHelpers::canonicalWavChannelSet (
static_cast<int> (
numChannels));
1540 static AudioChannelSet getChannelLayoutFromMask (
int dwChannelMask,
size_t totalNumChannels)
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;
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);
1623 bool write (
const int** data,
int numSamples)
override
1626 jassert (data !=
nullptr && *data !=
nullptr);
1653 bytesWritten += bytes;
1654 lengthInSamples += (
uint64) numSamples;
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);
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;
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,
1880 if (map ==
nullptr || ! mappedSection.
contains (sample))
1884 zeromem (result, (
size_t) num *
sizeof (
float));
1888 auto dest = &result;
1907 if (map ==
nullptr || numSamples <= 0 || ! mappedSection.
contains (
Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
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);
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)
1936 results[i] = scanMinAndMaxInterleaved<SampleType, AudioData::LittleEndian> (i, startSampleInFile, numSamples);
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)
1980 #if JUCE_USE_OGGVORBIS
1981 if (r->isSubformatOggVorbis)
1988 if (r->sampleRate > 0 && r->numChannels > 0 && r->bytesPerFrame > 0 && r->bitsPerSample <= 32)
1991 if (! deleteStreamIfOpeningFails)
2016 unsigned int numChannels,
int bitsPerSample,
2019 return createWriterFor (out, sampleRate, WavFileHelpers::canonicalWavChannelSet (
static_cast<int> (numChannels)),
2020 bitsPerSample, metadataValues, qualityOptionIndex);
2032 (
unsigned int) bitsPerSample, metadataValues);
2037namespace WavFileHelpers
2039 static bool slowCopyWavFileWithNewMetadata (
const File& file,
const StringPairArray& metadata)
2046 if (reader !=
nullptr)
2050 if (outStream !=
nullptr)
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();
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);
2227 while (! input->isExhausted())
2229 char chunkType[4] {};
2230 auto pos = input->getPosition();
2232 input->read (chunkType, 4);
2234 if (memcmp (chunkType,
"iXML", 4) == 0)
2236 auto length = (uint32) input->readInt();
2238 MemoryBlock xmlBlock;
2239 input->readIntoMemoryBlock (xmlBlock, (ssize_t) length);
2241 return parseXML (xmlBlock.toString()) !=
nullptr;
2244 input->setPosition (pos + 1);
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,
"!");
2258 expectEquals (oldValue, newValue);
2267 MemoryBlock writeToBlock (WavAudioFormat& format, StringPairArray meta)
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);
2295 template <
typename 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));
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";
2342static const WaveAudioFormatTests waveAudioFormatTests;
Holds a resizable array of primitive or copy-by-value objects.
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.
void addChannel(ChannelType newChannelType)
Adds a channel to the set.
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.
An arbitrarily large integer class.
static constexpr uint32 littleEndianInt(const void *bytes) noexcept
Turns 4 bytes into a little-endian integer.
static 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_wchar toUpperCase(juce_wchar character) noexcept
Converts a character to upper-case.
An output stream that writes into a local file.
bool setPosition(int64) override
Tries to move the stream's output position.
bool openedOk() const noexcept
Returns true if the stream opened without problems.
Represents a local file or directory.
int64 getSize() const
Returns the size of the file in bytes.
std::unique_ptr< FileInputStream > createInputStream() const
Creates a stream to read from this file.
Very simple container class to hold a pointer to some data on the heap.
void calloc(SizeType newNumElements, const size_t elementSize=sizeof(ElementType))
Allocates a specified amount of memory and clears it.
A class to hold a resizable block of raw data.
void * getData() noexcept
Returns a void pointer to the data.
String toString() const
Attempts to parse the contents of the block as a zero-terminated UTF8 string.
size_t getSize() const noexcept
Returns the block's current allocated size, in bytes.
void ensureSize(size_t minimumSize, bool initialiseNewSpaceToZero=false)
Increases the block's size only if it's smaller than a given size.
bool readSamples(int *const *destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) override
Subclasses must implement this method to perform the low-level read operation.
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.
Writes data to an internal memory buffer, which grows as required.
size_t getDataSize() const noexcept
Returns the number of bytes of data that have been written to the stream.
bool write(const void *, size_t) override
Writes a block of data to the stream.
MemoryBlock getMemoryBlock() const
Returns a copy of the stream's data as a memory block.
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.
virtual bool writeString(const String &text)
Stores a string in the stream in a binary format.
A general-purpose range object, that simply represents any linear range with a start and end point.
constexpr bool contains(const ValueType position) const noexcept
Returns true if the given position lies inside this range.
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.
void set(const String &key, const String &value)
Adds or amends a key/value pair.
void addUnorderedMap(const std::unordered_map< String, String > &mapToAdd)
Adds the contents of an unordered map to this StringPairArray.
int size() const noexcept
Returns the number of strings in the array.
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.
Manages a temporary file, which will be deleted when this object is deleted.
Holds an absolute date and time.
String formatted(const String &format) const
Converts this date/time to a string with a user-defined format.
This is a base class for classes that perform a unit test.
void zerostruct(Type &structure) noexcept
Overwrites a structure or object with zeros.
unsigned short uint16
A platform-independent 16-bit unsigned integer type.
wchar_t juce_wchar
A platform-independent 32-bit unicode character type.
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
constexpr Type jmax(Type a, Type b)
Returns the larger of two values.
RangedDirectoryIterator end(const RangedDirectoryIterator &)
Returns a default-constructed sentinel value.
std::unique_ptr< XmlElement > parseXML(const String &textToParse)
Attempts to parse some XML text, returning a new XmlElement if it was valid.
signed char int8
A platform-independent 8-bit signed integer type.
unsigned long long uint64
A platform-independent 64-bit unsigned integer type.
unsigned int uint32
A platform-independent 32-bit unsigned integer type.
unsigned char uint8
A platform-independent 8-bit unsigned integer type.
long long int64
A platform-independent 64-bit integer type.
std::unique_ptr< T > rawToUniquePtr(T *ptr)
Converts an owning raw pointer into a unique_ptr, deriving the type of the unique_ptr automatically.
void zeromem(void *memory, size_t numBytes) noexcept
Fills a block of memory with zeros.
AcidChunk(InputStream &input, size_t length)
Reads an acid RIFF chunk from a stream positioned just after the size byte.