This is a bug notice and its fix. Hopefully this will make it in future versions of juce.
I noticed that there appears to be a lack of support for WAVE_FORMAT_EXTENSIBLE. By this I mean that we have a sample app that converts one WAV to another WAV changing various parts of the format (# of channels, sample rate, and bits per sample). However, when I use a WAV that uses an audio format code of WAVE_FORMAT_EXTENSIBLE the conversion finishes but with a duration of 00:00:00. I looked into this and discovered that it appears only WAVE_FORMAT_PCM (1) and WAVE_FORMAT_IEEE_FLOAT (3) are supported. However, with only a little code change, WAVE_FORMAT_EXTENSIBLE can easily be supported so long as its sub format code is either PCM or IEEE_FLOAT.
The support for only PCM and IEEE_FLOAT can be seen in the WavAudioFormatReader constructor. When reading the "fmt " WAV chunk there is code that checks the audio format in this manner:
if (format == 3)
usesFloatingPointData = true;
else if (format != 1)
bytesPerFrame = 0;
Note again that 3 refers to WAVE_FORMAT_IEEE_FLOAT and 1 refers to WAVE_FORMAT_PCM (both supported). When the format is WAVE_FORMAT_EXTENSIBLE (0xFFFE or 65534) then one should refer to the sub format for how to read the WAV. I got the code working the way I want, but I’d love it if this became standard part of juce for future updates we may grab and for other users.
There were two important changes.
First change:
I made the format variable a uint16. This is important since WAVE_FORMAT_EXTENSIBLE will be read as -2 not 65534 if the format is a signed int16 a.k.a. short. I also made it not a const since later we’ll assign it to the value of the sub format if the format is WAVE_FORMAT_EXTENSIBLE
uint16 format = (uint16) input->readShort();
Second change:
I added this conditional code after reading in all the other data in the "fmt " chunk:
if (format == 65534) // if format is WAV_FORMAT_PCM_EXTENSIBLE
{
// Skip past the following:
// ---------------------------------
// 2 bytes blockAlign
// 2 bytes bitsPerSample
// 2 bytes cbSize
// 2 bytes wValidBitsPerSample
// 4 bytes dwChannelMask
input->skipNextBytes(12);
// Next is a 16 byte GUID. The first two bytes of the GUID
// make up the sub format.
const uint16 subFormat = (uint16) input->readShort();
format = subFormat;
}
Here is the entire revised constructor for WavAudioFormatReader (although only the "fmt " chunk data part is interesting).
WavAudioFormatReader (InputStream* const in)
: AudioFormatReader (in, TRANS (wavFormatName)),
dataLength (0),
bwavChunkStart (0),
bwavSize (0)
{
if (input->readInt() == chunkName ("RIFF"))
{
const uint32 len = (uint32) input->readInt();
const int64 end = input->getPosition() + len;
bool hasGotType = false;
bool hasGotData = false;
if (input->readInt() == chunkName ("WAVE"))
{
while (input->getPosition() < end
&& ! input->isExhausted())
{
const int chunkType = input->readInt();
uint32 length = (uint32) input->readInt();
const int64 chunkEnd = input->getPosition() + length + (length & 1);
if (chunkType == chunkName ("fmt "))
{
// read the format chunk
uint16 format = (uint16) input->readShort();
const short numChans = input->readShort();
sampleRate = input->readInt();
const int bytesPerSec = input->readInt();
numChannels = numChans;
bytesPerFrame = bytesPerSec / (int)sampleRate;
bitsPerSample = 8 * bytesPerFrame / numChans;
if (format == 65534) // if format is WAV_FORMAT_PCM_EXTENSIBLE
{
// Skip past the following:
// ---------------------------------
// 2 bytes blockAlign
// 2 bytes bitsPerSample
// 2 bytes cbSize
// 2 bytes wValidBitsPerSample
// 4 bytes dwChannelMask
input->skipNextBytes(12);
// Next is a 16 byte GUID. The first two bytes of the GUID
// make up the sub format.
const uint16 subFormat = (uint16) input->readShort();
format = subFormat;
}
if (format == 3) // if format is WAVE_FORMAT_IEEE_FLOAT
usesFloatingPointData = true;
else if (format != 1) // if format is not WAVE_FORMAT_PCM
bytesPerFrame = 0;
hasGotType = true;
}
else if (chunkType == chunkName ("data"))
{
// get the data chunk's position
dataLength = length;
dataChunkStart = input->getPosition();
lengthInSamples = (bytesPerFrame > 0) ? (dataLength / bytesPerFrame) : 0;
hasGotData = true;
}
else if (chunkType == chunkName ("bext"))
{
bwavChunkStart = input->getPosition();
bwavSize = length;
// Broadcast-wav extension chunk..
HeapBlock <BWAVChunk> bwav;
bwav.calloc (jmax ((size_t) length + 1, sizeof (BWAVChunk)), 1);
input->read (bwav, length);
bwav->copyTo (metadataValues);
}
else if (chunkType == chunkName ("smpl"))
{
HeapBlock <SMPLChunk> smpl;
smpl.calloc (jmax ((size_t) length + 1, sizeof (SMPLChunk)), 1);
input->read (smpl, length);
smpl->copyTo (metadataValues, length);
}
else if (chunkEnd <= input->getPosition())
{
break;
}
input->setPosition (chunkEnd);
}
}
}
}