30 static const char*
const wavFormatName =
"WAV file";
43 const String& originatorRef,
45 int64 timeReferenceSamples,
46 const String& codingHistory)
158 namespace WavFileHelpers
161 JUCE_CONSTEXPR
inline size_t roundUpSize (
size_t sz) noexcept {
return (sz + 3) & ~3u; }
164 #pragma pack (push, 1) 169 char description[256];
171 char originatorRef[32];
172 char originationDate[10];
173 char originationTime[8];
179 char codingHistory[1];
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)));
191 auto time = (((int64) timeHigh) << 32) + timeLow;
193 values.
set (WavAudioFormat::bwavTimeReference,
String (time));
194 values.
set (WavAudioFormat::bwavCodingHistory,
195 String::fromUTF8 (codingHistory, totalSize - (
int) offsetof (BWAVChunk, codingHistory)));
200 MemoryBlock data (roundUpSize (
sizeof (BWAVChunk) + values[WavAudioFormat::bwavCodingHistory].getNumBytesAsUTF8()));
203 auto* b = (BWAVChunk*) data.
getData();
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
265 uint32 midiUnityNote;
266 uint32 midiPitchFraction;
269 uint32 numSampleLoops;
273 template <
typename NameType>
274 static void setValue (
StringPairArray& values, NameType name, uint32 val)
279 static void setValue (
StringPairArray& values,
int prefix,
const char* name, uint32 val)
281 setValue (values,
"Loop" +
String (prefix) + name, val);
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);
296 for (
int i = 0; i < (int) numSampleLoops; ++i)
298 if ((uint8*) (loops + (i + 1)) > ((uint8*)
this) + totalSize)
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);
310 template <
typename NameType>
311 static uint32 getValue (
const StringPairArray& values, NameType name,
const char* def)
316 static uint32 getValue (
const StringPairArray& values,
int prefix,
const char* name,
const char* def)
318 return getValue (values,
"Loop" +
String (prefix) + name, def);
326 data.
setSize (roundUpSize (
sizeof (SMPLChunk) + (
size_t) (jmax (0, numLoops - 1)) *
sizeof (SampleLoop)),
true);
328 auto s =
static_cast<SMPLChunk*
> (data.
getData());
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");
338 s->samplerData = getValue (values,
"SamplerData",
"0");
340 for (
int i = 0; i < numLoops; ++i)
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");
366 static void setValue (
StringPairArray& values,
const char* name,
int val)
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);
382 static int8 getValue (
const StringPairArray& values,
const char* name,
const char* def)
392 if (keys.contains (
"LowNote",
true) && keys.contains (
"HighNote",
true))
395 auto* inst =
static_cast<InstChunk*
> (data.
getData());
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");
426 static void setValue (
StringPairArray& values,
int prefix,
const char* name, uint32 val)
435 for (
int i = 0; i < (int) numCues; ++i)
437 if ((uint8*) (cues + (i + 1)) > ((uint8*)
this) + totalSize)
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);
456 data.
setSize (roundUpSize (
sizeof (CueChunk) + (
size_t) (numCues - 1) *
sizeof (Cue)),
true);
458 auto c =
static_cast<CueChunk*
> (data.
getData());
462 const String dataChunkID (chunkName (
"data"));
469 for (
int i = 0; i < numCues; ++i)
471 auto prefix =
"Cue" +
String (i);
475 jassert (! identifiers.
contains (identifier));
476 identifiers.
add (identifier);
480 nextOrder = jmax (nextOrder, order) + 1;
482 auto& cue = c->cues[i];
507 return getValue (values, prefix + name);
513 auto label = values.
getValue (prefix +
"Text", prefix);
515 auto chunkLength = 4 + labelLength + (labelLength & 1);
519 out.
writeInt (getValue (values, prefix,
"Identifier"));
520 out.
write (label.toUTF8(), (size_t) labelLength);
528 auto text = values.
getValue (prefix +
"Text", prefix);
531 auto chunkLength = textLength + 20 + (textLength & 1);
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);
550 auto numCueLabels = getValue (values,
"NumCueLabels");
551 auto numCueNotes = getValue (values,
"NumCueNotes");
552 auto numCueRegions = getValue (values,
"NumCueRegions");
556 if (numCueLabels + numCueNotes + numCueRegions > 0)
560 for (
int i = 0; i < numCueLabels; ++i)
561 appendLabelOrNoteChunk (values,
"CueLabel" +
String (i), chunkName (
"labl"), out);
563 for (
int i = 0; i < numCueNotes; ++i)
564 appendLabelOrNoteChunk (values,
"CueNote" +
String (i), chunkName (
"note"), out);
566 for (
int i = 0; i < numCueRegions; ++i)
567 appendExtraChunk (values,
"CueRegion" +
String (i), out);
576 namespace ListInfoChunk
578 static const char*
const types[] =
660 WavAudioFormat::riffInfoYear
663 static bool isMatchingTypeIgnoringCase (
const int value,
const char*
const name) noexcept
665 for (
int i = 0; i < 4; ++i)
676 auto infoType = input.
readInt();
681 infoLength = jmin (infoLength, (int64) input.
readInt());
686 for (
auto& type : types)
688 if (isMatchingTypeIgnoringCase (infoType, type))
703 auto value = values.
getValue (paramName, {});
709 auto chunkLength = valueLength + (valueLength & 1);
711 out.
writeInt (chunkName (paramName));
713 out.
write (value.toUTF8(), (size_t) valueLength);
725 bool anyParamsDefined =
false;
727 for (
auto& type : types)
728 if (writeValue (values, out, type))
729 anyParamsDefined =
true;
742 input.
read (
this, (
int) jmin (
sizeof (*
this), length));
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);
755 if (values[WavAudioFormat::acidRootSet].getIntValue() != 0)
762 if (values.
containsKey (WavAudioFormat::acidTempo))
763 tempo = swapFloatByteOrder (values[WavAudioFormat::acidTempo].getFloatValue());
768 return AcidChunk (values).toMemoryBlock();
773 return (flags != 0 || rootNote != 0 || numBeats != 0 || meterDenominator != 0 || meterNumerator != 0)
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);
791 values.
set (WavAudioFormat::acidTempo,
String (swapFloatByteOrder (tempo)));
794 void setBoolFlag (
StringPairArray& values,
const char* name, uint32 mask)
const 799 static uint32 getFlagIfPresent (
const StringPairArray& values,
const char* name, uint32 flag)
804 static float swapFloatByteOrder (
const float x) noexcept
806 #ifdef JUCE_BIG_ENDIAN 807 union { uint32 asInt;
float asFloat; } n;
821 uint16 meterDenominator;
822 uint16 meterNumerator;
828 struct TracktionChunk
852 if (
auto xml = parseXML (source))
854 if (xml->hasTagName (
"ebucore:ebuCoreMain"))
856 if (
auto xml2 = xml->getChildByName (
"ebucore:coreMetadata"))
858 if (
auto xml3 = xml2->getChildByName (
"ebucore:identifier"))
860 if (
auto xml4 = xml3->getChildByName (
"dc:identifier"))
862 auto ISRCCode = xml4->getAllSubText().fromFirstOccurrenceOf (
"ISRC:",
false,
true);
864 if (ISRCCode.isNotEmpty())
865 destValues.
set (WavAudioFormat::ISRC, ISRCCode);
878 if (
ISRC.isNotEmpty())
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>";
901 struct ExtensibleWavSubFormat
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); }
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 } };
917 struct DataSize64Chunk
923 uint32 sampleCountLow;
924 uint32 sampleCountHigh;
939 using namespace WavFileHelpers;
940 uint64 len = 0, end = 0;
941 int cueNoteIndex = 0;
942 int cueLabelIndex = 0;
943 int cueRegionIndex = 0;
945 auto streamStartPos = input->getPosition();
946 auto firstChunkType = input->readInt();
948 if (firstChunkType == chunkName (
"RF64"))
950 input->skipNextBytes (4);
953 else if (firstChunkType == chunkName (
"RIFF"))
955 len = (uint64) (uint32) input->readInt();
956 end = len + (uint64) input->getPosition();
963 auto startOfRIFFChunk = input->getPosition();
965 if (input->readInt() == chunkName (
"WAVE"))
967 if (isRF64 && input->readInt() == chunkName (
"ds64"))
969 auto length = (uint32) input->readInt();
974 auto chunkEnd = input->getPosition() + length + (length & 1);
975 len = (uint64) input->readInt64();
976 end = len + (uint64) startOfRIFFChunk;
977 dataLength = input->readInt64();
978 input->setPosition (chunkEnd);
981 while ((uint64) input->getPosition() < end && ! input->isExhausted())
983 auto chunkType = input->readInt();
984 auto length = (uint32) input->readInt();
985 auto chunkEnd = input->getPosition() + length + (length & 1);
987 if (chunkType == chunkName (
"fmt "))
990 auto format = (
unsigned short) input->readShort();
991 numChannels = (
unsigned int) input->readShort();
992 sampleRate = input->readInt();
993 auto bytesPerSec = input->readInt();
994 input->skipNextBytes (2);
995 bitsPerSample = (
unsigned int) (
int) input->readShort();
997 if (bitsPerSample > 64)
999 bytesPerFrame = bytesPerSec / (int) sampleRate;
1000 bitsPerSample = 8 * (
unsigned int) bytesPerFrame / numChannels;
1004 bytesPerFrame = (int) (numChannels * bitsPerSample / 8);
1009 usesFloatingPointData =
true;
1011 else if (format == 0xfffe)
1019 input->skipNextBytes (4);
1020 auto channelMask = input->readInt();
1021 metadataValues.set (
"ChannelMask",
String (channelMask));
1022 channelLayout = getChannelLayoutFromMask (channelMask, numChannels);
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));
1030 if (subFormat == IEEEFloatFormat)
1031 usesFloatingPointData =
true;
1032 else if (subFormat != pcmFormat && subFormat != ambisonicFormat)
1036 else if (format == 0x674f
1041 || format == 0x6771)
1043 isSubformatOggVorbis =
true;
1045 input->setPosition (streamStartPos);
1048 else if (format != 1)
1053 else if (chunkType == chunkName (
"data"))
1058 chunkEnd = input->getPosition() + dataLength + (dataLength & 1);
1062 dataLength = length;
1065 dataChunkStart = input->getPosition();
1066 lengthInSamples = (bytesPerFrame > 0) ? (dataLength / bytesPerFrame) : 0;
1068 else if (chunkType == chunkName (
"bext"))
1070 bwavChunkStart = input->getPosition();
1074 bwav.
calloc (jmax ((
size_t) length + 1,
sizeof (BWAVChunk)), 1);
1075 input->read (bwav, (
int) length);
1076 bwav->copyTo (metadataValues, (
int) length);
1078 else if (chunkType == chunkName (
"smpl"))
1081 smpl.
calloc (jmax ((
size_t) length + 1,
sizeof (SMPLChunk)), 1);
1082 input->read (smpl, (
int) length);
1083 smpl->copyTo (metadataValues, (
int) length);
1085 else if (chunkType == chunkName (
"inst") || chunkType == chunkName (
"INST"))
1088 inst.
calloc (jmax ((
size_t) length + 1,
sizeof (InstChunk)), 1);
1089 input->read (inst, (
int) length);
1090 inst->copyTo (metadataValues);
1092 else if (chunkType == chunkName (
"cue "))
1095 cue.
calloc (jmax ((
size_t) length + 1,
sizeof (CueChunk)), 1);
1096 input->read (cue, (
int) length);
1097 cue->copyTo (metadataValues, (
int) length);
1099 else if (chunkType == chunkName (
"axml"))
1102 input->readIntoMemoryBlock (axml, (ssize_t) length);
1103 AXMLChunk::addToMetadata (metadataValues, axml.
toString());
1105 else if (chunkType == chunkName (
"LIST"))
1107 auto subChunkType = input->readInt();
1109 if (subChunkType == chunkName (
"info") || subChunkType == chunkName (
"INFO"))
1111 ListInfoChunk::addToMetadata (metadataValues, *input, chunkEnd);
1113 else if (subChunkType == chunkName (
"adtl"))
1115 while (input->getPosition() < chunkEnd)
1117 auto adtlChunkType = input->readInt();
1118 auto adtlLength = (uint32) input->readInt();
1119 auto adtlChunkEnd = input->getPosition() + (adtlLength + (adtlLength & 1));
1121 if (adtlChunkType == chunkName (
"labl") || adtlChunkType == chunkName (
"note"))
1125 if (adtlChunkType == chunkName (
"labl"))
1126 prefix <<
"CueLabel" << cueLabelIndex++;
1127 else if (adtlChunkType == chunkName (
"note"))
1128 prefix <<
"CueNote" << cueNoteIndex++;
1130 auto identifier = (uint32) input->readInt();
1131 auto stringLength = (int) adtlLength - 4;
1134 input->readIntoMemoryBlock (textBlock, stringLength);
1136 metadataValues.set (prefix +
"Identifier",
String (identifier));
1137 metadataValues.set (prefix +
"Text", textBlock.
toString());
1139 else if (adtlChunkType == chunkName (
"ltxt"))
1141 auto prefix =
"CueRegion" +
String (cueRegionIndex++);
1142 auto identifier = (uint32) input->readInt();
1143 auto sampleLength = (uint32) input->readInt();
1144 auto purpose = (uint32) input->readInt();
1145 auto country = (uint16) input->readShort();
1146 auto language = (uint16) input->readShort();
1147 auto dialect = (uint16) input->readShort();
1148 auto codePage = (uint16) input->readShort();
1149 auto stringLength = adtlLength - 20;
1152 input->readIntoMemoryBlock (textBlock, (
int) stringLength);
1154 metadataValues.set (prefix +
"Identifier",
String (identifier));
1155 metadataValues.set (prefix +
"SampleLength",
String (sampleLength));
1156 metadataValues.set (prefix +
"Purpose",
String (purpose));
1157 metadataValues.set (prefix +
"Country",
String (country));
1158 metadataValues.set (prefix +
"Language",
String (language));
1159 metadataValues.set (prefix +
"Dialect",
String (dialect));
1160 metadataValues.set (prefix +
"CodePage",
String (codePage));
1161 metadataValues.set (prefix +
"Text", textBlock.
toString());
1164 input->setPosition (adtlChunkEnd);
1168 else if (chunkType == chunkName (
"acid"))
1170 AcidChunk (*input, length).addToMetadata (metadataValues);
1172 else if (chunkType == chunkName (
"Trkn"))
1175 input->readIntoMemoryBlock (tracktion, (ssize_t) length);
1176 metadataValues.set (WavAudioFormat::tracktionLoopInfo, tracktion.
toString());
1178 else if (chunkEnd <= input->getPosition())
1183 input->setPosition (chunkEnd);
1187 if (cueLabelIndex > 0) metadataValues.set (
"NumCueLabels",
String (cueLabelIndex));
1188 if (cueNoteIndex > 0) metadataValues.set (
"NumCueNotes",
String (cueNoteIndex));
1189 if (cueRegionIndex > 0) metadataValues.set (
"NumCueRegions",
String (cueRegionIndex));
1190 if (metadataValues.size() > 0) metadataValues.set (
"MetaDataSource",
"WAV");
1194 bool readSamples (
int** destSamples,
int numDestChannels,
int startOffsetInDestBuffer,
1195 int64 startSampleInFile,
int numSamples)
override 1197 clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
1198 startSampleInFile, numSamples, lengthInSamples);
1200 if (numSamples <= 0)
1203 input->setPosition (dataChunkStart + startSampleInFile * bytesPerFrame);
1205 while (numSamples > 0)
1207 const int tempBufSize = 480 * 3 * 4;
1208 char tempBuffer[tempBufSize];
1210 auto numThisTime = jmin (tempBufSize / bytesPerFrame, numSamples);
1211 auto bytesRead = input->read (tempBuffer, numThisTime * bytesPerFrame);
1213 if (bytesRead < numThisTime * bytesPerFrame)
1215 jassert (bytesRead >= 0);
1216 zeromem (tempBuffer + bytesRead, (
size_t) (numThisTime * bytesPerFrame - bytesRead));
1219 copySampleData (bitsPerSample, usesFloatingPointData,
1220 destSamples, startOffsetInDestBuffer, numDestChannels,
1221 tempBuffer, (
int) numChannels, numThisTime);
1223 startOffsetInDestBuffer += numThisTime;
1224 numSamples -= numThisTime;
1230 static void copySampleData (
unsigned int numBitsPerSample,
const bool floatingPointData,
1231 int*
const* destSamples,
int startOffsetInDestBuffer,
int numDestChannels,
1232 const void* sourceData,
int numberOfChannels,
int numSamples) noexcept
1234 switch (numBitsPerSample)
1236 case 8: ReadHelper<AudioData::Int32, AudioData::UInt8, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
break;
1237 case 16: ReadHelper<AudioData::Int32, AudioData::Int16, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
break;
1238 case 24: ReadHelper<AudioData::Int32, AudioData::Int24, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
break;
1239 case 32:
if (floatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
1240 else ReadHelper<AudioData::Int32, AudioData::Int32, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData, numberOfChannels, numSamples);
1242 default: jassertfalse;
break;
1249 if (channelLayout.size() ==
static_cast<int> (numChannels))
1250 return channelLayout;
1252 return WavFileHelpers::canonicalWavChannelSet (static_cast<int> (numChannels));
1255 static AudioChannelSet getChannelLayoutFromMask (
int dwChannelMask,
size_t totalNumChannels)
1263 wavFileChannelLayout.
addChannel (static_cast<AudioChannelSet::ChannelType> (bit + 1));
1266 if (wavFileChannelLayout.
size() !=
static_cast<int> (totalNumChannels))
1270 if (totalNumChannels <= 2 && dwChannelMask == 0)
1276 while (wavFileChannelLayout.
size() <
static_cast<int> (totalNumChannels))
1277 wavFileChannelLayout.
addChannel (static_cast<AudioChannelSet::ChannelType> (discreteSpeaker++));
1281 return wavFileChannelLayout;
1284 int64 bwavChunkStart = 0, bwavSize = 0;
1285 int64 dataChunkStart = 0, dataLength = 0;
1286 int bytesPerFrame = 0;
1287 bool isRF64 =
false;
1288 bool isSubformatOggVorbis =
false;
1293 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatReader)
1300 WavAudioFormatWriter (
OutputStream*
const out,
const double rate,
1305 using namespace WavFileHelpers;
1307 if (metadataValues.
size() > 0)
1312 jassert (metadataValues.
getValue (
"MetaDataSource",
"None") !=
"AIFF");
1314 bwavChunk = BWAVChunk::createFrom (metadataValues);
1315 axmlChunk = AXMLChunk::createFrom (metadataValues);
1316 smplChunk = SMPLChunk::createFrom (metadataValues);
1317 instChunk = InstChunk::createFrom (metadataValues);
1318 cueChunk = CueChunk ::createFrom (metadataValues);
1319 listChunk = ListChunk::createFrom (metadataValues);
1320 listInfoChunk = ListInfoChunk::createFrom (metadataValues);
1321 acidChunk = AcidChunk::createFrom (metadataValues);
1322 trckChunk = TracktionChunk::createFrom (metadataValues);
1329 ~WavAudioFormatWriter()
override 1335 bool write (
const int** data,
int numSamples)
override 1337 jassert (numSamples >= 0);
1338 jassert (data !=
nullptr && *data !=
nullptr);
1343 auto bytes = numChannels * (size_t) numSamples * bitsPerSample / 8;
1344 tempBlock.ensureSize (bytes,
false);
1346 switch (bitsPerSample)
1348 case 8: WriteHelper<AudioData::UInt8, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples);
break;
1349 case 16: WriteHelper<AudioData::Int16, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples);
break;
1350 case 24: WriteHelper<AudioData::Int24, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples);
break;
1351 case 32: WriteHelper<AudioData::Int32, AudioData::Int32, AudioData::LittleEndian>::write (tempBlock.getData(), (int) numChannels, data, numSamples);
break;
1352 default: jassertfalse;
break;
1355 if (! output->write (tempBlock.getData(), bytes))
1365 bytesWritten += bytes;
1366 lengthInSamples += (uint64) numSamples;
1370 bool flush()
override 1372 auto lastWritePos = output->getPosition();
1375 if (output->setPosition (lastWritePos))
1385 MemoryBlock tempBlock, bwavChunk, axmlChunk, smplChunk, instChunk, cueChunk, listChunk, listInfoChunk, acidChunk, trckChunk;
1386 uint64 lengthInSamples = 0, bytesWritten = 0;
1387 int64 headerPosition = 0;
1388 bool writeFailed =
false;
1392 if ((bytesWritten & 1) != 0)
1393 output->writeByte (0);
1395 using namespace WavFileHelpers;
1397 if (headerPosition != output->getPosition() && ! output->setPosition (headerPosition))
1405 const size_t bytesPerFrame = numChannels * bitsPerSample / 8;
1406 uint64 audioDataSize = bytesPerFrame * lengthInSamples;
1407 auto channelMask = getChannelMaskFromChannelLayout (channelLayout);
1409 const bool isRF64 = (bytesWritten >= 0x100000000LL);
1410 const bool isWaveFmtEx = isRF64 || (channelMask != 0);
1412 int64 riffChunkSize = (int64) (4 + 8 + 40
1413 + 8 + audioDataSize + (audioDataSize & 1)
1414 + chunkSize (bwavChunk)
1415 + chunkSize (axmlChunk)
1416 + chunkSize (smplChunk)
1417 + chunkSize (instChunk)
1418 + chunkSize (cueChunk)
1419 + chunkSize (listChunk)
1420 + chunkSize (listInfoChunk)
1421 + chunkSize (acidChunk)
1422 + chunkSize (trckChunk)
1425 riffChunkSize += (riffChunkSize & 1);
1428 writeChunkHeader (chunkName (
"RF64"), -1);
1430 writeChunkHeader (chunkName (
"RIFF"), (
int) riffChunkSize);
1432 output->writeInt (chunkName (
"WAVE"));
1436 #if ! JUCE_WAV_DO_NOT_PAD_HEADER_SIZE 1447 writeChunkHeader (chunkName (
"JUNK"), 28 + (isWaveFmtEx? 0 : 24));
1448 output->writeRepeatedByte (0, 28 + (isWaveFmtEx? 0 : 24));
1453 #if JUCE_WAV_DO_NOT_PAD_HEADER_SIZE 1458 writeChunkHeader (chunkName (
"ds64"), 28);
1459 output->writeInt64 (riffChunkSize);
1460 output->writeInt64 ((int64) audioDataSize);
1461 output->writeRepeatedByte (0, 12);
1466 writeChunkHeader (chunkName (
"fmt "), 40);
1467 output->writeShort ((
short) (uint16) 0xfffe);
1471 writeChunkHeader (chunkName (
"fmt "), 16);
1472 output->writeShort (bitsPerSample < 32 ? (
short) 1
1476 output->writeShort ((
short) numChannels);
1477 output->writeInt ((
int) sampleRate);
1478 output->writeInt ((
int) (bytesPerFrame * sampleRate));
1479 output->writeShort ((
short) bytesPerFrame);
1480 output->writeShort ((
short) bitsPerSample);
1484 output->writeShort (22);
1485 output->writeShort ((
short) bitsPerSample);
1486 output->writeInt (channelMask);
1488 const ExtensibleWavSubFormat& subFormat = bitsPerSample < 32 ? pcmFormat : IEEEFloatFormat;
1490 output->writeInt ((
int) subFormat.data1);
1491 output->writeShort ((
short) subFormat.data2);
1492 output->writeShort ((
short) subFormat.data3);
1493 output->write (subFormat.data4, sizeof (subFormat.data4));
1496 writeChunk (bwavChunk, chunkName (
"bext"));
1497 writeChunk (axmlChunk, chunkName (
"axml"));
1498 writeChunk (smplChunk, chunkName (
"smpl"));
1499 writeChunk (instChunk, chunkName (
"inst"), 7);
1500 writeChunk (cueChunk, chunkName (
"cue "));
1501 writeChunk (listChunk, chunkName (
"LIST"));
1502 writeChunk (listInfoChunk, chunkName (
"LIST"));
1503 writeChunk (acidChunk, chunkName (
"acid"));
1504 writeChunk (trckChunk, chunkName (
"Trkn"));
1506 writeChunkHeader (chunkName (
"data"), isRF64 ? -1 : (
int) (lengthInSamples * bytesPerFrame));
1508 usesFloatingPointData = (bitsPerSample == 32);
1513 void writeChunkHeader (
int chunkType,
int size)
const 1515 output->writeInt (chunkType);
1516 output->writeInt (size);
1519 void writeChunk (
const MemoryBlock& data,
int chunkType,
int size = 0)
const 1523 writeChunkHeader (chunkType, size != 0 ? size : (
int) data.
getSize());
1528 static int getChannelMaskFromChannelLayout (
const AudioChannelSet& layout)
1539 auto wavChannelMask = 0;
1541 for (
auto channel : channels)
1543 int wavChannelBit =
static_cast<int> (channel) - 1;
1544 jassert (wavChannelBit >= 0 && wavChannelBit <= 31);
1546 wavChannelMask |= (1 << wavChannelBit);
1549 return wavChannelMask;
1552 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatWriter)
1559 MemoryMappedWavReader (
const File& wavFile,
const WavAudioFormatReader& reader)
1561 reader.dataLength, reader.bytesPerFrame)
1565 bool readSamples (
int** destSamples,
int numDestChannels,
int startOffsetInDestBuffer,
1566 int64 startSampleInFile,
int numSamples)
override 1568 clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
1569 startSampleInFile, numSamples, lengthInSamples);
1571 if (map ==
nullptr || ! mappedSection.contains (
Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
1577 WavAudioFormatReader::copySampleData (bitsPerSample, usesFloatingPointData,
1578 destSamples, startOffsetInDestBuffer, numDestChannels,
1579 sampleToPointer (startSampleInFile), (
int) numChannels, numSamples);
1583 void getSample (int64 sample,
float* result)
const noexcept
override 1585 auto num = (int) numChannels;
1587 if (map ==
nullptr || ! mappedSection.contains (sample))
1591 zeromem (result, (
size_t) num *
sizeof (
float));
1595 auto dest = &result;
1596 auto source = sampleToPointer (sample);
1598 switch (bitsPerSample)
1600 case 8: ReadHelper<AudioData::Float32, AudioData::UInt8, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
break;
1601 case 16: ReadHelper<AudioData::Float32, AudioData::Int16, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
break;
1602 case 24: ReadHelper<AudioData::Float32, AudioData::Int24, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
break;
1603 case 32:
if (usesFloatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
1604 else ReadHelper<AudioData::Float32, AudioData::Int32, AudioData::LittleEndian>::read (dest, 0, 1, source, 1, num);
1606 default: jassertfalse;
break;
1610 void readMaxLevels (int64 startSampleInFile, int64 numSamples,
Range<float>* results,
int numChannelsToRead)
override 1612 numSamples = jmin (numSamples, lengthInSamples - startSampleInFile);
1614 if (map ==
nullptr || numSamples <= 0 || ! mappedSection.contains (
Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
1616 jassert (numSamples <= 0);
1618 for (
int i = 0; i < numChannelsToRead; ++i)
1624 switch (bitsPerSample)
1626 case 8: scanMinAndMax<AudioData::UInt8> (startSampleInFile, numSamples, results, numChannelsToRead);
break;
1627 case 16: scanMinAndMax<AudioData::Int16> (startSampleInFile, numSamples, results, numChannelsToRead);
break;
1628 case 24: scanMinAndMax<AudioData::Int24> (startSampleInFile, numSamples, results, numChannelsToRead);
break;
1629 case 32:
if (usesFloatingPointData) scanMinAndMax<AudioData::Float32> (startSampleInFile, numSamples, results, numChannelsToRead);
1630 else scanMinAndMax<AudioData::Int32> (startSampleInFile, numSamples, results, numChannelsToRead);
1632 default: jassertfalse;
break;
1639 template <
typename SampleType>
1640 void scanMinAndMax (int64 startSampleInFile, int64 numSamples,
Range<float>* results,
int numChannelsToRead)
const noexcept
1642 for (
int i = 0; i < numChannelsToRead; ++i)
1643 results[i] = scanMinAndMaxInterleaved<SampleType, AudioData::LittleEndian> (i, startSampleInFile, numSamples);
1646 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedWavReader)
1655 return { 8000, 11025, 12000, 16000, 22050, 32000, 44100,
1656 48000, 88200, 96000, 176400, 192000, 352800, 384000 };
1661 return { 8, 16, 24, 32 };
1676 for (
auto channel : channelTypes)
1685 std::unique_ptr<WavAudioFormatReader> r (
new WavAudioFormatReader (sourceStream));
1687 #if JUCE_USE_OGGVORBIS 1688 if (r->isSubformatOggVorbis)
1691 return OggVorbisAudioFormat().createReaderFor (sourceStream, deleteStreamIfOpeningFails);
1695 if (r->sampleRate > 0 && r->numChannels > 0 && r->bytesPerFrame > 0 && r->bitsPerSample <= 32)
1698 if (! deleteStreamIfOpeningFails)
1713 WavAudioFormatReader reader (fin);
1715 if (reader.lengthInSamples > 0)
1716 return new MemoryMappedWavReader (fin->
getFile(), reader);
1723 unsigned int numChannels,
int bitsPerSample,
1726 return createWriterFor (out, sampleRate, WavFileHelpers::canonicalWavChannelSet (static_cast<int> (numChannels)),
1727 bitsPerSample, metadataValues, qualityOptionIndex);
1738 return new WavAudioFormatWriter (out, sampleRate, channelLayout,
1739 (
unsigned int) bitsPerSample, metadataValues);
1744 namespace WavFileHelpers
1746 static bool slowCopyWavFileWithNewMetadata (
const File& file,
const StringPairArray& metadata)
1753 if (reader !=
nullptr)
1757 if (outStream !=
nullptr)
1759 std::unique_ptr<AudioFormatWriter> writer (wav.
createWriterFor (outStream.get(), reader->sampleRate,
1760 reader->numChannels, (int) reader->bitsPerSample,
1763 if (writer !=
nullptr)
1765 outStream.release();
1767 bool ok = writer->writeFromAudioReader (*reader, 0, -1);
1782 using namespace WavFileHelpers;
1786 if (reader !=
nullptr)
1788 auto bwavPos = reader->bwavChunkStart;
1789 auto bwavSize = reader->bwavSize;
1794 auto chunk = BWAVChunk::createFrom (newMetadata);
1796 if (chunk.getSize() <= (size_t) bwavSize)
1799 auto oldSize = wavFile.
getSize();
1812 jassert (wavFile.
getSize() == oldSize);
1818 return slowCopyWavFileWithNewMetadata (wavFile, newMetadata);
1826 struct WaveAudioFormatTests :
public UnitTest 1828 WaveAudioFormatTests()
1829 :
UnitTest (
"Wave audio format tests", UnitTestCategories::audio)
1832 void runTest()
override 1834 beginTest (
"Setting up metadata");
1840 numTestAudioBufferSamples,
1843 for (
int i = numElementsInArray (WavFileHelpers::ListInfoChunk::types); --i >= 0;)
1844 metadataValues.
set (WavFileHelpers::ListInfoChunk::types[i],
1845 WavFileHelpers::ListInfoChunk::types[i]);
1847 if (metadataValues.
size() > 0)
1848 metadataValues.
set (
"MetaDataSource",
"WAV");
1850 metadataValues.
addArray (createDefaultSMPLMetadata());
1856 beginTest (
"Creating a basic wave writer");
1859 44100.0, numTestAudioBufferChannels,
1860 32, metadataValues, 0));
1861 expect (writer !=
nullptr);
1866 beginTest (
"Writing audio data to the basic wave writer");
1867 expect (writer->writeFromAudioSampleBuffer (buffer, 0, numTestAudioBufferSamples));
1871 beginTest (
"Creating a basic wave reader");
1874 expect (reader !=
nullptr);
1875 expect (reader->metadataValues == metadataValues,
"Somehow, the metadata is different!");
1882 numTestAudioBufferChannels = 2,
1883 numTestAudioBufferSamples = 256
1890 m.
set (
"Manufacturer",
"0");
1891 m.
set (
"Product",
"0");
1892 m.
set (
"SamplePeriod",
"0");
1893 m.
set (
"MidiUnityNote",
"60");
1894 m.
set (
"MidiPitchFraction",
"0");
1895 m.
set (
"SmpteFormat",
"0");
1896 m.
set (
"SmpteOffset",
"0");
1897 m.
set (
"NumSampleLoops",
"0");
1898 m.
set (
"SamplerData",
"0");
1903 JUCE_DECLARE_NON_COPYABLE (WaveAudioFormatTests)
1906 static const WaveAudioFormatTests waveAudioFormatTests;
size_t getSize() const noexcept
static Type swapIfBigEndian(Type value) noexcept
static juce_wchar toUpperCase(juce_wchar character) noexcept
bool overwriteTargetFileWithTemporary() const
const File & getFile() const noexcept
virtual bool writeByte(char byte)
static AudioChannelSet JUCE_CALLTYPE create7point1SDDS()
bool setPosition(int64) override
const StringArray & getAllKeys() const noexcept
void add(const ElementType &newElement)
int size() const noexcept
MemoryBlock getMemoryBlock() const
String formatted(const String &format) const
String getValue(StringRef, const String &defaultReturnValue) const
Array< ChannelType > getChannelTypes() const
static String createStringFromData(const void *data, int size)
void addChannel(ChannelType newChannelType)
FileInputStream * createInputStream() const
static Time JUCE_CALLTYPE getCurrentTime() noexcept
int size() const noexcept
void calloc(SizeType newNumElements, const size_t elementSize=sizeof(ElementType))
static AudioChannelSet JUCE_CALLTYPE mono()
static AudioChannelSet JUCE_CALLTYPE create7point0SDDS()
bool isDiscreteLayout() const noexcept
void * getData() noexcept
bool writeRepeatedByte(uint8 byte, size_t numTimesToRepeat) override
bool write(const void *, size_t) override
static AudioChannelSet JUCE_CALLTYPE create5point1()
static JUCE_CONSTEXPR uint16 swap(uint16 value) noexcept
static AudioChannelSet JUCE_CALLTYPE create5point0()
virtual bool writeShort(short value)
static JUCE_CONSTEXPR uint32 littleEndianInt(const void *bytes) noexcept
FileOutputStream * createOutputStream(size_t bufferSize=0x8000) const
static AudioChannelSet JUCE_CALLTYPE canonicalChannelSet(int numChannels)
size_t getDataSize() const noexcept
void set(const String &key, const String &value)
static AudioChannelSet JUCE_CALLTYPE createLCR()
static AudioChannelSet JUCE_CALLTYPE stereo()
bool containsKey(StringRef key) const noexcept
bool openedOk() const noexcept
int getIntValue() const noexcept
static AudioChannelSet JUCE_CALLTYPE discreteChannels(int numChannels)
virtual bool writeInt(int value)
void addArray(const StringPairArray &other)
static AudioChannelSet JUCE_CALLTYPE quadraphonic()
virtual bool writeString(const String &text)
int findNextSetBit(int startIndex) const noexcept
size_t getNumBytesAsUTF8() const noexcept
virtual int64 getPosition()=0
static String fromUTF8(const char *utf8buffer, int bufferSizeBytes=-1)
void fillWith(uint8 valueToUse) noexcept
bool contains(ParameterType elementToLookFor) const
void setSize(const size_t newSize, bool initialiseNewSpaceToZero=false)