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 = 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"))
1056 dataLength = length;
1058 dataChunkStart = input->getPosition();
1059 lengthInSamples = (bytesPerFrame > 0) ? (dataLength / bytesPerFrame) : 0;
1061 else if (chunkType == chunkName (
"bext"))
1063 bwavChunkStart = input->getPosition();
1067 bwav.
calloc (jmax ((
size_t) length + 1,
sizeof (BWAVChunk)), 1);
1068 input->read (bwav, (
int) length);
1069 bwav->copyTo (metadataValues, (
int) length);
1071 else if (chunkType == chunkName (
"smpl"))
1074 smpl.
calloc (jmax ((
size_t) length + 1,
sizeof (SMPLChunk)), 1);
1075 input->read (smpl, (
int) length);
1076 smpl->copyTo (metadataValues, (
int) length);
1078 else if (chunkType == chunkName (
"inst") || chunkType == chunkName (
"INST"))
1081 inst.
calloc (jmax ((
size_t) length + 1,
sizeof (InstChunk)), 1);
1082 input->read (inst, (
int) length);
1083 inst->copyTo (metadataValues);
1085 else if (chunkType == chunkName (
"cue "))
1088 cue.
calloc (jmax ((
size_t) length + 1,
sizeof (CueChunk)), 1);
1089 input->read (cue, (
int) length);
1090 cue->copyTo (metadataValues, (
int) length);
1092 else if (chunkType == chunkName (
"axml"))
1095 input->readIntoMemoryBlock (axml, (ssize_t) length);
1096 AXMLChunk::addToMetadata (metadataValues, axml.
toString());
1098 else if (chunkType == chunkName (
"LIST"))
1100 auto subChunkType = input->readInt();
1102 if (subChunkType == chunkName (
"info") || subChunkType == chunkName (
"INFO"))
1104 ListInfoChunk::addToMetadata (metadataValues, *input, chunkEnd);
1106 else if (subChunkType == chunkName (
"adtl"))
1108 while (input->getPosition() < chunkEnd)
1110 auto adtlChunkType = input->readInt();
1111 auto adtlLength = (uint32) input->readInt();
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++;
1123 auto identifier = (uint32) input->readInt();
1124 auto stringLength = (int) adtlLength - 4;
1127 input->readIntoMemoryBlock (textBlock, stringLength);
1129 metadataValues.set (prefix +
"Identifier",
String (identifier));
1130 metadataValues.set (prefix +
"Text", textBlock.
toString());
1132 else if (adtlChunkType == chunkName (
"ltxt"))
1134 auto prefix =
"CueRegion" +
String (cueRegionIndex++);
1135 auto identifier = (uint32) input->readInt();
1136 auto sampleLength = (uint32) input->readInt();
1137 auto purpose = (uint32) input->readInt();
1138 auto country = (uint16) input->readShort();
1139 auto language = (uint16) input->readShort();
1140 auto dialect = (uint16) input->readShort();
1141 auto codePage = (uint16) input->readShort();
1142 auto stringLength = adtlLength - 20;
1145 input->readIntoMemoryBlock (textBlock, (
int) stringLength);
1147 metadataValues.set (prefix +
"Identifier",
String (identifier));
1148 metadataValues.set (prefix +
"SampleLength",
String (sampleLength));
1149 metadataValues.set (prefix +
"Purpose",
String (purpose));
1150 metadataValues.set (prefix +
"Country",
String (country));
1151 metadataValues.set (prefix +
"Language",
String (language));
1152 metadataValues.set (prefix +
"Dialect",
String (dialect));
1153 metadataValues.set (prefix +
"CodePage",
String (codePage));
1154 metadataValues.set (prefix +
"Text", textBlock.
toString());
1157 input->setPosition (adtlChunkEnd);
1161 else if (chunkType == chunkName (
"acid"))
1163 AcidChunk (*input, length).addToMetadata (metadataValues);
1165 else if (chunkType == chunkName (
"Trkn"))
1168 input->readIntoMemoryBlock (tracktion, (ssize_t) length);
1169 metadataValues.set (WavAudioFormat::tracktionLoopInfo, tracktion.
toString());
1171 else if (chunkEnd <= input->getPosition())
1176 input->setPosition (chunkEnd);
1180 if (cueLabelIndex > 0) metadataValues.set (
"NumCueLabels",
String (cueLabelIndex));
1181 if (cueNoteIndex > 0) metadataValues.set (
"NumCueNotes",
String (cueNoteIndex));
1182 if (cueRegionIndex > 0) metadataValues.set (
"NumCueRegions",
String (cueRegionIndex));
1183 if (metadataValues.size() > 0) metadataValues.set (
"MetaDataSource",
"WAV");
1187 bool readSamples (
int** destSamples,
int numDestChannels,
int startOffsetInDestBuffer,
1188 int64 startSampleInFile,
int numSamples)
override 1190 clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
1191 startSampleInFile, numSamples, lengthInSamples);
1193 if (numSamples <= 0)
1196 input->setPosition (dataChunkStart + startSampleInFile * bytesPerFrame);
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));
1212 copySampleData (bitsPerSample, usesFloatingPointData,
1213 destSamples, startOffsetInDestBuffer, numDestChannels,
1214 tempBuffer, (
int) numChannels, numThisTime);
1216 startOffsetInDestBuffer += numThisTime;
1217 numSamples -= numThisTime;
1223 static void copySampleData (
unsigned int bitsPerSample,
const bool usesFloatingPointData,
1224 int*
const* destSamples,
int startOffsetInDestBuffer,
int numDestChannels,
1225 const void* sourceData,
int numChannels,
int numSamples) noexcept
1227 switch (bitsPerSample)
1235 default: jassertfalse;
break;
1242 if (channelLayout.size() ==
static_cast<int> (numChannels))
1243 return channelLayout;
1245 return WavFileHelpers::canonicalWavChannelSet (static_cast<int> (numChannels));
1248 static AudioChannelSet getChannelLayoutFromMask (
int dwChannelMask,
size_t totalNumChannels)
1256 wavFileChannelLayout.
addChannel (static_cast<AudioChannelSet::ChannelType> (bit + 1));
1259 if (wavFileChannelLayout.
size() !=
static_cast<int> (totalNumChannels))
1263 if (totalNumChannels <= 2 && dwChannelMask == 0)
1269 while (wavFileChannelLayout.
size() <
static_cast<int> (totalNumChannels))
1270 wavFileChannelLayout.
addChannel (static_cast<AudioChannelSet::ChannelType> (discreteSpeaker++));
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;
1298 using namespace WavFileHelpers;
1300 if (metadataValues.
size() > 0)
1305 jassert (metadataValues.
getValue (
"MetaDataSource",
"None") !=
"AIFF");
1307 bwavChunk = BWAVChunk::createFrom (metadataValues);
1308 axmlChunk = AXMLChunk::createFrom (metadataValues);
1309 smplChunk = SMPLChunk::createFrom (metadataValues);
1310 instChunk = InstChunk::createFrom (metadataValues);
1311 cueChunk = CueChunk ::createFrom (metadataValues);
1312 listChunk = ListChunk::createFrom (metadataValues);
1313 listInfoChunk = ListInfoChunk::createFrom (metadataValues);
1314 acidChunk = AcidChunk::createFrom (metadataValues);
1315 trckChunk = TracktionChunk::createFrom (metadataValues);
1328 bool write (
const int** data,
int numSamples)
override 1330 jassert (numSamples >= 0);
1331 jassert (data !=
nullptr && *data !=
nullptr);
1336 auto bytes = numChannels * (size_t) numSamples * bitsPerSample / 8;
1337 tempBlock.ensureSize (bytes,
false);
1339 switch (bitsPerSample)
1345 default: jassertfalse;
break;
1348 if (! output->write (tempBlock.getData(), bytes))
1358 bytesWritten += bytes;
1359 lengthInSamples += (uint64) numSamples;
1365 auto lastWritePos = output->getPosition();
1368 if (output->setPosition (lastWritePos))
1378 MemoryBlock tempBlock, bwavChunk, axmlChunk, smplChunk, instChunk, cueChunk, listChunk, listInfoChunk, acidChunk, trckChunk;
1379 uint64 lengthInSamples = 0, bytesWritten = 0;
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))
1398 const size_t bytesPerFrame = numChannels * bitsPerSample / 8;
1399 uint64 audioDataSize = bytesPerFrame * lengthInSamples;
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);
1465 output->writeShort (bitsPerSample < 32 ? (
short) 1
1469 output->writeShort ((
short) numChannels);
1470 output->writeInt ((
int) sampleRate);
1471 output->writeInt ((
int) (bytesPerFrame * sampleRate));
1472 output->writeShort ((
short) bytesPerFrame);
1473 output->writeShort ((
short) bitsPerSample);
1477 output->writeShort (22);
1478 output->writeShort ((
short) bitsPerSample);
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));
1501 usesFloatingPointData = (bitsPerSample == 32);
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 1516 writeChunkHeader (chunkType, size != 0 ? size : (
int) data.
getSize());
1521 static int getChannelMaskFromChannelLayout (
const AudioChannelSet& channelLayout)
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;
1554 reader.dataLength, reader.bytesPerFrame)
1558 bool readSamples (
int** destSamples,
int numDestChannels,
int startOffsetInDestBuffer,
1559 int64 startSampleInFile,
int numSamples)
override 1561 clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
1562 startSampleInFile, numSamples, lengthInSamples);
1564 if (map ==
nullptr || ! mappedSection.contains (
Range<int64> (startSampleInFile, startSampleInFile + numSamples)))
1570 WavAudioFormatReader::copySampleData (bitsPerSample, usesFloatingPointData,
1571 destSamples, startOffsetInDestBuffer, numDestChannels,
1572 sampleToPointer (startSampleInFile), (
int) numChannels, numSamples);
1576 void getSample (int64 sample,
float* result)
const noexcept
override 1578 auto num = (int) numChannels;
1580 if (map ==
nullptr || ! mappedSection.contains (sample))
1584 zeromem (result,
sizeof (
float) * (
size_t) num);
1588 auto dest = &result;
1589 auto source = sampleToPointer (sample);
1591 switch (bitsPerSample)
1599 default: jassertfalse;
break;
1605 numSamples = jmin (numSamples, lengthInSamples - startSampleInFile);
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)
1617 switch (bitsPerSample)
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);
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)
1748 if (outStream !=
nullptr)
1750 std::unique_ptr<AudioFormatWriter> writer (wav.
createWriterFor (outStream.get(), reader->sampleRate,
1751 reader->numChannels, (int) reader->bitsPerSample,
1754 if (writer !=
nullptr)
1756 outStream.release();
1758 bool ok = writer->writeFromAudioReader (*reader, 0, -1);
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());
1843 beginTest (
"Creating a basic wave writer");
1846 44100.0, numTestAudioBufferChannels,
1847 32, metadataValues, 0));
1848 expect (writer !=
nullptr);
1853 beginTest (
"Writing audio data to the basic wave writer");
1854 expect (writer->writeFromAudioSampleBuffer (buffer, 0, numTestAudioBufferSamples));
1858 beginTest (
"Creating a basic wave reader");
1861 expect (reader !=
nullptr);
1862 expect (reader->metadataValues == metadataValues,
"Somehow, the metadata is different!");
1869 numTestAudioBufferChannels = 2,
1870 numTestAudioBufferSamples = 256
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;
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.
void * getData() const noexcept
Returns a void pointer to the data.
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.
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.