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,
200 MemoryBlock data (roundUpSize (
sizeof (
BWAVChunk) + values[WavAudioFormat::bwavCodingHistory].getNumBytesAsUTF8()));
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);
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);
456 data.
setSize (roundUpSize (
sizeof (
CueChunk) + (
size_t) (numCues - 1) *
sizeof (
Cue)),
true);
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;
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>";
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;
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)
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;
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);
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)
1352 default: jassertfalse;
break;
1355 if (! output->write (tempBlock.getData(), bytes))
1365 bytesWritten += bytes;
1366 lengthInSamples += (uint64) numSamples;
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;
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)
1606 default: jassertfalse;
break;
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);
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)
1687 #if JUCE_USE_OGGVORBIS 1688 if (r->isSubformatOggVorbis)
1695 if (r->sampleRate > 0 && r->numChannels > 0 && r->bytesPerFrame > 0 && r->bitsPerSample <= 32)
1698 if (! deleteStreamIfOpeningFails)
1723 unsigned int numChannels,
int bitsPerSample,
1726 return createWriterFor (out, sampleRate, WavFileHelpers::canonicalWavChannelSet (static_cast<int> (numChannels)),
1727 bitsPerSample, metadataValues, qualityOptionIndex);
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;
Non-typed individual channels are indexed upwards from this value.
size_t getSize() const noexcept
Returns the block's current allocated size, in bytes.
static Type swapIfBigEndian(Type value) noexcept
Swaps the byte order of a signed or unsigned integer if the CPU is big-endian.
Represents a set of audio channel types.
static juce_wchar toUpperCase(juce_wchar character) noexcept
Converts a character to upper-case.
Manages a temporary file, which will be deleted when this object is deleted.
bool overwriteTargetFileWithTemporary() const
Tries to move the temporary file to overwrite the target file that was specified in the constructor...
const File & getFile() const noexcept
Returns the temporary file.
virtual bool writeByte(char byte)
Writes a single byte to the stream.
static AudioChannelSet JUCE_CALLTYPE create7point1SDDS()
Creates a set for a 7.1 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre, LFE).
bool setPosition(int64) override
Tries to move the stream's output position.
const StringArray & getAllKeys() const noexcept
Returns a list of all keys in the array.
void add(const ElementType &newElement)
Appends a new element at the end of the array.
void readMaxLevels(int64 startSampleInFile, int64 numSamples, Range< float > *results, int numChannelsToRead) override
Finds the highest and lowest sample levels from a section of the audio stream.
int size() const noexcept
Returns the number of channels in the set.
MemoryBlock getMemoryBlock() const
Returns a copy of the stream's data as a memory block.
Very simple container class to hold a pointer to some data on the heap.
String formatted(const String &format) const
Converts this date/time to a string with a user-defined format.
String getValue(StringRef, const String &defaultReturnValue) const
Finds the value corresponding to a key string.
Array< ChannelType > getChannelTypes() const
Returns an array of all the types in this channel set.
static String createStringFromData(const void *data, int size)
Creates a string from data in an unknown format.
void addChannel(ChannelType newChannelType)
Adds a channel to the set.
FileInputStream * createInputStream() const
Creates a stream to read from this file.
static Time JUCE_CALLTYPE getCurrentTime() noexcept
Returns a Time object that is set to the current system time.
int size() const noexcept
Returns the number of strings in the array.
An arbitrarily large integer class.
void calloc(SizeType newNumElements, const size_t elementSize=sizeof(ElementType))
Allocates a specified amount of memory and clears it.
void getSample(int64 sample, float *result) const noexcept override
Returns the samples for all channels at a given sample position.
This is a base class for classes that perform a unit test.
static AudioChannelSet JUCE_CALLTYPE mono()
Creates a one-channel mono set (centre).
String toString() const
Attempts to parse the contents of the block as a zero-terminated UTF8 string.
static AudioChannelSet JUCE_CALLTYPE create7point0SDDS()
Creates a set for a SDDS 7.0 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre).
AcidChunk(InputStream &input, size_t length)
Reads an acid RIFF chunk from a stream positioned just after the size byte.
bool isDiscreteLayout() const noexcept
Returns if this is a channel layout made-up of discrete channels.
int64 getSize() const
Returns the size of the file in bytes.
void * getData() noexcept
Returns a void pointer to the data.
bool writeRepeatedByte(uint8 byte, size_t numTimesToRepeat) override
Writes a byte to the output stream a given number of times.
bool write(const void *, size_t) override
Writes a block of data to the stream.
static AudioChannelSet JUCE_CALLTYPE create5point1()
Creates a set for a 5.1 surround setup (left, right, centre, leftSurround, rightSurround, LFE).
static JUCE_CONSTEXPR uint16 swap(uint16 value) noexcept
Swaps the upper and lower bytes of a 16-bit integer.
static AudioChannelSet JUCE_CALLTYPE create5point0()
Creates a set for a 5.0 surround setup (left, right, centre, leftSurround, rightSurround).
virtual bool writeShort(short value)
Writes a 16-bit integer to the stream in a little-endian byte order.
static JUCE_CONSTEXPR uint32 littleEndianInt(const void *bytes) noexcept
Turns 4 bytes into a little-endian integer.
Represents a local file or directory.
The base class for streams that write data to some kind of destination.
Holds a resizable array of primitive or copy-by-value objects.
FileOutputStream * createOutputStream(size_t bufferSize=0x8000) const
Creates a stream to write to this file.
static AudioChannelSet JUCE_CALLTYPE canonicalChannelSet(int numChannels)
Create a canonical channel set for a given number of channels.
size_t getDataSize() const noexcept
Returns the number of bytes of data that have been written to the stream.
void set(const String &key, const String &value)
Adds or amends a key/value pair.
bool readSamples(int **destSamples, int numDestChannels, int startOffsetInDestBuffer, int64 startSampleInFile, int numSamples) override
Subclasses must implement this method to perform the low-level read operation.
static AudioChannelSet JUCE_CALLTYPE createLCR()
Creates a set containing an LCR set (left, right, centre).
static AudioChannelSet JUCE_CALLTYPE stereo()
Creates a set containing a stereo set (left, right).
An output stream that writes into a local file.
bool containsKey(StringRef key) const noexcept
Returns true if the given key exists.
bool openedOk() const noexcept
Returns true if the stream opened without problems.
int getIntValue() const noexcept
Reads the value of the string as a decimal number (up to 32 bits in size).
static AudioChannelSet JUCE_CALLTYPE discreteChannels(int numChannels)
Creates a set of untyped discrete channels.
virtual bool writeInt(int value)
Writes a 32-bit integer to the stream in a little-endian byte order.
A container for holding a set of strings which are keyed by another string.
void addArray(const StringPairArray &other)
Adds the items from another array to this one.
static AudioChannelSet JUCE_CALLTYPE quadraphonic()
Creates a set for quadraphonic surround setup (left, right, leftSurround, rightSurround) ...
Writes data to an internal memory buffer, which grows as required.
virtual bool writeString(const String &text)
Stores a string in the stream in a binary format.
int findNextSetBit(int startIndex) const noexcept
Looks for the index of the next set bit after a given starting point.
size_t getNumBytesAsUTF8() const noexcept
Returns the number of bytes required to represent this string as UTF8.
A class to hold a resizable block of raw data.
virtual int64 getPosition()=0
Returns the stream's current position.
static String fromUTF8(const char *utf8buffer, int bufferSizeBytes=-1)
Creates a String from a UTF-8 encoded buffer.
Holds an absolute date and time.
void fillWith(uint8 valueToUse) noexcept
Fills the entire memory block with a repeated byte value.
bool contains(ParameterType elementToLookFor) const
Returns true if the array contains at least one occurrence of an object.
void setSize(const size_t newSize, bool initialiseNewSpaceToZero=false)
Resizes the memory block.
void clear() noexcept
Clears all the samples in all channels.