sm64pc/tools/audiofile-0.3.6/libaudiofile/IFF.cpp
2020-05-07 20:21:22 +02:00

490 lines
11 KiB
C++

/*
Audio File Library
Copyright (C) 2004, 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
*/
/*
IFF.cpp
This file contains routines for reading and writing IFF/8SVX
sound files.
*/
#include "config.h"
#include "IFF.h"
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "File.h"
#include "Marker.h"
#include "Setup.h"
#include "Tag.h"
#include "Track.h"
#include "afinternal.h"
#include "audiofile.h"
#include "byteorder.h"
#include "util.h"
static const _AFfilesetup iffDefaultFileSetup =
{
_AF_VALID_FILESETUP, /* valid */
AF_FILE_IFF_8SVX, /* fileFormat */
true, /* trackSet */
true, /* instrumentSet */
true, /* miscellaneousSet */
1, /* trackCount */
NULL, /* tracks */
0, /* instrumentCount */
NULL, /* instruments */
0, /* miscellaneousCount */
NULL /* miscellaneous */
};
bool IFFFile::recognize(File *fh)
{
uint8_t buffer[8];
fh->seek(0, File::SeekFromBeginning);
if (fh->read(buffer, 8) != 8 || memcmp(buffer, "FORM", 4) != 0)
return false;
if (fh->read(buffer, 4) != 4 || memcmp(buffer, "8SVX", 4) != 0)
return false;
return true;
}
IFFFile::IFFFile()
{
setFormatByteOrder(AF_BYTEORDER_BIGENDIAN);
m_miscellaneousPosition = 0;
m_VHDR_offset = 0;
m_BODY_offset = 0;
}
/*
Parse miscellaneous data chunks such as name, author, copyright,
and annotation chunks.
*/
status IFFFile::parseMiscellaneous(const Tag &type, size_t size)
{
int misctype = AF_MISC_UNRECOGNIZED;
assert(type == "NAME" || type == "AUTH" ||
type == "(c) " || type == "ANNO");
/* Skip zero-length miscellaneous chunks. */
if (size == 0)
return AF_FAIL;
m_miscellaneousCount++;
m_miscellaneous = (Miscellaneous *) _af_realloc(m_miscellaneous,
m_miscellaneousCount * sizeof (Miscellaneous));
if (type == "NAME")
misctype = AF_MISC_NAME;
else if (type == "AUTH")
misctype = AF_MISC_AUTH;
else if (type == "(c) ")
misctype = AF_MISC_COPY;
else if (type == "ANNO")
misctype = AF_MISC_ANNO;
m_miscellaneous[m_miscellaneousCount - 1].id = m_miscellaneousCount;
m_miscellaneous[m_miscellaneousCount - 1].type = misctype;
m_miscellaneous[m_miscellaneousCount - 1].size = size;
m_miscellaneous[m_miscellaneousCount - 1].position = 0;
m_miscellaneous[m_miscellaneousCount - 1].buffer = _af_malloc(size);
m_fh->read(m_miscellaneous[m_miscellaneousCount - 1].buffer, size);
return AF_SUCCEED;
}
/*
Parse voice header chunk.
*/
status IFFFile::parseVHDR(const Tag &type, size_t size)
{
assert(type == "VHDR");
Track *track = getTrack();
uint32_t oneShotSamples, repeatSamples, samplesPerRepeat;
uint16_t sampleRate;
uint8_t octaves, compression;
uint32_t volume;
readU32(&oneShotSamples);
readU32(&repeatSamples);
readU32(&samplesPerRepeat);
readU16(&sampleRate);
readU8(&octaves);
readU8(&compression);
readU32(&volume);
track->f.sampleWidth = 8;
track->f.sampleRate = sampleRate;
track->f.sampleFormat = AF_SAMPFMT_TWOSCOMP;
track->f.compressionType = AF_COMPRESSION_NONE;
track->f.byteOrder = AF_BYTEORDER_BIGENDIAN;
track->f.channelCount = 1;
track->f.framesPerPacket = 1;
track->f.computeBytesPerPacketPCM();
_af_set_sample_format(&track->f, track->f.sampleFormat, track->f.sampleWidth);
return AF_SUCCEED;
}
status IFFFile::parseBODY(const Tag &type, size_t size)
{
Track *track = getTrack();
/*
IFF/8SVX files have only one audio channel with one
byte per sample, so the number of frames is equal to
the number of bytes.
*/
track->totalfframes = size;
track->data_size = size;
/* Sound data follows. */
track->fpos_first_frame = m_fh->tell();
return AF_SUCCEED;
}
status IFFFile::readInit(AFfilesetup setup)
{
m_fh->seek(0, File::SeekFromBeginning);
Tag type;
uint32_t size;
Tag formtype;
readTag(&type);
readU32(&size);
readTag(&formtype);
if (type != "FORM" || formtype != "8SVX")
return AF_FAIL;
/* IFF/8SVX files have only one track. */
Track *track = allocateTrack();
if (!track)
return AF_FAIL;
/* Set the index to include the form type ('8SVX' in this case). */
size_t index = 4;
while (index < size)
{
Tag chunkid;
uint32_t chunksize = 0;
status result = AF_SUCCEED;
readTag(&chunkid);
readU32(&chunksize);
if (chunkid == "VHDR")
{
result = parseVHDR(chunkid, chunksize);
}
else if (chunkid == "BODY")
{
result = parseBODY(chunkid, chunksize);
}
else if (chunkid == "NAME" ||
chunkid == "AUTH" ||
chunkid == "(c) " ||
chunkid == "ANNO")
{
parseMiscellaneous(chunkid, chunksize);
}
if (result == AF_FAIL)
return AF_FAIL;
/*
Increment the index by the size of the chunk
plus the size of the chunk header.
*/
index += chunksize + 8;
/* All chunks must be aligned on an even number of bytes. */
if ((index % 2) != 0)
index++;
/* Set the seek position to the beginning of the next chunk. */
m_fh->seek(index + 8, File::SeekFromBeginning);
}
/* The file has been successfully parsed. */
return AF_SUCCEED;
}
AFfilesetup IFFFile::completeSetup(AFfilesetup setup)
{
if (setup->trackSet && setup->trackCount != 1)
{
_af_error(AF_BAD_NUMTRACKS, "IFF/8SVX file must have 1 track");
return AF_NULL_FILESETUP;
}
TrackSetup *track = &setup->tracks[0];
if (track->sampleFormatSet &&
track->f.sampleFormat != AF_SAMPFMT_TWOSCOMP)
{
_af_error(AF_BAD_SAMPFMT,
"IFF/8SVX format supports only two's complement integer data");
return AF_NULL_FILESETUP;
}
if (track->sampleFormatSet && track->f.sampleWidth != 8)
{
_af_error(AF_BAD_WIDTH,
"IFF/8SVX file allows only 8 bits per sample "
"(%d bits requested)", track->f.sampleWidth);
return AF_NULL_FILESETUP;
}
if (track->channelCountSet && track->f.channelCount != 1)
{
_af_error(AF_BAD_CHANNELS,
"invalid channel count (%d) for IFF/8SVX format "
"(only 1 channel supported)",
track->f.channelCount);
return AF_NULL_FILESETUP;
}
if (track->f.compressionType != AF_COMPRESSION_NONE)
{
_af_error(AF_BAD_COMPRESSION,
"IFF/8SVX does not support compression");
return AF_NULL_FILESETUP;
}
/* Ignore requested byte order since samples are only one byte. */
track->f.byteOrder = AF_BYTEORDER_BIGENDIAN;
/* Either one channel was requested or no request was made. */
track->f.channelCount = 1;
_af_set_sample_format(&track->f, AF_SAMPFMT_TWOSCOMP, 8);
if (track->markersSet && track->markerCount != 0)
{
_af_error(AF_BAD_NUMMARKS,
"IFF/8SVX format does not support markers");
return AF_NULL_FILESETUP;
}
if (track->aesDataSet)
{
_af_error(AF_BAD_FILESETUP, "IFF/8SVX format does not support AES data");
return AF_NULL_FILESETUP;
}
if (setup->instrumentSet && setup->instrumentCount != 0)
{
_af_error(AF_BAD_NUMINSTS,
"IFF/8SVX format does not support instruments");
return AF_NULL_FILESETUP;
}
return _af_filesetup_copy(setup, &iffDefaultFileSetup, true);
}
status IFFFile::writeInit(AFfilesetup setup)
{
if (initFromSetup(setup) == AF_FAIL)
return AF_FAIL;
uint32_t fileSize = 0;
m_fh->write("FORM", 4);
writeU32(&fileSize);
m_fh->write("8SVX", 4);
writeVHDR();
writeMiscellaneous();
writeBODY();
return AF_SUCCEED;
}
status IFFFile::update()
{
uint32_t length;
writeVHDR();
writeMiscellaneous();
writeBODY();
/* Get the length of the file. */
length = m_fh->length();
length -= 8;
/* Set the length of the FORM chunk. */
m_fh->seek(4, File::SeekFromBeginning);
writeU32(&length);
return AF_SUCCEED;
}
status IFFFile::writeVHDR()
{
uint32_t chunkSize;
uint32_t oneShotSamples, repeatSamples, samplesPerRepeat;
uint16_t sampleRate;
uint8_t octaves, compression;
uint32_t volume;
/*
If VHDR_offset hasn't been set yet, set it to the
current offset.
*/
if (m_VHDR_offset == 0)
m_VHDR_offset = m_fh->tell();
else
m_fh->seek(m_VHDR_offset, File::SeekFromBeginning);
Track *track = getTrack();
m_fh->write("VHDR", 4);
chunkSize = 20;
writeU32(&chunkSize);
/*
IFF/8SVX files have only one audio channel, so the
number of samples is equal to the number of frames.
*/
oneShotSamples = track->totalfframes;
writeU32(&oneShotSamples);
repeatSamples = 0;
writeU32(&repeatSamples);
samplesPerRepeat = 0;
writeU32(&samplesPerRepeat);
sampleRate = track->f.sampleRate;
writeU16(&sampleRate);
octaves = 0;
compression = 0;
writeU8(&octaves);
writeU8(&compression);
/* Volume is in fixed-point notation; 65536 means gain of 1.0. */
volume = 65536;
writeU32(&volume);
return AF_SUCCEED;
}
status IFFFile::writeBODY()
{
uint32_t chunkSize;
Track *track = getTrack();
if (m_BODY_offset == 0)
m_BODY_offset = m_fh->tell();
else
m_fh->seek(m_BODY_offset, File::SeekFromBeginning);
m_fh->write("BODY", 4);
/*
IFF/8SVX supports only one channel, so the number of
frames is equal to the number of samples, and each
sample is one byte.
*/
chunkSize = track->totalfframes;
writeU32(&chunkSize);
if (track->fpos_first_frame == 0)
track->fpos_first_frame = m_fh->tell();
/* Add a pad byte to the end of the chunk if the chunk size is odd. */
if ((chunkSize % 2) == 1)
{
uint8_t zero = 0;
m_fh->seek(m_BODY_offset + 8 + chunkSize, File::SeekFromBeginning);
writeU8(&zero);
}
return AF_SUCCEED;
}
/*
WriteMiscellaneous writes all the miscellaneous data chunks in a
file handle structure to an IFF/8SVX file.
*/
status IFFFile::writeMiscellaneous()
{
if (m_miscellaneousPosition == 0)
m_miscellaneousPosition = m_fh->tell();
else
m_fh->seek(m_miscellaneousPosition, File::SeekFromBeginning);
for (int i=0; i<m_miscellaneousCount; i++)
{
Miscellaneous *misc = &m_miscellaneous[i];
Tag chunkType;
uint32_t chunkSize;
uint8_t padByte = 0;
switch (misc->type)
{
case AF_MISC_NAME:
chunkType = "NAME"; break;
case AF_MISC_AUTH:
chunkType = "AUTH"; break;
case AF_MISC_COPY:
chunkType = "(c) "; break;
case AF_MISC_ANNO:
chunkType = "ANNO"; break;
}
writeTag(&chunkType);
chunkSize = misc->size;
writeU32(&chunkSize);
/*
Write the miscellaneous buffer and then a pad byte
if necessary. If the buffer is null, skip the space
for now.
*/
if (misc->buffer != NULL)
m_fh->write(misc->buffer, misc->size);
else
m_fh->seek(misc->size, File::SeekFromCurrent);
if (misc->size % 2 != 0)
writeU8(&padByte);
}
return AF_SUCCEED;
}