sm64pc/tools/audiofile-0.3.6/libaudiofile/VOC.cpp

432 lines
10 KiB
C++

/*
Audio File Library
Copyright (C) 2011-2013, Michael Pruett <michael@68k.org>
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
*/
#include "config.h"
#include "VOC.h"
#include "File.h"
#include "Track.h"
#include "Setup.h"
#include "byteorder.h"
#include "util.h"
#include <string.h>
#include <assert.h>
const int _af_voc_compression_types[_AF_VOC_NUM_COMPTYPES] =
{
AF_COMPRESSION_G711_ULAW,
AF_COMPRESSION_G711_ALAW
};
static const int kVOCMagicLength = 20;
static const char kVOCMagic[kVOCMagicLength + 1] = "Creative Voice File\x1a";
enum
{
kVOCTerminator = 0,
kVOCSoundData = 1,
kVOCSoundDataContinuation = 2,
kVOCSilence = 3,
kVOCMarker = 4,
kVOCText = 5,
kVOCRepeatStart = 6,
kVOCRepeatEnd = 7,
kVOCExtendedInfo = 8,
kVOCSoundDataNew = 9
};
enum
{
kVOCFormatU8 = 0,
kVOCFormatCreativeADPCM4_8 = 1,
kVOCFormatCreativeADPCM3_8 = 2,
kVOCFormatCreativeADPCM2_8 = 3,
kVOCFormatS16 = 4,
kVOCFormatAlaw = 6,
kVOCFormatUlaw = 7,
kVOCFormatCreativeADPCM4_16 = 0x200,
};
static const _AFfilesetup vocDefaultFileSetup =
{
_AF_VALID_FILESETUP, // valid
AF_FILE_VOC, // fileFormat
true, // trackSet
true, // instrumentSet
true, // miscellaneousSet
1, // trackCount
NULL, // tracks
1, // instrumentCount
NULL, // instruments
0, // miscellaneousCount
NULL // miscellaneous
};
VOCFile::VOCFile() :
m_soundDataOffset(-1)
{
setFormatByteOrder(AF_BYTEORDER_LITTLEENDIAN);
}
bool VOCFile::recognize(File *f)
{
f->seek(0, File::SeekFromBeginning);
char buffer[kVOCMagicLength];
if (f->read(buffer, kVOCMagicLength) != kVOCMagicLength)
return false;
if (memcmp(buffer, kVOCMagic, kVOCMagicLength) != 0)
return false;
return true;
}
AFfilesetup VOCFile::completeSetup(AFfilesetup setup)
{
if (setup->trackSet && setup->trackCount != 1)
{
_af_error(AF_BAD_NUMTRACKS, "VOC file must have 1 track");
return AF_NULL_FILESETUP;
}
TrackSetup *track = &setup->tracks[0];
if (track->sampleFormatSet)
{
if (!track->f.isInteger())
{
_af_error(AF_BAD_SAMPFMT,
"VOC format supports only integer audio data");
return AF_NULL_FILESETUP;
}
if ((track->f.isSigned() && track->f.sampleWidth != 16) ||
(track->f.isUnsigned() && track->f.sampleWidth != 8))
{
_af_error(AF_BAD_SAMPFMT,
"VOC format supports only 16-bit signed or 8-bit unsigned data");
return AF_NULL_FILESETUP;
}
}
else
_af_set_sample_format(&track->f, AF_SAMPFMT_TWOSCOMP,
track->f.sampleWidth);
if (track->f.isUncompressed() &&
track->byteOrderSet &&
track->f.byteOrder != AF_BYTEORDER_LITTLEENDIAN &&
track->f.isByteOrderSignificant())
{
_af_error(AF_BAD_BYTEORDER, "VOC supports only little-endian data");
return AF_NULL_FILESETUP;
}
if (track->f.isUncompressed())
track->f.byteOrder = AF_BYTEORDER_LITTLEENDIAN;
if (track->f.compressionType != AF_COMPRESSION_NONE &&
track->f.compressionType != AF_COMPRESSION_G711_ULAW &&
track->f.compressionType != AF_COMPRESSION_G711_ALAW)
{
_af_error(AF_BAD_COMPTYPE,
"compression format %d not supported in VOC file",
track->f.compressionType);
return AF_NULL_FILESETUP;
}
if (track->markersSet && track->markerCount)
{
_af_error(AF_BAD_NUMMARKS, "VOC does not support markers");
return AF_NULL_FILESETUP;
}
if (track->aesDataSet)
{
_af_error(AF_BAD_FILESETUP, "VOC does not support AES data");
return AF_NULL_FILESETUP;
}
return _af_filesetup_copy(setup, &vocDefaultFileSetup, true);
}
status VOCFile::readInit(AFfilesetup)
{
m_fh->seek(20, File::SeekFromBeginning);
uint16_t dataOffset, version, checksum;
readU16(&dataOffset);
readU16(&version);
readU16(&checksum);
Track *track = allocateTrack();
bool hasExtendedInfo = false;
bool foundSoundData = false;
off_t position = m_fh->tell();
off_t fileLength = m_fh->length();
while (position < fileLength)
{
uint32_t blockHeader;
if (!readU32(&blockHeader))
break;
uint8_t blockType = blockHeader & 0xff;
uint32_t blockSize = blockHeader >> 8;
if (blockType == kVOCSoundData)
{
if (foundSoundData)
{
_af_error(AF_BAD_HEADER, "VOC file contains multiple sound data blocks");
return AF_FAIL;
}
foundSoundData = true;
uint8_t frequencyDivisor, codec;
readU8(&frequencyDivisor);
readU8(&codec);
if (!hasExtendedInfo)
{
track->f.channelCount = 1;
track->f.sampleRate = 1000000 / (256 - frequencyDivisor);
}
track->f.compressionType = AF_COMPRESSION_NONE;
track->f.byteOrder = AF_BYTEORDER_LITTLEENDIAN;
track->f.framesPerPacket = 1;
if (codec == kVOCFormatU8)
{
_af_set_sample_format(&track->f, AF_SAMPFMT_UNSIGNED, 8);
track->f.computeBytesPerPacketPCM();
}
else if (codec == kVOCFormatCreativeADPCM4_8 ||
codec == kVOCFormatCreativeADPCM3_8 ||
codec == kVOCFormatCreativeADPCM2_8)
{
_af_error(AF_BAD_NOT_IMPLEMENTED,
"Creative ADPCM compression is not currently suppported");
return AF_FAIL;
}
else
{
_af_error(AF_BAD_CODEC_TYPE,
"VOC file contains unrecognized codec type %d", codec);
return AF_FAIL;
}
track->fpos_first_frame = m_fh->tell();
track->data_size = m_fh->length() - 1 - track->fpos_first_frame;
track->computeTotalFileFrames();
}
else if (blockType == kVOCExtendedInfo)
{
if (foundSoundData)
{
_af_error(AF_BAD_HEADER, "VOC extended information found after sound data");
return AF_FAIL;
}
hasExtendedInfo = true;
uint16_t frequencyDivisor;
uint8_t bitsPerSample;
uint8_t isStereo;
readU16(&frequencyDivisor);
readU8(&bitsPerSample);
readU8(&isStereo);
track->f.sampleWidth = bitsPerSample;
track->f.channelCount = isStereo ? 2 : 1;
uint32_t frequencyDividend = 256000000 / (isStereo ? 2 : 1);
track->f.sampleRate = frequencyDividend / (65536 - frequencyDivisor);
}
else if (blockType == kVOCSoundDataNew)
{
if (foundSoundData)
{
_af_error(AF_BAD_HEADER, "VOC file contains multiple sound data blocks");
return AF_FAIL;
}
foundSoundData = true;
uint32_t sampleRate;
uint8_t bitsPerSample, channels;
uint16_t format;
uint32_t pad;
readU32(&sampleRate);
readU8(&bitsPerSample);
readU8(&channels);
readU16(&format);
readU32(&pad);
if (!channels)
{
_af_error(AF_BAD_CHANNELS, "invalid file with 0 channels");
return AF_FAIL;
}
track->fpos_first_frame = m_fh->tell();
track->data_size = blockSize - 12;
track->f.compressionType = AF_COMPRESSION_NONE;
track->f.byteOrder = AF_BYTEORDER_LITTLEENDIAN;
track->f.sampleRate = sampleRate;
track->f.channelCount = channels;
track->f.framesPerPacket = 1;
if (format == kVOCFormatU8)
{
_af_set_sample_format(&track->f, AF_SAMPFMT_UNSIGNED, 8);
track->f.computeBytesPerPacketPCM();
}
else if (format == kVOCFormatS16)
{
_af_set_sample_format(&track->f, AF_SAMPFMT_TWOSCOMP, 16);
track->f.computeBytesPerPacketPCM();
}
else if (format == kVOCFormatAlaw)
{
track->f.compressionType = AF_COMPRESSION_G711_ALAW;
track->f.byteOrder = _AF_BYTEORDER_NATIVE;
track->f.bytesPerPacket = track->f.channelCount;
_af_set_sample_format(&track->f, AF_SAMPFMT_TWOSCOMP, 16);
}
else if (format == kVOCFormatUlaw)
{
track->f.compressionType = AF_COMPRESSION_G711_ULAW;
track->f.byteOrder = _AF_BYTEORDER_NATIVE;
track->f.bytesPerPacket = track->f.channelCount;
_af_set_sample_format(&track->f, AF_SAMPFMT_TWOSCOMP, 16);
}
else if (format == kVOCFormatCreativeADPCM4_8 ||
format == kVOCFormatCreativeADPCM3_8 ||
format == kVOCFormatCreativeADPCM2_8 ||
format == kVOCFormatCreativeADPCM4_16)
{
_af_error(AF_BAD_NOT_IMPLEMENTED,
"Creative ADPCM compression is not currently supported");
return AF_FAIL;
}
else
{
_af_error(AF_BAD_CODEC_TYPE,
"VOC file contains unrecognized codec type %d", format);
return AF_FAIL;
}
track->computeTotalFileFrames();
}
position += 4 + blockSize;
m_fh->seek(position, File::SeekFromBeginning);
}
return AF_SUCCEED;
}
status VOCFile::writeInit(AFfilesetup setup)
{
if (initFromSetup(setup) == AF_FAIL)
return AF_FAIL;
m_fh->write(kVOCMagic, kVOCMagicLength);
uint16_t dataOffset = 0x001a;
uint16_t version = 0x0114;
uint16_t checksum = 0x1234 + ~version;
writeU16(&dataOffset);
writeU16(&version);
writeU16(&checksum);
return writeSoundData();
}
status VOCFile::update()
{
if (writeSoundData() == AF_FAIL || writeTerminator() == AF_FAIL)
return AF_FAIL;
return AF_SUCCEED;
}
status VOCFile::writeSoundData()
{
if (m_soundDataOffset == -1)
m_soundDataOffset = m_fh->tell();
else
m_fh->seek(m_soundDataOffset, File::SeekFromBeginning);
Track *track = getTrack();
assert((track->f.isSigned() && track->f.sampleWidth == 16) ||
(track->f.isUnsigned() && track->f.sampleWidth == 8));
uint8_t blockType = kVOCSoundDataNew;
uint32_t blockSize = 12 + track->data_size;
uint32_t blockHeader = blockSize << 8 | blockType;
if (!writeU32(&blockHeader))
return AF_FAIL;
uint32_t sampleRate = track->f.sampleRate;
uint8_t bitsPerSample = track->f.sampleWidth;
uint8_t channels = track->f.channelCount;
uint16_t format;
if (track->f.compressionType == AF_COMPRESSION_G711_ULAW)
{
format = kVOCFormatUlaw;
bitsPerSample = 8;
}
else if (track->f.compressionType == AF_COMPRESSION_G711_ALAW)
{
format = kVOCFormatAlaw;
bitsPerSample = 8;
}
else if (track->f.compressionType == AF_COMPRESSION_NONE)
{
if (track->f.isUnsigned())
format = kVOCFormatU8;
else
format = kVOCFormatS16;
}
uint32_t pad = 0;
if (!writeU32(&sampleRate) ||
!writeU8(&bitsPerSample) ||
!writeU8(&channels) ||
!writeU16(&format) ||
!writeU32(&pad))
return AF_FAIL;
if (track->fpos_first_frame == 0)
track->fpos_first_frame = m_fh->tell();
return AF_SUCCEED;
}
status VOCFile::writeTerminator()
{
Track *track = getTrack();
m_fh->seek(track->fpos_first_frame + track->data_size, File::SeekFromBeginning);
uint8_t terminator = kVOCTerminator;
if (!writeU8(&terminator))
return AF_FAIL;
return AF_SUCCEED;
}