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];
191 auto time = (((int64) timeHigh) << 32) + timeLow;
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);
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)
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))
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);
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)
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));
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;
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();
908 bool operator== (
const ExtensibleWavSubFormat& other)
const noexcept {
return memcmp (
this, &other,
sizeof (*
this)) == 0; }
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 } };
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;
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"))
1056 dataLength = length;
1059 lengthInSamples = (bytesPerFrame > 0) ? (dataLength / bytesPerFrame) : 0;
1061 else if (chunkType == chunkName (
"bext"))
1067 bwav.
calloc (jmax ((
size_t) length + 1,
sizeof (BWAVChunk)), 1);
1071 else if (chunkType == chunkName (
"smpl"))
1074 smpl.
calloc (jmax ((
size_t) length + 1,
sizeof (SMPLChunk)), 1);
1078 else if (chunkType == chunkName (
"inst") || chunkType == chunkName (
"INST"))
1081 inst.
calloc (jmax ((
size_t) length + 1,
sizeof (InstChunk)), 1);
1085 else if (chunkType == chunkName (
"cue "))
1088 cue.
calloc (jmax ((
size_t) length + 1,
sizeof (CueChunk)), 1);
1092 else if (chunkType == chunkName (
"axml"))
1098 else if (chunkType == chunkName (
"LIST"))
1102 if (subChunkType == chunkName (
"info") || subChunkType == chunkName (
"INFO"))
1106 else if (subChunkType == chunkName (
"adtl"))
1112 auto adtlChunkEnd =
input->
getPosition() + (adtlLength + (adtlLength & 1));
1114 if (adtlChunkType == chunkName (
"labl") || adtlChunkType == chunkName (
"note"))
1118 if (adtlChunkType == chunkName (
"labl"))
1119 prefix <<
"CueLabel" << cueLabelIndex++;
1120 else if (adtlChunkType == chunkName (
"note"))
1121 prefix <<
"CueNote" << cueNoteIndex++;
1124 auto stringLength = (int) adtlLength - 4;
1132 else if (adtlChunkType == chunkName (
"ltxt"))
1134 auto prefix =
"CueRegion" +
String (cueRegionIndex++);
1142 auto stringLength = adtlLength - 20;
1161 else if (chunkType == chunkName (
"acid"))
1165 else if (chunkType == chunkName (
"Trkn"))
1171 else if (chunkEnd <= input->getPosition())
1187 bool readSamples (
int** destSamples,
int numDestChannels,
int startOffsetInDestBuffer,
1188 int64 startSampleInFile,
int numSamples)
override
1193 if (numSamples <= 0)
1198 while (numSamples > 0)
1200 const int tempBufSize = 480 * 3 * 4;
1201 char tempBuffer[tempBufSize];
1203 auto numThisTime = jmin (tempBufSize / bytesPerFrame, numSamples);
1204 auto bytesRead =
input->
read (tempBuffer, numThisTime * bytesPerFrame);
1206 if (bytesRead < numThisTime * bytesPerFrame)
1208 jassert (bytesRead >= 0);
1209 zeromem (tempBuffer + bytesRead, (
size_t) (numThisTime * bytesPerFrame - bytesRead));
1213 destSamples, startOffsetInDestBuffer, numDestChannels,
1216 startOffsetInDestBuffer += numThisTime;
1217 numSamples -= numThisTime;
1224 int*
const* destSamples,
int startOffsetInDestBuffer,
int numDestChannels,
1225 const void* sourceData,
int numChannels,
int numSamples) noexcept
1229 case 8: ReadHelper<AudioData::Int32, AudioData::UInt8, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData,
numChannels, numSamples);
break;
1230 case 16: ReadHelper<AudioData::Int32, AudioData::Int16, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData,
numChannels, numSamples);
break;
1231 case 24: ReadHelper<AudioData::Int32, AudioData::Int24, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData,
numChannels, numSamples);
break;
1232 case 32:
if (
usesFloatingPointData) ReadHelper<AudioData::Float32, AudioData::Float32, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData,
numChannels, numSamples);
1233 else ReadHelper<AudioData::Int32, AudioData::Int32, AudioData::LittleEndian>::read (destSamples, startOffsetInDestBuffer, numDestChannels, sourceData,
numChannels, numSamples);
1235 default: jassertfalse;
break;
1243 return channelLayout;
1245 return WavFileHelpers::canonicalWavChannelSet (
static_cast<int> (
numChannels));
1248 static AudioChannelSet getChannelLayoutFromMask (
int dwChannelMask,
size_t totalNumChannels)
1255 for (
auto bit = channelBits.findNextSetBit (0); bit >= 0; bit = channelBits.findNextSetBit (bit + 1))
1259 if (wavFileChannelLayout.
size() !=
static_cast<int> (totalNumChannels))
1263 if (totalNumChannels <= 2 && dwChannelMask == 0)
1269 while (wavFileChannelLayout.
size() <
static_cast<int> (totalNumChannels))
1274 return wavFileChannelLayout;
1277 int64 bwavChunkStart = 0, bwavSize = 0;
1278 int64 dataChunkStart = 0, dataLength = 0;
1279 int bytesPerFrame = 0;
1280 bool isRF64 =
false;
1281 bool isSubformatOggVorbis =
false;
1283 AudioChannelSet channelLayout;
1286 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatReader)
1298 using namespace WavFileHelpers;
1328 bool write (
const int** data,
int numSamples)
override
1330 jassert (numSamples >= 0);
1331 jassert (data !=
nullptr && *data !=
nullptr);
1337 tempBlock.ensureSize (bytes,
false);
1345 default: jassertfalse;
break;
1348 if (! output->write (tempBlock.getData(), bytes))
1358 bytesWritten += bytes;
1365 auto lastWritePos = output->getPosition();
1368 if (output->setPosition (lastWritePos))
1378 MemoryBlock tempBlock, bwavChunk, axmlChunk, smplChunk, instChunk, cueChunk, listChunk, listInfoChunk, acidChunk, trckChunk;
1380 int64 headerPosition = 0;
1381 bool writeFailed =
false;
1385 if ((bytesWritten & 1) != 0)
1386 output->writeByte (0);
1388 using namespace WavFileHelpers;
1390 if (headerPosition != output->getPosition() && ! output->setPosition (headerPosition))
1400 auto channelMask = getChannelMaskFromChannelLayout (channelLayout);
1402 const bool isRF64 = (bytesWritten >= 0x100000000LL);
1403 const bool isWaveFmtEx = isRF64 || (channelMask != 0);
1405 int64 riffChunkSize = (int64) (4 + 8 + 40
1406 + 8 + audioDataSize + (audioDataSize & 1)
1407 + chunkSize (bwavChunk)
1408 + chunkSize (axmlChunk)
1409 + chunkSize (smplChunk)
1410 + chunkSize (instChunk)
1411 + chunkSize (cueChunk)
1412 + chunkSize (listChunk)
1413 + chunkSize (listInfoChunk)
1414 + chunkSize (acidChunk)
1415 + chunkSize (trckChunk)
1418 riffChunkSize += (riffChunkSize & 1);
1421 writeChunkHeader (chunkName (
"RF64"), -1);
1423 writeChunkHeader (chunkName (
"RIFF"), (
int) riffChunkSize);
1425 output->writeInt (chunkName (
"WAVE"));
1429 #if ! JUCE_WAV_DO_NOT_PAD_HEADER_SIZE
1440 writeChunkHeader (chunkName (
"JUNK"), 28 + (isWaveFmtEx? 0 : 24));
1441 output->writeRepeatedByte (0, 28 + (isWaveFmtEx? 0 : 24));
1446 #if JUCE_WAV_DO_NOT_PAD_HEADER_SIZE
1451 writeChunkHeader (chunkName (
"ds64"), 28);
1452 output->writeInt64 (riffChunkSize);
1453 output->writeInt64 ((int64) audioDataSize);
1454 output->writeRepeatedByte (0, 12);
1459 writeChunkHeader (chunkName (
"fmt "), 40);
1460 output->writeShort ((
short) (uint16) 0xfffe);
1464 writeChunkHeader (chunkName (
"fmt "), 16);
1471 output->writeInt ((
int) (bytesPerFrame *
sampleRate));
1472 output->writeShort ((
short) bytesPerFrame);
1477 output->writeShort (22);
1479 output->writeInt (channelMask);
1481 const ExtensibleWavSubFormat& subFormat =
bitsPerSample < 32 ? pcmFormat : IEEEFloatFormat;
1483 output->writeInt ((
int) subFormat.data1);
1484 output->writeShort ((
short) subFormat.data2);
1485 output->writeShort ((
short) subFormat.data3);
1486 output->write (subFormat.data4, sizeof (subFormat.data4));
1489 writeChunk (bwavChunk, chunkName (
"bext"));
1490 writeChunk (axmlChunk, chunkName (
"axml"));
1491 writeChunk (smplChunk, chunkName (
"smpl"));
1492 writeChunk (instChunk, chunkName (
"inst"), 7);
1493 writeChunk (cueChunk, chunkName (
"cue "));
1494 writeChunk (listChunk, chunkName (
"LIST"));
1495 writeChunk (listInfoChunk, chunkName (
"LIST"));
1496 writeChunk (acidChunk, chunkName (
"acid"));
1497 writeChunk (trckChunk, chunkName (
"Trkn"));
1499 writeChunkHeader (chunkName (
"data"), isRF64 ? -1 : (
int) (
lengthInSamples * bytesPerFrame));
1504 static size_t chunkSize (
const MemoryBlock& data) noexcept {
return data.getSize() > 0 ? (8 + data.getSize()) : 0; }
1506 void writeChunkHeader (
int chunkType,
int size)
const
1508 output->writeInt (chunkType);
1509 output->writeInt (size);
1512 void writeChunk (
const MemoryBlock& data,
int chunkType,
int size = 0)
const
1514 if (data.getSize() > 0)
1516 writeChunkHeader (chunkType, size != 0 ? size : (
int) data.getSize());
1521 static int getChannelMaskFromChannelLayout (
const AudioChannelSet& channelLayout)
1523 if (channelLayout.isDiscreteLayout())
1531 auto channels = channelLayout.getChannelTypes();
1532 auto wavChannelMask = 0;
1534 for (
auto channel : channels)
1536 int wavChannelBit =
static_cast<int> (channel) - 1;
1537 jassert (wavChannelBit >= 0 && wavChannelBit <= 31);
1539 wavChannelMask |= (1 << wavChannelBit);
1542 return wavChannelMask;
1545 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavAudioFormatWriter)
1554 reader.dataLength, reader.bytesPerFrame)
1558 bool readSamples (
int** destSamples,
int numDestChannels,
int startOffsetInDestBuffer,
1559 int64 startSampleInFile,
int numSamples)
override
1564 if (map ==
nullptr || ! mappedSection.contains (
Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
1571 destSamples, startOffsetInDestBuffer, numDestChannels,
1572 sampleToPointer (startSampleInFile), (
int)
numChannels, numSamples);
1576 void getSample (int64 sample,
float* result)
const noexcept
override
1580 if (map ==
nullptr || ! mappedSection.contains (sample))
1584 zeromem (result,
sizeof (
float) * (
size_t) num);
1588 auto dest = &result;
1589 auto source = sampleToPointer (sample);
1599 default: jassertfalse;
break;
1607 if (map ==
nullptr || numSamples <= 0 || ! mappedSection.contains (
Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
1609 jassert (numSamples <= 0);
1611 for (
int i = 0; i < numChannelsToRead; ++i)
1619 case 8: scanMinAndMax<AudioData::UInt8> (startSampleInFile, numSamples, results, numChannelsToRead);
break;
1620 case 16: scanMinAndMax<AudioData::Int16> (startSampleInFile, numSamples, results, numChannelsToRead);
break;
1621 case 24: scanMinAndMax<AudioData::Int24> (startSampleInFile, numSamples, results, numChannelsToRead);
break;
1622 case 32:
if (
usesFloatingPointData) scanMinAndMax<AudioData::Float32> (startSampleInFile, numSamples, results, numChannelsToRead);
1623 else scanMinAndMax<AudioData::Int32> (startSampleInFile, numSamples, results, numChannelsToRead);
1625 default: jassertfalse;
break;
1630 template <
typename SampleType>
1631 void scanMinAndMax (int64 startSampleInFile, int64 numSamples,
Range<float>* results,
int numChannelsToRead)
const noexcept
1633 for (
int i = 0; i < numChannelsToRead; ++i)
1634 results[i] = scanMinAndMaxInterleaved<SampleType, AudioData::LittleEndian> (i, startSampleInFile, numSamples);
1637 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryMappedWavReader)
1646 return { 8000, 11025, 12000, 16000, 22050, 32000, 44100,
1647 48000, 88200, 96000, 176400, 192000, 352800, 384000 };
1652 return { 8, 16, 24, 32 };
1667 for (
auto channel : channelTypes)
1678 #if JUCE_USE_OGGVORBIS
1679 if (r->isSubformatOggVorbis)
1686 if (r->sampleRate > 0 && r->numChannels > 0 && r->bytesPerFrame > 0 && r->bitsPerSample <= 32)
1689 if (! deleteStreamIfOpeningFails)
1714 unsigned int numChannels,
int bitsPerSample,
1717 return createWriterFor (out, sampleRate, WavFileHelpers::canonicalWavChannelSet (
static_cast<int> (numChannels)),
1718 bitsPerSample, metadataValues, qualityOptionIndex);
1730 (
unsigned int) bitsPerSample, metadataValues);
1735 namespace WavFileHelpers
1737 static bool slowCopyWavFileWithNewMetadata (
const File& file,
const StringPairArray& metadata)
1744 if (reader !=
nullptr)
1746 std::unique_ptr<OutputStream> outStream (tempFile.getFile().createOutputStream());
1748 if (outStream !=
nullptr)
1754 if (writer !=
nullptr)
1756 outStream.release();
1758 bool ok = writer->writeFromAudioReader (*reader, 0, -1);
1762 return ok && tempFile.overwriteTargetFileWithTemporary();
1773 using namespace WavFileHelpers;
1777 if (reader !=
nullptr)
1779 auto bwavPos = reader->bwavChunkStart;
1780 auto bwavSize = reader->bwavSize;
1785 auto chunk = BWAVChunk::createFrom (newMetadata);
1787 if (chunk.getSize() <= (
size_t) bwavSize)
1790 auto oldSize = wavFile.
getSize();
1803 jassert (wavFile.
getSize() == oldSize);
1809 return slowCopyWavFileWithNewMetadata (wavFile, newMetadata);
1815 struct WaveAudioFormatTests :
public UnitTest
1817 WaveAudioFormatTests() :
UnitTest (
"Wave audio format tests") {}
1819 void runTest()
override
1821 beginTest (
"Setting up metadata");
1827 numTestAudioBufferSamples,
1830 for (
int i = numElementsInArray (WavFileHelpers::ListInfoChunk::types); --i >= 0;)
1831 metadataValues.set (WavFileHelpers::ListInfoChunk::types[i],
1832 WavFileHelpers::ListInfoChunk::types[i]);
1834 if (metadataValues.size() > 0)
1835 metadataValues.set (
"MetaDataSource",
"WAV");
1837 metadataValues.addArray (createDefaultSMPLMetadata());
1840 MemoryBlock memoryBlock;
1843 beginTest (
"Creating a basic wave writer");
1845 std::unique_ptr<AudioFormatWriter> writer (format.createWriterFor (
new MemoryOutputStream (memoryBlock,
false),
1846 44100.0, numTestAudioBufferChannels,
1847 32, metadataValues, 0));
1848 expect (writer !=
nullptr);
1850 AudioBuffer<float> buffer (numTestAudioBufferChannels, numTestAudioBufferSamples);
1853 beginTest (
"Writing audio data to the basic wave writer");
1854 expect (writer->writeFromAudioSampleBuffer (buffer, 0, numTestAudioBufferSamples));
1858 beginTest (
"Creating a basic wave reader");
1860 std::unique_ptr<AudioFormatReader> reader (format.createReaderFor (
new MemoryInputStream (memoryBlock,
false),
false));
1861 expect (reader !=
nullptr);
1862 expect (reader->
metadataValues == metadataValues,
"Somehow, the metadata is different!");
1869 numTestAudioBufferChannels = 2,
1870 numTestAudioBufferSamples = 256
1873 StringPairArray createDefaultSMPLMetadata()
const
1877 m.set (
"Manufacturer",
"0");
1878 m.set (
"Product",
"0");
1879 m.set (
"SamplePeriod",
"0");
1880 m.set (
"MidiUnityNote",
"60");
1881 m.set (
"MidiPitchFraction",
"0");
1882 m.set (
"SmpteFormat",
"0");
1883 m.set (
"SmpteOffset",
"0");
1884 m.set (
"NumSampleLoops",
"0");
1885 m.set (
"SamplerData",
"0");
1890 JUCE_DECLARE_NON_COPYABLE (WaveAudioFormatTests)
1893 static const WaveAudioFormatTests waveAudioFormatTests;