30static const char*
const wavFormatName =
"WAV file";
43 const String& originatorRef,
46 const String& codingHistory)
158namespace 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];
181 void copyTo (StringPairArray& values,
const int totalSize)
const
191 auto time = (((int64) timeHigh) << 32) + timeLow;
195 String::fromUTF8 (codingHistory, totalSize - (
int) offsetof (BWAVChunk, codingHistory)));
198 static MemoryBlock createFrom (
const StringPairArray& values)
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
235 inline AudioChannelSet canonicalWavChannelSet (
int numChannels)
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);
284 void copyTo (StringPairArray& values,
const int totalSize)
const
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);
321 static MemoryBlock createFrom (
const StringPairArray& values)
324 auto numLoops = jmin (64, values.getValue (
"NumSampleLoops",
"0").getIntValue());
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)
368 values.
set (name, String (val));
371 void copyTo (StringPairArray& values)
const
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)
384 return (int8) values.getValue (name, def).getIntValue();
387 static MemoryBlock createFrom (
const StringPairArray& values)
390 auto& keys = values.getAllKeys();
392 if (keys.contains (
"LowNote",
true) && keys.contains (
"HighNote",
true))
394 data.setSize (8,
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)
431 void copyTo (StringPairArray& values,
const int totalSize)
const
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);
449 static MemoryBlock createFrom (
const StringPairArray& values)
452 const int numCues = values.getValue (
"NumCuePoints",
"0").getIntValue();
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"));
466 Array<uint32> identifiers;
469 for (
int i = 0; i < numCues; ++i)
471 auto prefix =
"Cue" + String (i);
472 auto identifier = (uint32) values.getValue (prefix +
"Identifier",
"0").getIntValue();
475 jassert (! identifiers.contains (identifier));
476 identifiers.add (identifier);
479 auto order = values.getValue (prefix +
"Order", String (nextOrder)).getIntValue();
480 nextOrder = jmax (nextOrder, order) + 1;
482 auto& cue = c->cues[i];
500 static int getValue (
const StringPairArray& values,
const String& name)
502 return values.getValue (name,
"0").getIntValue();
505 static int getValue (
const StringPairArray& values,
const String& prefix,
const char* name)
507 return getValue (values, prefix + name);
510 static void appendLabelOrNoteChunk (
const StringPairArray& values,
const String& prefix,
511 const int chunkType, MemoryOutputStream& out)
513 auto label = values.getValue (prefix +
"Text", prefix);
514 auto labelLength = (int) label.getNumBytesAsUTF8() + 1;
515 auto chunkLength = 4 + labelLength + (labelLength & 1);
517 out.writeInt (chunkType);
518 out.writeInt (chunkLength);
519 out.writeInt (getValue (values, prefix,
"Identifier"));
520 out.write (label.toUTF8(), (size_t) labelLength);
522 if ((out.getDataSize() & 1) != 0)
526 static void appendExtraChunk (
const StringPairArray& values,
const String& prefix, MemoryOutputStream& out)
528 auto text = values.getValue (prefix +
"Text", prefix);
530 auto textLength = (int) text.getNumBytesAsUTF8() + 1;
531 auto chunkLength = textLength + 20 + (textLength & 1);
533 out.writeInt (chunkName (
"ltxt"));
534 out.writeInt (chunkLength);
535 out.writeInt (getValue (values, prefix,
"Identifier"));
536 out.writeInt (getValue (values, prefix,
"SampleLength"));
537 out.writeInt (getValue (values, prefix,
"Purpose"));
538 out.writeShort ((
short) getValue (values, prefix,
"Country"));
539 out.writeShort ((
short) getValue (values, prefix,
"Language"));
540 out.writeShort ((
short) getValue (values, prefix,
"Dialect"));
541 out.writeShort ((
short) getValue (values, prefix,
"CodePage"));
542 out.write (text.toUTF8(), (size_t) textLength);
544 if ((out.getDataSize() & 1) != 0)
548 static MemoryBlock createFrom (
const StringPairArray& values)
550 auto numCueLabels = getValue (values,
"NumCueLabels");
551 auto numCueNotes = getValue (values,
"NumCueNotes");
552 auto numCueRegions = getValue (values,
"NumCueRegions");
554 MemoryOutputStream out;
556 if (numCueLabels + numCueNotes + numCueRegions > 0)
558 out.writeInt (chunkName (
"adtl"));
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);
570 return out.getMemoryBlock();
576 namespace ListInfoChunk
578 static const char*
const types[] =
663 static bool isMatchingTypeIgnoringCase (
const int value,
const char*
const name)
noexcept
665 for (
int i = 0; i < 4; ++i)
686 for (
auto& type : types)
688 if (isMatchingTypeIgnoringCase (
infoType, type))
693 (
int)
mb.getSize()));
715 if ((
out.getDataSize() & 1) != 0)
724 out.writeInt (chunkName (
"INFO"));
727 for (
auto& type : types)
728 if (writeValue (values,
out, type))
742 input.
read (
this, (
int) jmin (
sizeof (*
this), length));
766 static MemoryBlock createFrom (
const StringPairArray& values)
768 return AcidChunk (values).toMemoryBlock();
771 MemoryBlock toMemoryBlock()
const
773 return (flags != 0 || rootNote != 0 || numBeats != 0 || meterDenominator != 0 || meterNumerator != 0)
774 ? MemoryBlock (
this,
sizeof (*
this)) : MemoryBlock();
777 void addToMetadata (StringPairArray& values)
const
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
830 static MemoryBlock createFrom (
const StringPairArray& values)
832 MemoryOutputStream out;
839 if ((out.getDataSize() & 1) != 0)
843 return out.getMemoryBlock();
850 static void addToMetadata (StringPairArray& destValues,
const String& source)
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())
873 static MemoryBlock createFrom (
const StringPairArray& values)
876 MemoryOutputStream xml;
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>";
893 xml.writeRepeatedByte (0, xml.getDataSize());
896 return xml.getMemoryBlock();
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;
934class WavAudioFormatReader :
public AudioFormatReader
939 using namespace WavFileHelpers;
940 uint64 len = 0, end = 0;
941 int cueNoteIndex = 0;
942 int cueLabelIndex = 0;
943 int cueRegionIndex = 0;
948 if (firstChunkType == chunkName (
"RF64"))
953 else if (firstChunkType == chunkName (
"RIFF"))
976 end = len + (uint64) startOfRIFFChunk;
987 if (chunkType == chunkName (
"fmt "))
999 bytesPerFrame = bytesPerSec / (int)
sampleRate;
1011 else if (format == 0xfffe)
1022 channelLayout = getChannelLayoutFromMask (channelMask,
numChannels);
1024 ExtensibleWavSubFormat subFormat;
1028 input->
read (subFormat.data4, sizeof (subFormat.data4));
1030 if (subFormat == IEEEFloatFormat)
1032 else if (subFormat != pcmFormat && subFormat != ambisonicFormat)
1036 else if (format == 0x674f
1041 || format == 0x6771)
1043 isSubformatOggVorbis =
true;
1048 else if (format != 1)
1053 else if (chunkType == chunkName (
"data"))
1062 dataLength = length;
1066 lengthInSamples = (bytesPerFrame > 0) ? (dataLength / bytesPerFrame) : 0;
1068 else if (chunkType == chunkName (
"bext"))
1073 HeapBlock<BWAVChunk> bwav;
1074 bwav.calloc (jmax ((
size_t) length + 1,
sizeof (BWAVChunk)), 1);
1078 else if (chunkType == chunkName (
"smpl"))
1080 HeapBlock<SMPLChunk> smpl;
1081 smpl.calloc (jmax ((
size_t) length + 1,
sizeof (SMPLChunk)), 1);
1085 else if (chunkType == chunkName (
"inst") || chunkType == chunkName (
"INST"))
1087 HeapBlock<InstChunk> inst;
1088 inst.calloc (jmax ((
size_t) length + 1,
sizeof (InstChunk)), 1);
1092 else if (chunkType == chunkName (
"cue "))
1094 HeapBlock<CueChunk> cue;
1095 cue.calloc (jmax ((
size_t) length + 1,
sizeof (CueChunk)), 1);
1099 else if (chunkType == chunkName (
"axml"))
1105 else if (chunkType == chunkName (
"LIST"))
1109 if (subChunkType == chunkName (
"info") || subChunkType == chunkName (
"INFO"))
1113 else if (subChunkType == chunkName (
"adtl"))
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++;
1131 auto stringLength = (int) adtlLength - 4;
1133 MemoryBlock textBlock;
1139 else if (adtlChunkType == chunkName (
"ltxt"))
1141 auto prefix =
"CueRegion" + String (cueRegionIndex++);
1149 auto stringLength = adtlLength - 20;
1151 MemoryBlock textBlock;
1168 else if (chunkType == chunkName (
"acid"))
1172 else if (chunkType == chunkName (
"Trkn"))
1174 MemoryBlock tracktion;
1178 else if (chunkEnd <= input->getPosition())
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));
1194 bool readSamples (
int** destSamples,
int numDestChannels,
int startOffsetInDestBuffer,
1195 int64 startSampleInFile,
int numSamples)
override
1200 if (numSamples <= 0)
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));
1220 destSamples, startOffsetInDestBuffer, numDestChannels,
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;
1247 AudioChannelSet getChannelLayout()
override
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)
1257 AudioChannelSet wavFileChannelLayout;
1260 BigInteger channelBits (dwChannelMask);
1262 for (
auto bit = channelBits.findNextSetBit (0); bit >= 0; bit = channelBits.findNextSetBit (bit + 1))
1266 if (wavFileChannelLayout.size() !=
static_cast<int> (totalNumChannels))
1270 if (totalNumChannels <= 2 && dwChannelMask == 0)
1276 while (wavFileChannelLayout.size() <
static_cast<int> (totalNumChannels))
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;
1290 AudioChannelSet channelLayout;
1293 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatReader)
1297class WavAudioFormatWriter :
public AudioFormatWriter
1300 WavAudioFormatWriter (OutputStream*
const out,
const double rate,
1301 const AudioChannelSet& channelLayoutToUse,
const unsigned int bits,
1302 const StringPairArray& metadataValues)
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);
1325 headerPosition = out->getPosition();
1329 ~WavAudioFormatWriter()
override
1335 bool write (
const int** data,
int numSamples)
override
1337 jassert (numSamples >= 0);
1338 jassert (data !=
nullptr && *data !=
nullptr);
1344 tempBlock.ensureSize (bytes,
false);
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;
1365 bytesWritten += bytes;
1366 lengthInSamples += (uint64) numSamples;
1370 bool flush()
override
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)
1395 using namespace WavFileHelpers;
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);
1436 #if ! JUCE_WAV_DO_NOT_PAD_HEADER_SIZE
1447 writeChunkHeader (chunkName (
"JUNK"), 28 + (isWaveFmtEx? 0 : 24));
1453 #if JUCE_WAV_DO_NOT_PAD_HEADER_SIZE
1458 writeChunkHeader (chunkName (
"ds64"), 28);
1466 writeChunkHeader (chunkName (
"fmt "), 40);
1471 writeChunkHeader (chunkName (
"fmt "), 16);
1488 const ExtensibleWavSubFormat& subFormat =
bitsPerSample < 32 ? pcmFormat : IEEEFloatFormat;
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));
1511 static size_t chunkSize (
const MemoryBlock& data)
noexcept {
return data.getSize() > 0 ? (8 + data.getSize()) : 0; }
1513 void writeChunkHeader (
int chunkType,
int size)
const
1519 void writeChunk (
const MemoryBlock& data,
int chunkType,
int size = 0)
const
1521 if (data.getSize() > 0)
1523 writeChunkHeader (chunkType, size != 0 ? size : (int) data.getSize());
1528 static int getChannelMaskFromChannelLayout (
const AudioChannelSet& layout)
1530 if (layout.isDiscreteLayout())
1538 auto channels = layout.getChannelTypes();
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)
1556class MemoryMappedWavReader :
public MemoryMappedAudioFormatReader
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
1571 if (map ==
nullptr || ! mappedSection.
contains (Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
1578 destSamples, startOffsetInDestBuffer, numDestChannels,
1583 void getSample (int64 sample,
float* result)
const noexcept override
1587 if (map ==
nullptr || ! mappedSection.
contains (sample))
1591 zeromem (result, (
size_t) num *
sizeof (
float));
1595 auto dest = &result;
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
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)
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 };
1685 std::unique_ptr<WavAudioFormatReader> r (
new WavAudioFormatReader (sourceStream));
1687 #if JUCE_USE_OGGVORBIS
1688 if (r->isSubformatOggVorbis)
1695 if (r->sampleRate > 0 && r->numChannels > 0 && r->bytesPerFrame > 0 && r->bitsPerSample <= 32)
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)),
1738 return new WavAudioFormatWriter (
out, sampleRate, channelLayout,
1739 (
unsigned int) bitsPerSample, metadataValues);
1744namespace WavFileHelpers
1751 std::unique_ptr<AudioFormatReader> reader (
wav.createReaderFor (file.
createInputStream(),
true));
1753 if (reader !=
nullptr)
1755 std::unique_ptr<OutputStream>
outStream (
tempFile.getFile().createOutputStream());
1759 std::unique_ptr<AudioFormatWriter> writer (
wav.createWriterFor (
outStream.get(), reader->sampleRate,
1760 reader->numChannels, (
int) reader->bitsPerSample,
1763 if (writer !=
nullptr)
1767 bool ok = writer->writeFromAudioReader (*reader, 0, -1);
1771 return ok &&
tempFile.overwriteTargetFileWithTemporary();
1782 using namespace WavFileHelpers;
1784 std::unique_ptr<WavAudioFormatReader> reader (
static_cast<WavAudioFormatReader*
> (
createReaderFor (
wavFile.createInputStream(),
true)));
1786 if (reader !=
nullptr)
1788 auto bwavPos = reader->bwavChunkStart;
1789 auto bwavSize = reader->bwavSize;
1796 if (
chunk.getSize() <= (
size_t) bwavSize)
1829 :
UnitTest (
"Wave audio format tests", UnitTestCategories::audio)
1832 void runTest()
override
1834 beginTest (
"Setting up metadata");
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");
1852 WavAudioFormat format;
1856 beginTest (
"Creating a basic wave writer");
1858 std::unique_ptr<AudioFormatWriter> writer (format.createWriterFor (
new MemoryOutputStream (
memoryBlock,
false),
1860 32, metadataValues, 0));
1861 expect (writer !=
nullptr);
1866 beginTest (
"Writing audio data to the basic wave writer");
1871 beginTest (
"Creating a basic wave reader");
1873 std::unique_ptr<AudioFormatReader> reader (format.createReaderFor (
new MemoryInputStream (
memoryBlock,
false),
false));
1874 expect (reader !=
nullptr);
1875 expect (reader->metadataValues == metadataValues,
"Somehow, the metadata is different!");
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");
1906static const WaveAudioFormatTests waveAudioFormatTests;
bool isEmpty() const noexcept
void set(int indexToChange, ParameterType newValue)
static AudioChannelSet JUCE_CALLTYPE quadraphonic()
static AudioChannelSet JUCE_CALLTYPE create5point0()
static AudioChannelSet JUCE_CALLTYPE mono()
static AudioChannelSet JUCE_CALLTYPE stereo()
static AudioChannelSet JUCE_CALLTYPE create5point1()
static AudioChannelSet JUCE_CALLTYPE create7point0SDDS()
static AudioChannelSet JUCE_CALLTYPE create7point1SDDS()
static AudioChannelSet JUCE_CALLTYPE canonicalChannelSet(int numChannels)
static AudioChannelSet JUCE_CALLTYPE discreteChannels(int numChannels)
static AudioChannelSet JUCE_CALLTYPE createLCR()
static JUCE_CONSTEXPR uint16 swap(uint16 value) noexcept
static Type swapIfBigEndian(Type value) noexcept
static JUCE_CONSTEXPR uint32 littleEndianInt(const void *bytes) noexcept
static juce_wchar toUpperCase(juce_wchar character) noexcept
FileInputStream * createInputStream() const
virtual bool write(const void *dataToWrite, size_t numberOfBytes)=0
virtual bool writeRepeatedByte(uint8 byte, size_t numTimesToRepeat)
virtual int64 getPosition()=0
virtual bool writeByte(char byte)
virtual bool writeShort(short value)
virtual bool writeInt64(int64 value)
virtual bool setPosition(int64 newPosition)=0
virtual bool writeInt(int value)
JUCE_CONSTEXPR bool contains(const ValueType position) const noexcept
String getValue(StringRef, const String &defaultReturnValue) const
bool containsKey(StringRef key) const noexcept
void set(const String &key, const String &value)
int size() const noexcept
static String createStringFromData(const void *data, int size)
static String fromUTF8(const char *utf8buffer, int bufferSizeBytes=-1)
static Time JUCE_CALLTYPE getCurrentTime() noexcept