1603 lines
38 KiB
C++
1603 lines
38 KiB
C++
/*
|
|
Audio File Library
|
|
Copyright (C) 1998-2000, 2003-2004, 2010-2013, Michael Pruett <michael@68k.org>
|
|
Copyright (C) 2000-2002, Silicon Graphics, Inc.
|
|
Copyright (C) 2002-2003, Davy Durham
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, write to the
|
|
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
/*
|
|
WAVE.cpp
|
|
|
|
This file contains code for reading and writing RIFF WAVE format
|
|
sound files.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "WAVE.h"
|
|
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "File.h"
|
|
#include "Instrument.h"
|
|
#include "Marker.h"
|
|
#include "Setup.h"
|
|
#include "Tag.h"
|
|
#include "Track.h"
|
|
#include "UUID.h"
|
|
#include "byteorder.h"
|
|
#include "util.h"
|
|
|
|
/* These constants are from RFC 2361. */
|
|
enum
|
|
{
|
|
WAVE_FORMAT_UNKNOWN = 0x0000, /* Microsoft Unknown Wave Format */
|
|
WAVE_FORMAT_PCM = 0x0001, /* Microsoft PCM Format */
|
|
WAVE_FORMAT_ADPCM = 0x0002, /* Microsoft ADPCM Format */
|
|
WAVE_FORMAT_IEEE_FLOAT = 0x0003, /* IEEE Float */
|
|
WAVE_FORMAT_VSELP = 0x0004, /* Compaq Computer's VSELP */
|
|
WAVE_FORMAT_IBM_CVSD = 0x0005, /* IBM CVSD */
|
|
WAVE_FORMAT_ALAW = 0x0006, /* Microsoft ALAW */
|
|
WAVE_FORMAT_MULAW = 0x0007, /* Microsoft MULAW */
|
|
WAVE_FORMAT_OKI_ADPCM = 0x0010, /* OKI ADPCM */
|
|
WAVE_FORMAT_DVI_ADPCM = 0x0011, /* Intel's DVI ADPCM */
|
|
WAVE_FORMAT_MEDIASPACE_ADPCM = 0x0012, /* Videologic's MediaSpace ADPCM */
|
|
WAVE_FORMAT_SIERRA_ADPCM = 0x0013, /* Sierra ADPCM */
|
|
WAVE_FORMAT_G723_ADPCM = 0x0014, /* G.723 ADPCM */
|
|
WAVE_FORMAT_DIGISTD = 0x0015, /* DSP Solutions' DIGISTD */
|
|
WAVE_FORMAT_DIGIFIX = 0x0016, /* DSP Solutions' DIGIFIX */
|
|
WAVE_FORMAT_DIALOGIC_OKI_ADPCM = 0x0017, /* Dialogic OKI ADPCM */
|
|
WAVE_FORMAT_MEDIAVISION_ADPCM = 0x0018, /* MediaVision ADPCM */
|
|
WAVE_FORMAT_CU_CODEC = 0x0019, /* HP CU */
|
|
WAVE_FORMAT_YAMAHA_ADPCM = 0x0020, /* Yamaha ADPCM */
|
|
WAVE_FORMAT_SONARC = 0x0021, /* Speech Compression's Sonarc */
|
|
WAVE_FORMAT_DSP_TRUESPEECH = 0x0022, /* DSP Group's True Speech */
|
|
WAVE_FORMAT_ECHOSC1 = 0x0023, /* Echo Speech's EchoSC1 */
|
|
WAVE_FORMAT_AUDIOFILE_AF36 = 0x0024, /* Audiofile AF36 */
|
|
WAVE_FORMAT_APTX = 0x0025, /* APTX */
|
|
WAVE_FORMAT_DOLBY_AC2 = 0x0030, /* Dolby AC2 */
|
|
WAVE_FORMAT_GSM610 = 0x0031, /* GSM610 */
|
|
WAVE_FORMAT_MSNAUDIO = 0x0032, /* MSNAudio */
|
|
WAVE_FORMAT_ANTEX_ADPCME = 0x0033, /* Antex ADPCME */
|
|
|
|
WAVE_FORMAT_MPEG = 0x0050, /* MPEG */
|
|
WAVE_FORMAT_MPEGLAYER3 = 0x0055, /* MPEG layer 3 */
|
|
WAVE_FORMAT_LUCENT_G723 = 0x0059, /* Lucent G.723 */
|
|
WAVE_FORMAT_G726_ADPCM = 0x0064, /* G.726 ADPCM */
|
|
WAVE_FORMAT_G722_ADPCM = 0x0065, /* G.722 ADPCM */
|
|
|
|
IBM_FORMAT_MULAW = 0x0101,
|
|
IBM_FORMAT_ALAW = 0x0102,
|
|
IBM_FORMAT_ADPCM = 0x0103,
|
|
|
|
WAVE_FORMAT_CREATIVE_ADPCM = 0x0200,
|
|
|
|
WAVE_FORMAT_EXTENSIBLE = 0xfffe
|
|
};
|
|
|
|
const int _af_wave_compression_types[_AF_WAVE_NUM_COMPTYPES] =
|
|
{
|
|
AF_COMPRESSION_G711_ULAW,
|
|
AF_COMPRESSION_G711_ALAW,
|
|
AF_COMPRESSION_IMA,
|
|
AF_COMPRESSION_MS_ADPCM
|
|
};
|
|
|
|
const InstParamInfo _af_wave_inst_params[_AF_WAVE_NUM_INSTPARAMS] =
|
|
{
|
|
{ AF_INST_MIDI_BASENOTE, AU_PVTYPE_LONG, "MIDI base note", {60} },
|
|
{ AF_INST_NUMCENTS_DETUNE, AU_PVTYPE_LONG, "Detune in cents", {0} },
|
|
{ AF_INST_MIDI_LOVELOCITY, AU_PVTYPE_LONG, "Low velocity", {1} },
|
|
{ AF_INST_MIDI_HIVELOCITY, AU_PVTYPE_LONG, "High velocity", {127} },
|
|
{ AF_INST_MIDI_LONOTE, AU_PVTYPE_LONG, "Low note", {0} },
|
|
{ AF_INST_MIDI_HINOTE, AU_PVTYPE_LONG, "High note", {127} },
|
|
{ AF_INST_NUMDBS_GAIN, AU_PVTYPE_LONG, "Gain in dB", {0} }
|
|
};
|
|
|
|
static const _AFfilesetup waveDefaultFileSetup =
|
|
{
|
|
_AF_VALID_FILESETUP, /* valid */
|
|
AF_FILE_WAVE, /* fileFormat */
|
|
true, /* trackSet */
|
|
true, /* instrumentSet */
|
|
true, /* miscellaneousSet */
|
|
1, /* trackCount */
|
|
NULL, /* tracks */
|
|
0, /* instrumentCount */
|
|
NULL, /* instruments */
|
|
0, /* miscellaneousCount */
|
|
NULL /* miscellaneous */
|
|
};
|
|
|
|
static const UUID _af_wave_guid_pcm =
|
|
{{
|
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
|
|
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
|
|
}};
|
|
static const UUID _af_wave_guid_ieee_float =
|
|
{{
|
|
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
|
|
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
|
|
}};
|
|
static const UUID _af_wave_guid_ulaw =
|
|
{{
|
|
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
|
|
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
|
|
}};
|
|
static const UUID _af_wave_guid_alaw =
|
|
{{
|
|
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
|
|
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
|
|
}};
|
|
|
|
WAVEFile::WAVEFile()
|
|
{
|
|
setFormatByteOrder(AF_BYTEORDER_LITTLEENDIAN);
|
|
|
|
m_factOffset = 0;
|
|
m_miscellaneousOffset = 0;
|
|
m_markOffset = 0;
|
|
m_dataSizeOffset = 0;
|
|
|
|
m_msadpcmNumCoefficients = 0;
|
|
}
|
|
|
|
status WAVEFile::parseFrameCount(const Tag &id, uint32_t size)
|
|
{
|
|
Track *track = getTrack();
|
|
|
|
uint32_t totalFrames;
|
|
readU32(&totalFrames);
|
|
|
|
track->totalfframes = totalFrames;
|
|
|
|
return AF_SUCCEED;
|
|
}
|
|
|
|
status WAVEFile::parseFormat(const Tag &id, uint32_t size)
|
|
{
|
|
Track *track = getTrack();
|
|
|
|
uint16_t formatTag;
|
|
readU16(&formatTag);
|
|
uint16_t channelCount;
|
|
readU16(&channelCount);
|
|
uint32_t sampleRate;
|
|
readU32(&sampleRate);
|
|
uint32_t averageBytesPerSecond;
|
|
readU32(&averageBytesPerSecond);
|
|
uint16_t blockAlign;
|
|
readU16(&blockAlign);
|
|
|
|
if (!channelCount)
|
|
{
|
|
_af_error(AF_BAD_CHANNELS, "invalid file with 0 channels");
|
|
return AF_FAIL;
|
|
}
|
|
|
|
track->f.channelCount = channelCount;
|
|
track->f.sampleRate = sampleRate;
|
|
track->f.byteOrder = AF_BYTEORDER_LITTLEENDIAN;
|
|
|
|
/* Default to uncompressed audio data. */
|
|
track->f.compressionType = AF_COMPRESSION_NONE;
|
|
track->f.framesPerPacket = 1;
|
|
|
|
switch (formatTag)
|
|
{
|
|
case WAVE_FORMAT_PCM:
|
|
{
|
|
uint16_t bitsPerSample;
|
|
readU16(&bitsPerSample);
|
|
|
|
track->f.sampleWidth = bitsPerSample;
|
|
|
|
if (bitsPerSample == 0 || bitsPerSample > 32)
|
|
{
|
|
_af_error(AF_BAD_WIDTH,
|
|
"bad sample width of %d bits",
|
|
bitsPerSample);
|
|
return AF_FAIL;
|
|
}
|
|
|
|
if (bitsPerSample <= 8)
|
|
track->f.sampleFormat = AF_SAMPFMT_UNSIGNED;
|
|
else
|
|
track->f.sampleFormat = AF_SAMPFMT_TWOSCOMP;
|
|
}
|
|
break;
|
|
|
|
case WAVE_FORMAT_MULAW:
|
|
case IBM_FORMAT_MULAW:
|
|
track->f.sampleWidth = 16;
|
|
track->f.sampleFormat = AF_SAMPFMT_TWOSCOMP;
|
|
track->f.byteOrder = _AF_BYTEORDER_NATIVE;
|
|
track->f.compressionType = AF_COMPRESSION_G711_ULAW;
|
|
track->f.bytesPerPacket = track->f.channelCount;
|
|
break;
|
|
|
|
case WAVE_FORMAT_ALAW:
|
|
case IBM_FORMAT_ALAW:
|
|
track->f.sampleWidth = 16;
|
|
track->f.sampleFormat = AF_SAMPFMT_TWOSCOMP;
|
|
track->f.byteOrder = _AF_BYTEORDER_NATIVE;
|
|
track->f.compressionType = AF_COMPRESSION_G711_ALAW;
|
|
track->f.bytesPerPacket = track->f.channelCount;
|
|
break;
|
|
|
|
case WAVE_FORMAT_IEEE_FLOAT:
|
|
{
|
|
uint16_t bitsPerSample;
|
|
readU16(&bitsPerSample);
|
|
|
|
if (bitsPerSample == 64)
|
|
{
|
|
track->f.sampleWidth = 64;
|
|
track->f.sampleFormat = AF_SAMPFMT_DOUBLE;
|
|
}
|
|
else
|
|
{
|
|
track->f.sampleWidth = 32;
|
|
track->f.sampleFormat = AF_SAMPFMT_FLOAT;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WAVE_FORMAT_ADPCM:
|
|
{
|
|
uint16_t bitsPerSample, extraByteCount,
|
|
samplesPerBlock, numCoefficients;
|
|
|
|
if (track->f.channelCount != 1 &&
|
|
track->f.channelCount != 2)
|
|
{
|
|
_af_error(AF_BAD_CHANNELS,
|
|
"WAVE file with MS ADPCM compression "
|
|
"must have 1 or 2 channels");
|
|
}
|
|
|
|
readU16(&bitsPerSample);
|
|
readU16(&extraByteCount);
|
|
readU16(&samplesPerBlock);
|
|
readU16(&numCoefficients);
|
|
|
|
/* numCoefficients should be at least 7. */
|
|
assert(numCoefficients >= 7 && numCoefficients <= 255);
|
|
|
|
m_msadpcmNumCoefficients = numCoefficients;
|
|
|
|
for (int i=0; i<m_msadpcmNumCoefficients; i++)
|
|
{
|
|
readS16(&m_msadpcmCoefficients[i][0]);
|
|
readS16(&m_msadpcmCoefficients[i][1]);
|
|
}
|
|
|
|
track->f.sampleWidth = 16;
|
|
track->f.sampleFormat = AF_SAMPFMT_TWOSCOMP;
|
|
track->f.compressionType = AF_COMPRESSION_MS_ADPCM;
|
|
track->f.byteOrder = _AF_BYTEORDER_NATIVE;
|
|
|
|
track->f.framesPerPacket = samplesPerBlock;
|
|
track->f.bytesPerPacket = blockAlign;
|
|
|
|
// Create the parameter list.
|
|
AUpvlist pv = AUpvnew(2);
|
|
AUpvsetparam(pv, 0, _AF_MS_ADPCM_NUM_COEFFICIENTS);
|
|
AUpvsetvaltype(pv, 0, AU_PVTYPE_LONG);
|
|
long l = m_msadpcmNumCoefficients;
|
|
AUpvsetval(pv, 0, &l);
|
|
|
|
AUpvsetparam(pv, 1, _AF_MS_ADPCM_COEFFICIENTS);
|
|
AUpvsetvaltype(pv, 1, AU_PVTYPE_PTR);
|
|
void *v = m_msadpcmCoefficients;
|
|
AUpvsetval(pv, 1, &v);
|
|
|
|
track->f.compressionParams = pv;
|
|
}
|
|
break;
|
|
|
|
case WAVE_FORMAT_DVI_ADPCM:
|
|
{
|
|
uint16_t bitsPerSample, extraByteCount, samplesPerBlock;
|
|
|
|
readU16(&bitsPerSample);
|
|
readU16(&extraByteCount);
|
|
readU16(&samplesPerBlock);
|
|
|
|
if (bitsPerSample != 4)
|
|
{
|
|
_af_error(AF_BAD_NOT_IMPLEMENTED,
|
|
"IMA ADPCM compression supports only 4 bits per sample");
|
|
}
|
|
|
|
int bytesPerBlock = (samplesPerBlock + 14) / 8 * 4 * channelCount;
|
|
if (bytesPerBlock > blockAlign || (samplesPerBlock % 8) != 1)
|
|
{
|
|
_af_error(AF_BAD_CODEC_CONFIG,
|
|
"Invalid samples per block for IMA ADPCM compression");
|
|
}
|
|
|
|
track->f.sampleWidth = 16;
|
|
track->f.sampleFormat = AF_SAMPFMT_TWOSCOMP;
|
|
track->f.compressionType = AF_COMPRESSION_IMA;
|
|
track->f.byteOrder = _AF_BYTEORDER_NATIVE;
|
|
|
|
initIMACompressionParams();
|
|
|
|
track->f.framesPerPacket = samplesPerBlock;
|
|
track->f.bytesPerPacket = blockAlign;
|
|
}
|
|
break;
|
|
|
|
case WAVE_FORMAT_EXTENSIBLE:
|
|
{
|
|
uint16_t bitsPerSample;
|
|
readU16(&bitsPerSample);
|
|
uint16_t extraByteCount;
|
|
readU16(&extraByteCount);
|
|
uint16_t reserved;
|
|
readU16(&reserved);
|
|
uint32_t channelMask;
|
|
readU32(&channelMask);
|
|
UUID subformat;
|
|
readUUID(&subformat);
|
|
if (subformat == _af_wave_guid_pcm)
|
|
{
|
|
track->f.sampleWidth = bitsPerSample;
|
|
|
|
if (bitsPerSample == 0 || bitsPerSample > 32)
|
|
{
|
|
_af_error(AF_BAD_WIDTH,
|
|
"bad sample width of %d bits",
|
|
bitsPerSample);
|
|
return AF_FAIL;
|
|
}
|
|
|
|
// Use valid bits per sample if bytes per sample is the same.
|
|
if (reserved <= bitsPerSample &&
|
|
(reserved + 7) / 8 == (bitsPerSample + 7) / 8)
|
|
track->f.sampleWidth = reserved;
|
|
|
|
if (bitsPerSample <= 8)
|
|
track->f.sampleFormat = AF_SAMPFMT_UNSIGNED;
|
|
else
|
|
track->f.sampleFormat = AF_SAMPFMT_TWOSCOMP;
|
|
}
|
|
else if (subformat == _af_wave_guid_ieee_float)
|
|
{
|
|
if (bitsPerSample == 64)
|
|
{
|
|
track->f.sampleWidth = 64;
|
|
track->f.sampleFormat = AF_SAMPFMT_DOUBLE;
|
|
}
|
|
else
|
|
{
|
|
track->f.sampleWidth = 32;
|
|
track->f.sampleFormat = AF_SAMPFMT_FLOAT;
|
|
}
|
|
}
|
|
else if (subformat == _af_wave_guid_alaw ||
|
|
subformat == _af_wave_guid_ulaw)
|
|
{
|
|
track->f.compressionType = subformat == _af_wave_guid_alaw ?
|
|
AF_COMPRESSION_G711_ALAW : AF_COMPRESSION_G711_ULAW;
|
|
track->f.sampleWidth = 16;
|
|
track->f.sampleFormat = AF_SAMPFMT_TWOSCOMP;
|
|
track->f.byteOrder = _AF_BYTEORDER_NATIVE;
|
|
track->f.bytesPerPacket = channelCount;
|
|
}
|
|
else
|
|
{
|
|
_af_error(AF_BAD_NOT_IMPLEMENTED, "WAVE extensible data format %s is not currently supported", subformat.name().c_str());
|
|
return AF_FAIL;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WAVE_FORMAT_YAMAHA_ADPCM:
|
|
case WAVE_FORMAT_OKI_ADPCM:
|
|
case WAVE_FORMAT_CREATIVE_ADPCM:
|
|
case IBM_FORMAT_ADPCM:
|
|
_af_error(AF_BAD_NOT_IMPLEMENTED, "WAVE ADPCM data format 0x%x is not currently supported", formatTag);
|
|
return AF_FAIL;
|
|
break;
|
|
|
|
case WAVE_FORMAT_MPEG:
|
|
_af_error(AF_BAD_NOT_IMPLEMENTED, "WAVE MPEG data format is not supported");
|
|
return AF_FAIL;
|
|
break;
|
|
|
|
case WAVE_FORMAT_MPEGLAYER3:
|
|
_af_error(AF_BAD_NOT_IMPLEMENTED, "WAVE MPEG layer 3 data format is not supported");
|
|
return AF_FAIL;
|
|
break;
|
|
|
|
default:
|
|
_af_error(AF_BAD_NOT_IMPLEMENTED, "WAVE file data format 0x%x not currently supported != 0xfffe ? %d, != EXTENSIBLE? %d", formatTag, formatTag != 0xfffe, formatTag != WAVE_FORMAT_EXTENSIBLE);
|
|
return AF_FAIL;
|
|
break;
|
|
}
|
|
|
|
if (track->f.isUncompressed())
|
|
track->f.computeBytesPerPacketPCM();
|
|
|
|
_af_set_sample_format(&track->f, track->f.sampleFormat, track->f.sampleWidth);
|
|
|
|
return AF_SUCCEED;
|
|
}
|
|
|
|
status WAVEFile::parseData(const Tag &id, uint32_t size)
|
|
{
|
|
Track *track = getTrack();
|
|
|
|
track->fpos_first_frame = m_fh->tell();
|
|
track->data_size = size;
|
|
|
|
return AF_SUCCEED;
|
|
}
|
|
|
|
status WAVEFile::parsePlayList(const Tag &id, uint32_t size)
|
|
{
|
|
uint32_t segmentCount;
|
|
readU32(&segmentCount);
|
|
|
|
if (segmentCount == 0)
|
|
{
|
|
m_instrumentCount = 0;
|
|
m_instruments = NULL;
|
|
return AF_SUCCEED;
|
|
}
|
|
|
|
for (unsigned segment=0; segment<segmentCount; segment++)
|
|
{
|
|
uint32_t startMarkID, loopLength, loopCount;
|
|
|
|
readU32(&startMarkID);
|
|
readU32(&loopLength);
|
|
readU32(&loopCount);
|
|
}
|
|
|
|
return AF_SUCCEED;
|
|
}
|
|
|
|
status WAVEFile::parseCues(const Tag &id, uint32_t size)
|
|
{
|
|
Track *track = getTrack();
|
|
|
|
uint32_t markerCount;
|
|
readU32(&markerCount);
|
|
track->markerCount = markerCount;
|
|
|
|
if (markerCount == 0)
|
|
{
|
|
track->markers = NULL;
|
|
return AF_SUCCEED;
|
|
}
|
|
|
|
if ((track->markers = _af_marker_new(markerCount)) == NULL)
|
|
return AF_FAIL;
|
|
|
|
for (unsigned i=0; i<markerCount; i++)
|
|
{
|
|
uint32_t id, position, chunkid;
|
|
uint32_t chunkByteOffset, blockByteOffset;
|
|
uint32_t sampleFrameOffset;
|
|
Marker *marker = &track->markers[i];
|
|
|
|
readU32(&id);
|
|
readU32(&position);
|
|
readU32(&chunkid);
|
|
readU32(&chunkByteOffset);
|
|
readU32(&blockByteOffset);
|
|
|
|
/*
|
|
sampleFrameOffset represents the position of
|
|
the mark in units of frames.
|
|
*/
|
|
readU32(&sampleFrameOffset);
|
|
|
|
marker->id = id;
|
|
marker->position = sampleFrameOffset;
|
|
marker->name = _af_strdup("");
|
|
marker->comment = _af_strdup("");
|
|
}
|
|
|
|
return AF_SUCCEED;
|
|
}
|
|
|
|
/* Parse an adtl sub-chunk within a LIST chunk. */
|
|
status WAVEFile::parseADTLSubChunk(const Tag &id, uint32_t size)
|
|
{
|
|
Track *track = getTrack();
|
|
|
|
AFfileoffset endPos = m_fh->tell() + size;
|
|
|
|
while (m_fh->tell() < endPos)
|
|
{
|
|
Tag chunkID;
|
|
uint32_t chunkSize;
|
|
|
|
readTag(&chunkID);
|
|
readU32(&chunkSize);
|
|
|
|
if (chunkID == "labl" || chunkID == "note")
|
|
{
|
|
uint32_t id;
|
|
long length=chunkSize-4;
|
|
char *p = (char *) _af_malloc(length);
|
|
|
|
readU32(&id);
|
|
m_fh->read(p, length);
|
|
|
|
Marker *marker = track->getMarker(id);
|
|
|
|
if (marker)
|
|
{
|
|
if (chunkID == "labl")
|
|
{
|
|
free(marker->name);
|
|
marker->name = p;
|
|
}
|
|
else if (chunkID == "note")
|
|
{
|
|
free(marker->comment);
|
|
marker->comment = p;
|
|
}
|
|
else
|
|
free(p);
|
|
}
|
|
else
|
|
free(p);
|
|
|
|
/*
|
|
If chunkSize is odd, skip an extra byte
|
|
at the end of the chunk.
|
|
*/
|
|
if ((chunkSize % 2) != 0)
|
|
m_fh->seek(1, File::SeekFromCurrent);
|
|
}
|
|
else
|
|
{
|
|
/* If chunkSize is odd, skip an extra byte. */
|
|
m_fh->seek(chunkSize + (chunkSize % 2), File::SeekFromCurrent);
|
|
}
|
|
}
|
|
return AF_SUCCEED;
|
|
}
|
|
|
|
/* Parse an INFO sub-chunk within a LIST chunk. */
|
|
status WAVEFile::parseINFOSubChunk(const Tag &id, uint32_t size)
|
|
{
|
|
AFfileoffset endPos = m_fh->tell() + size;
|
|
|
|
while (m_fh->tell() < endPos)
|
|
{
|
|
int misctype = AF_MISC_UNRECOGNIZED;
|
|
Tag miscid;
|
|
uint32_t miscsize;
|
|
|
|
readTag(&miscid);
|
|
readU32(&miscsize);
|
|
|
|
if (miscid == "IART")
|
|
misctype = AF_MISC_AUTH;
|
|
else if (miscid == "INAM")
|
|
misctype = AF_MISC_NAME;
|
|
else if (miscid == "ICOP")
|
|
misctype = AF_MISC_COPY;
|
|
else if (miscid == "ICMT")
|
|
misctype = AF_MISC_ICMT;
|
|
else if (miscid == "ICRD")
|
|
misctype = AF_MISC_ICRD;
|
|
else if (miscid == "ISFT")
|
|
misctype = AF_MISC_ISFT;
|
|
|
|
if (misctype != AF_MISC_UNRECOGNIZED)
|
|
{
|
|
char *string = (char *) _af_malloc(miscsize);
|
|
|
|
m_fh->read(string, miscsize);
|
|
|
|
m_miscellaneousCount++;
|
|
m_miscellaneous = (Miscellaneous *) _af_realloc(m_miscellaneous, sizeof (Miscellaneous) * m_miscellaneousCount);
|
|
|
|
m_miscellaneous[m_miscellaneousCount-1].id = m_miscellaneousCount;
|
|
m_miscellaneous[m_miscellaneousCount-1].type = misctype;
|
|
m_miscellaneous[m_miscellaneousCount-1].size = miscsize;
|
|
m_miscellaneous[m_miscellaneousCount-1].position = 0;
|
|
m_miscellaneous[m_miscellaneousCount-1].buffer = string;
|
|
}
|
|
else
|
|
{
|
|
m_fh->seek(miscsize, File::SeekFromCurrent);
|
|
}
|
|
|
|
/* Make the current position an even number of bytes. */
|
|
if (miscsize % 2 != 0)
|
|
m_fh->seek(1, File::SeekFromCurrent);
|
|
}
|
|
return AF_SUCCEED;
|
|
}
|
|
|
|
status WAVEFile::parseList(const Tag &id, uint32_t size)
|
|
{
|
|
Tag typeID;
|
|
readTag(&typeID);
|
|
size-=4;
|
|
|
|
if (typeID == "adtl")
|
|
{
|
|
/* Handle adtl sub-chunks. */
|
|
return parseADTLSubChunk(typeID, size);
|
|
}
|
|
else if (typeID == "INFO")
|
|
{
|
|
/* Handle INFO sub-chunks. */
|
|
return parseINFOSubChunk(typeID, size);
|
|
}
|
|
else
|
|
{
|
|
/* Skip unhandled sub-chunks. */
|
|
m_fh->seek(size, File::SeekFromCurrent);
|
|
return AF_SUCCEED;
|
|
}
|
|
return AF_SUCCEED;
|
|
}
|
|
|
|
status WAVEFile::parseInstrument(const Tag &id, uint32_t size)
|
|
{
|
|
uint8_t baseNote;
|
|
int8_t detune, gain;
|
|
uint8_t lowNote, highNote, lowVelocity, highVelocity;
|
|
uint8_t padByte;
|
|
|
|
readU8(&baseNote);
|
|
readS8(&detune);
|
|
readS8(&gain);
|
|
readU8(&lowNote);
|
|
readU8(&highNote);
|
|
readU8(&lowVelocity);
|
|
readU8(&highVelocity);
|
|
readU8(&padByte);
|
|
|
|
return AF_SUCCEED;
|
|
}
|
|
|
|
bool WAVEFile::recognize(File *fh)
|
|
{
|
|
uint8_t buffer[8];
|
|
|
|
fh->seek(0, File::SeekFromBeginning);
|
|
|
|
if (fh->read(buffer, 8) != 8 || memcmp(buffer, "RIFF", 4) != 0)
|
|
return false;
|
|
if (fh->read(buffer, 4) != 4 || memcmp(buffer, "WAVE", 4) != 0)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
status WAVEFile::readInit(AFfilesetup setup)
|
|
{
|
|
Tag type, formtype;
|
|
uint32_t size;
|
|
uint32_t index = 0;
|
|
|
|
bool hasFormat = false;
|
|
bool hasData = false;
|
|
bool hasFrameCount = false;
|
|
|
|
Track *track = allocateTrack();
|
|
|
|
m_fh->seek(0, File::SeekFromBeginning);
|
|
|
|
readTag(&type);
|
|
readU32(&size);
|
|
readTag(&formtype);
|
|
|
|
assert(type == "RIFF");
|
|
assert(formtype == "WAVE");
|
|
|
|
/* Include the offset of the form type. */
|
|
index += 4;
|
|
|
|
while (index < size)
|
|
{
|
|
Tag chunkid;
|
|
uint32_t chunksize = 0;
|
|
status result;
|
|
|
|
readTag(&chunkid);
|
|
readU32(&chunksize);
|
|
|
|
if (chunkid == "fmt ")
|
|
{
|
|
result = parseFormat(chunkid, chunksize);
|
|
if (result == AF_FAIL)
|
|
return AF_FAIL;
|
|
|
|
hasFormat = true;
|
|
}
|
|
else if (chunkid == "data")
|
|
{
|
|
/* The format chunk must precede the data chunk. */
|
|
if (!hasFormat)
|
|
{
|
|
_af_error(AF_BAD_HEADER, "missing format chunk in WAVE file");
|
|
return AF_FAIL;
|
|
}
|
|
|
|
result = parseData(chunkid, chunksize);
|
|
if (result == AF_FAIL)
|
|
return AF_FAIL;
|
|
|
|
hasData = true;
|
|
}
|
|
else if (chunkid == "inst")
|
|
{
|
|
result = parseInstrument(chunkid, chunksize);
|
|
if (result == AF_FAIL)
|
|
return AF_FAIL;
|
|
}
|
|
else if (chunkid == "fact")
|
|
{
|
|
hasFrameCount = true;
|
|
result = parseFrameCount(chunkid, chunksize);
|
|
if (result == AF_FAIL)
|
|
return AF_FAIL;
|
|
}
|
|
else if (chunkid == "cue ")
|
|
{
|
|
result = parseCues(chunkid, chunksize);
|
|
if (result == AF_FAIL)
|
|
return AF_FAIL;
|
|
}
|
|
else if (chunkid == "LIST" || chunkid == "list")
|
|
{
|
|
result = parseList(chunkid, chunksize);
|
|
if (result == AF_FAIL)
|
|
return AF_FAIL;
|
|
}
|
|
else if (chunkid == "INST")
|
|
{
|
|
result = parseInstrument(chunkid, chunksize);
|
|
if (result == AF_FAIL)
|
|
return AF_FAIL;
|
|
}
|
|
else if (chunkid == "plst")
|
|
{
|
|
result = parsePlayList(chunkid, chunksize);
|
|
if (result == AF_FAIL)
|
|
return AF_FAIL;
|
|
}
|
|
|
|
index += chunksize + 8;
|
|
|
|
/* All chunks must be aligned on an even number of bytes */
|
|
if ((index % 2) != 0)
|
|
index++;
|
|
|
|
m_fh->seek(index + 8, File::SeekFromBeginning);
|
|
}
|
|
|
|
/* The format chunk and the data chunk are required. */
|
|
if (!hasFormat || !hasData)
|
|
{
|
|
return AF_FAIL;
|
|
}
|
|
|
|
/*
|
|
At this point we know that the file has a format chunk and a
|
|
data chunk, so we can assume that track->f and track->data_size
|
|
have been initialized.
|
|
*/
|
|
if (!hasFrameCount)
|
|
{
|
|
if (track->f.bytesPerPacket && track->f.framesPerPacket)
|
|
{
|
|
track->computeTotalFileFrames();
|
|
}
|
|
else
|
|
{
|
|
_af_error(AF_BAD_HEADER, "Frame count required but not found");
|
|
return AF_FAIL;
|
|
}
|
|
}
|
|
|
|
return AF_SUCCEED;
|
|
}
|
|
|
|
AFfilesetup WAVEFile::completeSetup(AFfilesetup setup)
|
|
{
|
|
if (setup->trackSet && setup->trackCount != 1)
|
|
{
|
|
_af_error(AF_BAD_NUMTRACKS, "WAVE file must have 1 track");
|
|
return AF_NULL_FILESETUP;
|
|
}
|
|
|
|
TrackSetup *track = setup->getTrack();
|
|
|
|
if (track->f.isCompressed())
|
|
{
|
|
if (!track->sampleFormatSet)
|
|
_af_set_sample_format(&track->f, AF_SAMPFMT_TWOSCOMP, 16);
|
|
else
|
|
_af_set_sample_format(&track->f, track->f.sampleFormat, track->f.sampleWidth);
|
|
}
|
|
else if (track->sampleFormatSet)
|
|
{
|
|
switch (track->f.sampleFormat)
|
|
{
|
|
case AF_SAMPFMT_FLOAT:
|
|
if (track->sampleWidthSet &&
|
|
track->f.sampleWidth != 32)
|
|
{
|
|
_af_error(AF_BAD_WIDTH,
|
|
"Warning: invalid sample width for floating-point WAVE file: %d (must be 32 bits)\n",
|
|
track->f.sampleWidth);
|
|
_af_set_sample_format(&track->f, AF_SAMPFMT_FLOAT, 32);
|
|
}
|
|
break;
|
|
|
|
case AF_SAMPFMT_DOUBLE:
|
|
if (track->sampleWidthSet &&
|
|
track->f.sampleWidth != 64)
|
|
{
|
|
_af_error(AF_BAD_WIDTH,
|
|
"Warning: invalid sample width for double-precision floating-point WAVE file: %d (must be 64 bits)\n",
|
|
track->f.sampleWidth);
|
|
_af_set_sample_format(&track->f, AF_SAMPFMT_DOUBLE, 64);
|
|
}
|
|
break;
|
|
|
|
case AF_SAMPFMT_UNSIGNED:
|
|
if (track->sampleWidthSet)
|
|
{
|
|
if (track->f.sampleWidth < 1 || track->f.sampleWidth > 32)
|
|
{
|
|
_af_error(AF_BAD_WIDTH, "invalid sample width for WAVE file: %d (must be 1-32 bits)\n", track->f.sampleWidth);
|
|
return AF_NULL_FILESETUP;
|
|
}
|
|
if (track->f.sampleWidth > 8)
|
|
{
|
|
_af_error(AF_BAD_SAMPFMT, "WAVE integer data of more than 8 bits must be two's complement signed");
|
|
_af_set_sample_format(&track->f, AF_SAMPFMT_TWOSCOMP, track->f.sampleWidth);
|
|
}
|
|
}
|
|
else
|
|
/*
|
|
If the sample width is not set but the user requests
|
|
unsigned data, set the width to 8 bits.
|
|
*/
|
|
_af_set_sample_format(&track->f, track->f.sampleFormat, 8);
|
|
break;
|
|
|
|
case AF_SAMPFMT_TWOSCOMP:
|
|
if (track->sampleWidthSet)
|
|
{
|
|
if (track->f.sampleWidth < 1 || track->f.sampleWidth > 32)
|
|
{
|
|
_af_error(AF_BAD_WIDTH, "invalid sample width %d for WAVE file (must be 1-32)", track->f.sampleWidth);
|
|
return AF_NULL_FILESETUP;
|
|
}
|
|
else if (track->f.sampleWidth <= 8)
|
|
{
|
|
_af_error(AF_BAD_SAMPFMT, "Warning: WAVE format integer data of 1-8 bits must be unsigned; setting sample format to unsigned");
|
|
_af_set_sample_format(&track->f, AF_SAMPFMT_UNSIGNED, track->f.sampleWidth);
|
|
}
|
|
}
|
|
else
|
|
/*
|
|
If no sample width was specified, we default to 16 bits
|
|
for signed integer data.
|
|
*/
|
|
_af_set_sample_format(&track->f, track->f.sampleFormat, 16);
|
|
break;
|
|
}
|
|
}
|
|
/*
|
|
Otherwise set the sample format depending on the sample
|
|
width or set completely to default.
|
|
*/
|
|
else
|
|
{
|
|
if (!track->sampleWidthSet)
|
|
{
|
|
track->f.sampleWidth = 16;
|
|
track->f.sampleFormat = AF_SAMPFMT_TWOSCOMP;
|
|
}
|
|
else
|
|
{
|
|
if (track->f.sampleWidth < 1 || track->f.sampleWidth > 32)
|
|
{
|
|
_af_error(AF_BAD_WIDTH, "invalid sample width %d for WAVE file (must be 1-32)", track->f.sampleWidth);
|
|
return AF_NULL_FILESETUP;
|
|
}
|
|
else if (track->f.sampleWidth > 8)
|
|
/* Here track->f.sampleWidth is in {1..32}. */
|
|
track->f.sampleFormat = AF_SAMPFMT_TWOSCOMP;
|
|
else
|
|
/* Here track->f.sampleWidth is in {1..8}. */
|
|
track->f.sampleFormat = AF_SAMPFMT_UNSIGNED;
|
|
}
|
|
}
|
|
|
|
if (track->f.compressionType != AF_COMPRESSION_NONE &&
|
|
track->f.compressionType != AF_COMPRESSION_G711_ULAW &&
|
|
track->f.compressionType != AF_COMPRESSION_G711_ALAW &&
|
|
track->f.compressionType != AF_COMPRESSION_IMA &&
|
|
track->f.compressionType != AF_COMPRESSION_MS_ADPCM)
|
|
{
|
|
_af_error(AF_BAD_NOT_IMPLEMENTED, "compression format not supported in WAVE format");
|
|
return AF_NULL_FILESETUP;
|
|
}
|
|
|
|
if (track->f.isUncompressed() &&
|
|
track->byteOrderSet &&
|
|
track->f.byteOrder != AF_BYTEORDER_LITTLEENDIAN &&
|
|
track->f.isByteOrderSignificant())
|
|
{
|
|
_af_error(AF_BAD_BYTEORDER, "WAVE format only supports little-endian data");
|
|
return AF_NULL_FILESETUP;
|
|
}
|
|
|
|
if (track->f.isUncompressed())
|
|
track->f.byteOrder = AF_BYTEORDER_LITTLEENDIAN;
|
|
|
|
if (track->aesDataSet)
|
|
{
|
|
_af_error(AF_BAD_FILESETUP, "WAVE files cannot have AES data");
|
|
return AF_NULL_FILESETUP;
|
|
}
|
|
|
|
if (setup->instrumentSet)
|
|
{
|
|
if (setup->instrumentCount > 1)
|
|
{
|
|
_af_error(AF_BAD_NUMINSTS, "WAVE files can have 0 or 1 instrument");
|
|
return AF_NULL_FILESETUP;
|
|
}
|
|
else if (setup->instrumentCount == 1)
|
|
{
|
|
if (setup->instruments[0].loopSet &&
|
|
setup->instruments[0].loopCount > 0 &&
|
|
(!track->markersSet || track->markerCount == 0))
|
|
{
|
|
_af_error(AF_BAD_NUMMARKS, "WAVE files with loops must contain at least 1 marker");
|
|
return AF_NULL_FILESETUP;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Make sure the miscellaneous data is of an acceptable type. */
|
|
if (setup->miscellaneousSet)
|
|
{
|
|
for (int i=0; i<setup->miscellaneousCount; i++)
|
|
{
|
|
switch (setup->miscellaneous[i].type)
|
|
{
|
|
case AF_MISC_COPY:
|
|
case AF_MISC_AUTH:
|
|
case AF_MISC_NAME:
|
|
case AF_MISC_ICRD:
|
|
case AF_MISC_ISFT:
|
|
case AF_MISC_ICMT:
|
|
break;
|
|
default:
|
|
_af_error(AF_BAD_MISCTYPE, "illegal miscellaneous type [%d] for WAVE file", setup->miscellaneous[i].type);
|
|
return AF_NULL_FILESETUP;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
Allocate an AFfilesetup and make all the unset fields correct.
|
|
*/
|
|
AFfilesetup newsetup = _af_filesetup_copy(setup, &waveDefaultFileSetup, false);
|
|
|
|
/* Make sure we do not copy loops if they are not specified in setup. */
|
|
if (setup->instrumentSet && setup->instrumentCount > 0 &&
|
|
setup->instruments[0].loopSet)
|
|
{
|
|
free(newsetup->instruments[0].loops);
|
|
newsetup->instruments[0].loopCount = 0;
|
|
}
|
|
|
|
return newsetup;
|
|
}
|
|
|
|
bool WAVEFile::isInstrumentParameterValid(AUpvlist list, int i)
|
|
{
|
|
int param, type;
|
|
|
|
AUpvgetparam(list, i, ¶m);
|
|
AUpvgetvaltype(list, i, &type);
|
|
if (type != AU_PVTYPE_LONG)
|
|
return false;
|
|
|
|
long lval;
|
|
AUpvgetval(list, i, &lval);
|
|
|
|
switch (param)
|
|
{
|
|
case AF_INST_MIDI_BASENOTE:
|
|
return ((lval >= 0) && (lval <= 127));
|
|
|
|
case AF_INST_NUMCENTS_DETUNE:
|
|
return ((lval >= -50) && (lval <= 50));
|
|
|
|
case AF_INST_MIDI_LOVELOCITY:
|
|
return ((lval >= 1) && (lval <= 127));
|
|
|
|
case AF_INST_MIDI_HIVELOCITY:
|
|
return ((lval >= 1) && (lval <= 127));
|
|
|
|
case AF_INST_MIDI_LONOTE:
|
|
return ((lval >= 0) && (lval <= 127));
|
|
|
|
case AF_INST_MIDI_HINOTE:
|
|
return ((lval >= 0) && (lval <= 127));
|
|
|
|
case AF_INST_NUMDBS_GAIN:
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
status WAVEFile::writeFormat()
|
|
{
|
|
uint16_t formatTag, channelCount;
|
|
uint32_t sampleRate, averageBytesPerSecond;
|
|
uint16_t blockAlign;
|
|
uint32_t chunkSize;
|
|
uint16_t bitsPerSample;
|
|
|
|
Track *track = getTrack();
|
|
|
|
m_fh->write("fmt ", 4);
|
|
|
|
switch (track->f.compressionType)
|
|
{
|
|
case AF_COMPRESSION_NONE:
|
|
chunkSize = 16;
|
|
if (track->f.sampleFormat == AF_SAMPFMT_FLOAT ||
|
|
track->f.sampleFormat == AF_SAMPFMT_DOUBLE)
|
|
{
|
|
formatTag = WAVE_FORMAT_IEEE_FLOAT;
|
|
}
|
|
else if (track->f.sampleFormat == AF_SAMPFMT_TWOSCOMP ||
|
|
track->f.sampleFormat == AF_SAMPFMT_UNSIGNED)
|
|
{
|
|
formatTag = WAVE_FORMAT_PCM;
|
|
}
|
|
else
|
|
{
|
|
_af_error(AF_BAD_COMPTYPE, "bad sample format");
|
|
return AF_FAIL;
|
|
}
|
|
|
|
blockAlign = _af_format_frame_size(&track->f, false);
|
|
bitsPerSample = 8 * _af_format_sample_size(&track->f, false);
|
|
break;
|
|
|
|
/*
|
|
G.711 compression uses eight bits per sample.
|
|
*/
|
|
case AF_COMPRESSION_G711_ULAW:
|
|
chunkSize = 18;
|
|
formatTag = IBM_FORMAT_MULAW;
|
|
blockAlign = track->f.channelCount;
|
|
bitsPerSample = 8;
|
|
break;
|
|
|
|
case AF_COMPRESSION_G711_ALAW:
|
|
chunkSize = 18;
|
|
formatTag = IBM_FORMAT_ALAW;
|
|
blockAlign = track->f.channelCount;
|
|
bitsPerSample = 8;
|
|
break;
|
|
|
|
case AF_COMPRESSION_IMA:
|
|
chunkSize = 20;
|
|
formatTag = WAVE_FORMAT_DVI_ADPCM;
|
|
blockAlign = track->f.bytesPerPacket;
|
|
bitsPerSample = 4;
|
|
break;
|
|
|
|
case AF_COMPRESSION_MS_ADPCM:
|
|
chunkSize = 50;
|
|
formatTag = WAVE_FORMAT_ADPCM;
|
|
blockAlign = track->f.bytesPerPacket;
|
|
bitsPerSample = 4;
|
|
break;
|
|
|
|
default:
|
|
_af_error(AF_BAD_COMPTYPE, "bad compression type");
|
|
return AF_FAIL;
|
|
}
|
|
|
|
writeU32(&chunkSize);
|
|
writeU16(&formatTag);
|
|
|
|
channelCount = track->f.channelCount;
|
|
writeU16(&channelCount);
|
|
|
|
sampleRate = track->f.sampleRate;
|
|
writeU32(&sampleRate);
|
|
|
|
averageBytesPerSecond =
|
|
track->f.sampleRate * _af_format_frame_size(&track->f, false);
|
|
if (track->f.compressionType == AF_COMPRESSION_IMA ||
|
|
track->f.compressionType == AF_COMPRESSION_MS_ADPCM)
|
|
averageBytesPerSecond = track->f.sampleRate * track->f.bytesPerPacket /
|
|
track->f.framesPerPacket;
|
|
writeU32(&averageBytesPerSecond);
|
|
|
|
writeU16(&blockAlign);
|
|
|
|
writeU16(&bitsPerSample);
|
|
|
|
if (track->f.compressionType == AF_COMPRESSION_G711_ULAW ||
|
|
track->f.compressionType == AF_COMPRESSION_G711_ALAW)
|
|
{
|
|
uint16_t zero = 0;
|
|
writeU16(&zero);
|
|
}
|
|
else if (track->f.compressionType == AF_COMPRESSION_IMA)
|
|
{
|
|
uint16_t extraByteCount = 2;
|
|
writeU16(&extraByteCount);
|
|
uint16_t samplesPerBlock = track->f.framesPerPacket;
|
|
writeU16(&samplesPerBlock);
|
|
}
|
|
else if (track->f.compressionType == AF_COMPRESSION_MS_ADPCM)
|
|
{
|
|
uint16_t extraByteCount = 2 + 2 + m_msadpcmNumCoefficients * 4;
|
|
writeU16(&extraByteCount);
|
|
uint16_t samplesPerBlock = track->f.framesPerPacket;
|
|
writeU16(&samplesPerBlock);
|
|
|
|
uint16_t numCoefficients = m_msadpcmNumCoefficients;
|
|
writeU16(&numCoefficients);
|
|
|
|
for (int i=0; i<m_msadpcmNumCoefficients; i++)
|
|
{
|
|
writeS16(&m_msadpcmCoefficients[i][0]);
|
|
writeS16(&m_msadpcmCoefficients[i][1]);
|
|
}
|
|
}
|
|
|
|
return AF_SUCCEED;
|
|
}
|
|
|
|
status WAVEFile::writeFrameCount()
|
|
{
|
|
uint32_t factSize = 4;
|
|
uint32_t totalFrameCount;
|
|
|
|
Track *track = getTrack();
|
|
|
|
/* Omit the fact chunk only for uncompressed integer audio formats. */
|
|
if (track->f.compressionType == AF_COMPRESSION_NONE &&
|
|
(track->f.sampleFormat == AF_SAMPFMT_TWOSCOMP ||
|
|
track->f.sampleFormat == AF_SAMPFMT_UNSIGNED))
|
|
return AF_SUCCEED;
|
|
|
|
/*
|
|
If the offset for the fact chunk hasn't been set yet,
|
|
set it to the file's current position.
|
|
*/
|
|
if (m_factOffset == 0)
|
|
m_factOffset = m_fh->tell();
|
|
else
|
|
m_fh->seek(m_factOffset, File::SeekFromBeginning);
|
|
|
|
m_fh->write("fact", 4);
|
|
writeU32(&factSize);
|
|
|
|
totalFrameCount = track->totalfframes;
|
|
writeU32(&totalFrameCount);
|
|
|
|
return AF_SUCCEED;
|
|
}
|
|
|
|
status WAVEFile::writeData()
|
|
{
|
|
Track *track = getTrack();
|
|
|
|
m_fh->write("data", 4);
|
|
m_dataSizeOffset = m_fh->tell();
|
|
|
|
uint32_t chunkSize = track->data_size;
|
|
|
|
writeU32(&chunkSize);
|
|
track->fpos_first_frame = m_fh->tell();
|
|
|
|
return AF_SUCCEED;
|
|
}
|
|
|
|
status WAVEFile::update()
|
|
{
|
|
Track *track = getTrack();
|
|
|
|
if (track->fpos_first_frame != 0)
|
|
{
|
|
uint32_t dataLength, fileLength;
|
|
|
|
// Update the frame count chunk if present.
|
|
writeFrameCount();
|
|
|
|
// Update the length of the data chunk.
|
|
m_fh->seek(m_dataSizeOffset, File::SeekFromBeginning);
|
|
dataLength = (uint32_t) track->data_size;
|
|
writeU32(&dataLength);
|
|
|
|
// Update the length of the RIFF chunk.
|
|
fileLength = (uint32_t) m_fh->length();
|
|
fileLength -= 8;
|
|
|
|
m_fh->seek(4, File::SeekFromBeginning);
|
|
writeU32(&fileLength);
|
|
}
|
|
|
|
/*
|
|
Write the actual data that was set after initializing
|
|
the miscellaneous IDs. The size of the data will be
|
|
unchanged.
|
|
*/
|
|
writeMiscellaneous();
|
|
|
|
// Write the new positions; the size of the data will be unchanged.
|
|
writeCues();
|
|
|
|
return AF_SUCCEED;
|
|
}
|
|
|
|
/* Convert an Audio File Library miscellaneous type to a WAVE type. */
|
|
static bool misc_type_to_wave (int misctype, Tag *miscid)
|
|
{
|
|
if (misctype == AF_MISC_AUTH)
|
|
*miscid = "IART";
|
|
else if (misctype == AF_MISC_NAME)
|
|
*miscid = "INAM";
|
|
else if (misctype == AF_MISC_COPY)
|
|
*miscid = "ICOP";
|
|
else if (misctype == AF_MISC_ICMT)
|
|
*miscid = "ICMT";
|
|
else if (misctype == AF_MISC_ICRD)
|
|
*miscid = "ICRD";
|
|
else if (misctype == AF_MISC_ISFT)
|
|
*miscid = "ISFT";
|
|
else
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
status WAVEFile::writeMiscellaneous()
|
|
{
|
|
if (m_miscellaneousCount != 0)
|
|
{
|
|
uint32_t miscellaneousBytes;
|
|
uint32_t chunkSize;
|
|
|
|
/* Start at 12 to account for 'LIST', size, and 'INFO'. */
|
|
miscellaneousBytes = 12;
|
|
|
|
/* Then calculate the size of the whole INFO chunk. */
|
|
for (int i=0; i<m_miscellaneousCount; i++)
|
|
{
|
|
Tag miscid;
|
|
|
|
// Skip miscellaneous data of an unsupported type.
|
|
if (!misc_type_to_wave(m_miscellaneous[i].type, &miscid))
|
|
continue;
|
|
|
|
// Account for miscellaneous type and size.
|
|
miscellaneousBytes += 8;
|
|
miscellaneousBytes += m_miscellaneous[i].size;
|
|
|
|
// Add a pad byte if necessary.
|
|
if (m_miscellaneous[i].size % 2 != 0)
|
|
miscellaneousBytes++;
|
|
|
|
assert(miscellaneousBytes % 2 == 0);
|
|
}
|
|
|
|
if (m_miscellaneousOffset == 0)
|
|
m_miscellaneousOffset = m_fh->tell();
|
|
else
|
|
m_fh->seek(m_miscellaneousOffset, File::SeekFromBeginning);
|
|
|
|
/*
|
|
Write the data. On the first call to this
|
|
function (from _af_wave_write_init), the
|
|
data won't be available, fh->seek is used to
|
|
reserve space until the data has been provided.
|
|
On subseuent calls to this function (from
|
|
_af_wave_update), the data will really be written.
|
|
*/
|
|
|
|
/* Write 'LIST'. */
|
|
m_fh->write("LIST", 4);
|
|
|
|
/* Write the size of the following chunk. */
|
|
chunkSize = miscellaneousBytes-8;
|
|
writeU32(&chunkSize);
|
|
|
|
/* Write 'INFO'. */
|
|
m_fh->write("INFO", 4);
|
|
|
|
/* Write each miscellaneous chunk. */
|
|
for (int i=0; i<m_miscellaneousCount; i++)
|
|
{
|
|
uint32_t miscsize = m_miscellaneous[i].size;
|
|
Tag miscid;
|
|
|
|
// Skip miscellaneous data of an unsupported type.
|
|
if (!misc_type_to_wave(m_miscellaneous[i].type, &miscid))
|
|
continue;
|
|
|
|
writeTag(&miscid);
|
|
writeU32(&miscsize);
|
|
if (m_miscellaneous[i].buffer != NULL)
|
|
{
|
|
uint8_t zero = 0;
|
|
|
|
m_fh->write(m_miscellaneous[i].buffer, m_miscellaneous[i].size);
|
|
|
|
// Pad if necessary.
|
|
if ((m_miscellaneous[i].size%2) != 0)
|
|
writeU8(&zero);
|
|
}
|
|
else
|
|
{
|
|
int size;
|
|
size = m_miscellaneous[i].size;
|
|
|
|
// Pad if necessary.
|
|
if ((size % 2) != 0)
|
|
size++;
|
|
m_fh->seek(size, File::SeekFromCurrent);
|
|
}
|
|
}
|
|
}
|
|
|
|
return AF_SUCCEED;
|
|
}
|
|
|
|
status WAVEFile::writeCues()
|
|
{
|
|
Track *track = getTrack();
|
|
|
|
if (!track->markerCount)
|
|
return AF_SUCCEED;
|
|
|
|
if (m_markOffset == 0)
|
|
m_markOffset = m_fh->tell();
|
|
else
|
|
m_fh->seek(m_markOffset, File::SeekFromBeginning);
|
|
|
|
Tag cue("cue ");
|
|
writeTag(&cue);
|
|
|
|
/*
|
|
The cue chunk consists of 4 bytes for the number of cue points
|
|
followed by 24 bytes for each cue point record.
|
|
*/
|
|
uint32_t cueChunkSize = 4 + track->markerCount * 24;
|
|
writeU32(&cueChunkSize);
|
|
uint32_t numCues = track->markerCount;
|
|
writeU32(&numCues);
|
|
|
|
// Write each marker to the file.
|
|
for (int i=0; i<track->markerCount; i++)
|
|
{
|
|
uint32_t identifier = track->markers[i].id;
|
|
writeU32(&identifier);
|
|
|
|
uint32_t position = i;
|
|
writeU32(&position);
|
|
|
|
Tag data("data");
|
|
writeTag(&data);
|
|
|
|
/*
|
|
For an uncompressed WAVE file which contains only one data chunk,
|
|
chunkStart and blockStart are zero.
|
|
*/
|
|
uint32_t chunkStart = 0;
|
|
writeU32(&chunkStart);
|
|
|
|
uint32_t blockStart = 0;
|
|
writeU32(&blockStart);
|
|
|
|
AFframecount markPosition = track->markers[i].position;
|
|
uint32_t sampleOffset = markPosition;
|
|
writeU32(&sampleOffset);
|
|
}
|
|
|
|
// Now write the cue names and comments within a master list chunk.
|
|
uint32_t listChunkSize = 4;
|
|
for (int i=0; i<track->markerCount; i++)
|
|
{
|
|
const char *name = track->markers[i].name;
|
|
const char *comment = track->markers[i].comment;
|
|
|
|
/*
|
|
Each 'labl' or 'note' chunk consists of 4 bytes for the chunk ID,
|
|
4 bytes for the chunk data size, 4 bytes for the cue point ID,
|
|
and then the length of the label as a null-terminated string.
|
|
|
|
In all, this is 12 bytes plus the length of the string, its null
|
|
termination byte, and a trailing pad byte if the length of the
|
|
chunk is otherwise odd.
|
|
*/
|
|
listChunkSize += 12 + zStringLength(name);
|
|
listChunkSize += 12 + zStringLength(comment);
|
|
}
|
|
|
|
Tag list("LIST");
|
|
writeTag(&list);
|
|
writeU32(&listChunkSize);
|
|
Tag adtl("adtl");
|
|
writeTag(&adtl);
|
|
|
|
for (int i=0; i<track->markerCount; i++)
|
|
{
|
|
uint32_t cuePointID = track->markers[i].id;
|
|
|
|
const char *name = track->markers[i].name;
|
|
uint32_t labelSize = 4 + zStringLength(name);
|
|
Tag lablTag("labl");
|
|
writeTag(&lablTag);
|
|
writeU32(&labelSize);
|
|
writeU32(&cuePointID);
|
|
writeZString(name);
|
|
|
|
const char *comment = track->markers[i].comment;
|
|
uint32_t noteSize = 4 + zStringLength(comment);
|
|
Tag noteTag("note");
|
|
writeTag(¬eTag);
|
|
writeU32(¬eSize);
|
|
writeU32(&cuePointID);
|
|
writeZString(comment);
|
|
}
|
|
|
|
return AF_SUCCEED;
|
|
}
|
|
|
|
bool WAVEFile::writeZString(const char *s)
|
|
{
|
|
ssize_t lengthPlusNull = strlen(s) + 1;
|
|
if (m_fh->write(s, lengthPlusNull) != lengthPlusNull)
|
|
return false;
|
|
if (lengthPlusNull & 1)
|
|
{
|
|
uint8_t zero = 0;
|
|
if (!writeU8(&zero))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
size_t WAVEFile::zStringLength(const char *s)
|
|
{
|
|
size_t lengthPlusNull = strlen(s) + 1;
|
|
return lengthPlusNull + (lengthPlusNull & 1);
|
|
}
|
|
|
|
status WAVEFile::writeInit(AFfilesetup setup)
|
|
{
|
|
if (initFromSetup(setup) == AF_FAIL)
|
|
return AF_FAIL;
|
|
|
|
initCompressionParams();
|
|
|
|
uint32_t zero = 0;
|
|
|
|
m_fh->seek(0, File::SeekFromBeginning);
|
|
m_fh->write("RIFF", 4);
|
|
m_fh->write(&zero, 4);
|
|
m_fh->write("WAVE", 4);
|
|
|
|
writeMiscellaneous();
|
|
writeCues();
|
|
writeFormat();
|
|
writeFrameCount();
|
|
writeData();
|
|
|
|
return AF_SUCCEED;
|
|
}
|
|
|
|
bool WAVEFile::readUUID(UUID *u)
|
|
{
|
|
return m_fh->read(u->data, 16) == 16;
|
|
}
|
|
|
|
bool WAVEFile::writeUUID(const UUID *u)
|
|
{
|
|
return m_fh->write(u->data, 16) == 16;
|
|
}
|
|
|
|
void WAVEFile::initCompressionParams()
|
|
{
|
|
Track *track = getTrack();
|
|
if (track->f.compressionType == AF_COMPRESSION_IMA)
|
|
initIMACompressionParams();
|
|
else if (track->f.compressionType == AF_COMPRESSION_MS_ADPCM)
|
|
initMSADPCMCompressionParams();
|
|
}
|
|
|
|
void WAVEFile::initIMACompressionParams()
|
|
{
|
|
Track *track = getTrack();
|
|
|
|
track->f.framesPerPacket = 505;
|
|
track->f.bytesPerPacket = 256 * track->f.channelCount;
|
|
|
|
AUpvlist pv = AUpvnew(1);
|
|
AUpvsetparam(pv, 0, _AF_IMA_ADPCM_TYPE);
|
|
AUpvsetvaltype(pv, 0, AU_PVTYPE_LONG);
|
|
long l = _AF_IMA_ADPCM_TYPE_WAVE;
|
|
AUpvsetval(pv, 0, &l);
|
|
|
|
track->f.compressionParams = pv;
|
|
}
|
|
|
|
void WAVEFile::initMSADPCMCompressionParams()
|
|
{
|
|
const int16_t coefficients[7][2] =
|
|
{
|
|
{ 256, 0 },
|
|
{ 512, -256 },
|
|
{ 0, 0 },
|
|
{ 192, 64 },
|
|
{ 240, 0 },
|
|
{ 460, -208 },
|
|
{ 392, -232 }
|
|
};
|
|
memcpy(m_msadpcmCoefficients, coefficients, sizeof (int16_t) * 7 * 2);
|
|
m_msadpcmNumCoefficients = 7;
|
|
|
|
Track *track = getTrack();
|
|
|
|
track->f.framesPerPacket = 500;
|
|
track->f.bytesPerPacket = 256 * track->f.channelCount;
|
|
|
|
AUpvlist pv = AUpvnew(2);
|
|
AUpvsetparam(pv, 0, _AF_MS_ADPCM_NUM_COEFFICIENTS);
|
|
AUpvsetvaltype(pv, 0, AU_PVTYPE_LONG);
|
|
long l = m_msadpcmNumCoefficients;
|
|
AUpvsetval(pv, 0, &l);
|
|
|
|
AUpvsetparam(pv, 1, _AF_MS_ADPCM_COEFFICIENTS);
|
|
AUpvsetvaltype(pv, 1, AU_PVTYPE_PTR);
|
|
void *v = m_msadpcmCoefficients;
|
|
AUpvsetval(pv, 1, &v);
|
|
|
|
track->f.compressionParams = pv;
|
|
}
|