442 lines
11 KiB
C++
442 lines
11 KiB
C++
/*
|
|
Audio File Library
|
|
Copyright (C) 2001, Silicon Graphics, Inc.
|
|
Copyright (C) 2011, 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
|
|
*/
|
|
|
|
/*
|
|
IRCAM.cpp
|
|
|
|
This file contains routines for reading and writing
|
|
Berkeley/IRCAM/CARL sound files.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "IRCAM.h"
|
|
|
|
#include <assert.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "File.h"
|
|
#include "Marker.h"
|
|
#include "Setup.h"
|
|
#include "Track.h"
|
|
#include "byteorder.h"
|
|
#include "util.h"
|
|
|
|
enum
|
|
{
|
|
SF_CHAR = 1,
|
|
SF_SHORT = 2,
|
|
SF_24INT = 3,
|
|
SF_LONG = 0x40004,
|
|
SF_FLOAT = 4,
|
|
SF_DOUBLE = 8,
|
|
SF_ALAW = 0x10001,
|
|
SF_ULAW = 0x20001
|
|
};
|
|
|
|
#define SF_MAXCHAN 4
|
|
#define SF_MAXCOMMENT 512
|
|
#define SF_MINCOMMENT 256
|
|
|
|
enum
|
|
{
|
|
SF_END,
|
|
SF_MAXAMP,
|
|
SF_COMMENT,
|
|
SF_LINKCODE
|
|
};
|
|
|
|
#define SIZEOF_BSD_HEADER 1024
|
|
|
|
static const uint8_t ircam_vax_le_magic[4] = {0x64, 0xa3, 0x01, 0x00},
|
|
ircam_vax_be_magic[4] = {0x00, 0x01, 0xa3, 0x64},
|
|
ircam_sun_be_magic[4] = {0x64, 0xa3, 0x02, 0x00},
|
|
ircam_sun_le_magic[4] = {0x00, 0x02, 0xa3, 0x64},
|
|
ircam_mips_le_magic[4] = {0x64, 0xa3, 0x03, 0x00},
|
|
ircam_mips_be_magic[4] = {0x00, 0x03, 0xa3, 0x64},
|
|
ircam_next_be_magic[4] = {0x64, 0xa3, 0x04, 0x00},
|
|
ircam_next_le_magic[4] = {0x00, 0x04, 0xa3, 0x64};
|
|
|
|
static const _AFfilesetup ircam_default_filesetup =
|
|
{
|
|
_AF_VALID_FILESETUP, /* valid */
|
|
AF_FILE_IRCAM, /* fileFormat */
|
|
true, /* trackSet */
|
|
true, /* instrumentSet */
|
|
true, /* miscellaneousSet */
|
|
1, /* trackCount */
|
|
NULL, /* tracks */
|
|
0, /* instrumentCount */
|
|
NULL, /* instruments */
|
|
0, /* miscellaneousCount */
|
|
NULL /* miscellaneous */
|
|
};
|
|
|
|
const int _af_ircam_compression_types[_AF_IRCAM_NUM_COMPTYPES] =
|
|
{
|
|
AF_COMPRESSION_G711_ULAW,
|
|
AF_COMPRESSION_G711_ALAW
|
|
};
|
|
|
|
bool IRCAMFile::recognize(File *fh)
|
|
{
|
|
uint8_t buffer[4];
|
|
|
|
fh->seek(0, File::SeekFromBeginning);
|
|
|
|
if (fh->read(buffer, 4) != 4)
|
|
return false;
|
|
|
|
/* Check to see if the file's magic number matches. */
|
|
if (!memcmp(buffer, ircam_vax_le_magic, 4) ||
|
|
!memcmp(buffer, ircam_vax_be_magic, 4) ||
|
|
!memcmp(buffer, ircam_sun_be_magic, 4) ||
|
|
!memcmp(buffer, ircam_sun_le_magic, 4) ||
|
|
!memcmp(buffer, ircam_mips_le_magic, 4) ||
|
|
!memcmp(buffer, ircam_mips_be_magic, 4) ||
|
|
!memcmp(buffer, ircam_next_be_magic, 4) ||
|
|
!memcmp(buffer, ircam_next_le_magic, 4))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
AFfilesetup IRCAMFile::completeSetup(AFfilesetup setup)
|
|
{
|
|
if (setup->trackSet && setup->trackCount != 1)
|
|
{
|
|
_af_error(AF_BAD_NUMTRACKS, "BICSF file must have 1 track");
|
|
return AF_NULL_FILESETUP;
|
|
}
|
|
|
|
TrackSetup *track = &setup->tracks[0];
|
|
if (track->sampleFormatSet)
|
|
{
|
|
if (track->f.isUnsigned())
|
|
{
|
|
_af_error(AF_BAD_SAMPFMT,
|
|
"BICSF format does not support unsigned data");
|
|
return AF_NULL_FILESETUP;
|
|
}
|
|
|
|
if (track->f.isSigned() &&
|
|
track->f.sampleWidth != 8 &&
|
|
track->f.sampleWidth != 16 &&
|
|
track->f.sampleWidth != 24 &&
|
|
track->f.sampleWidth != 32)
|
|
{
|
|
_af_error(AF_BAD_WIDTH,
|
|
"BICSF format supports only 8-, 16-, 24-, or 32-bit "
|
|
"two's complement audio data");
|
|
return AF_NULL_FILESETUP;
|
|
}
|
|
}
|
|
|
|
if (track->rateSet && track->f.sampleRate <= 0.0)
|
|
{
|
|
_af_error(AF_BAD_RATE,
|
|
"invalid sample rate %.30g for BICSF file",
|
|
track->f.sampleRate);
|
|
return AF_NULL_FILESETUP;
|
|
}
|
|
|
|
if (track->channelCountSet && track->f.channelCount != 1 &&
|
|
track->f.channelCount != 2 && track->f.channelCount != 4)
|
|
{
|
|
_af_error(AF_BAD_CHANNELS,
|
|
"invalid channel count (%d) for BICSF format "
|
|
"(1, 2, or 4 channels only)",
|
|
track->f.channelCount);
|
|
return AF_NULL_FILESETUP;
|
|
}
|
|
|
|
if (track->compressionSet &&
|
|
track->f.compressionType != AF_COMPRESSION_NONE &&
|
|
track->f.compressionType != AF_COMPRESSION_G711_ULAW &&
|
|
track->f.compressionType != AF_COMPRESSION_G711_ALAW)
|
|
{
|
|
_af_error(AF_BAD_NOT_IMPLEMENTED,
|
|
"BICSF format does not support compression type %d",
|
|
track->f.compressionType);
|
|
return AF_NULL_FILESETUP;
|
|
}
|
|
|
|
if (track->aesDataSet)
|
|
{
|
|
_af_error(AF_BAD_FILESETUP, "BICSF file cannot have AES data");
|
|
return AF_NULL_FILESETUP;
|
|
}
|
|
|
|
if (track->markersSet && track->markerCount != 0)
|
|
{
|
|
_af_error(AF_BAD_NUMMARKS, "BICSF format does not support markers");
|
|
return AF_NULL_FILESETUP;
|
|
}
|
|
|
|
if (setup->instrumentSet && setup->instrumentCount != 0)
|
|
{
|
|
_af_error(AF_BAD_NUMINSTS, "BICSF format does not support instruments");
|
|
return AF_NULL_FILESETUP;
|
|
}
|
|
|
|
/* XXXmpruett: We don't support miscellaneous chunks for now. */
|
|
if (setup->miscellaneousSet && setup->miscellaneousCount != 0)
|
|
{
|
|
_af_error(AF_BAD_NOT_IMPLEMENTED, "BICSF format does not currently support miscellaneous chunks");
|
|
return AF_NULL_FILESETUP;
|
|
}
|
|
|
|
return _af_filesetup_copy(setup, &ircam_default_filesetup, true);
|
|
}
|
|
|
|
status IRCAMFile::readInit(AFfilesetup setup)
|
|
{
|
|
float maxAmp = 1.0;
|
|
|
|
m_fh->seek(0, File::SeekFromBeginning);
|
|
|
|
uint8_t magic[4];
|
|
if (m_fh->read(magic, 4) != 4)
|
|
{
|
|
_af_error(AF_BAD_READ, "Could not read BICSF file header");
|
|
return AF_FAIL;
|
|
}
|
|
|
|
if (memcmp(magic, ircam_vax_le_magic, 4) != 0 &&
|
|
memcmp(magic, ircam_vax_be_magic, 4) != 0 &&
|
|
memcmp(magic, ircam_sun_be_magic, 4) != 0 &&
|
|
memcmp(magic, ircam_sun_le_magic, 4) != 0 &&
|
|
memcmp(magic, ircam_mips_le_magic, 4) != 0 &&
|
|
memcmp(magic, ircam_mips_be_magic, 4) != 0 &&
|
|
memcmp(magic, ircam_next_be_magic, 4) != 0 &&
|
|
memcmp(magic, ircam_next_le_magic, 4) != 0)
|
|
{
|
|
_af_error(AF_BAD_FILEFMT,
|
|
"file is not a BICSF file (bad magic number)");
|
|
return AF_FAIL;
|
|
}
|
|
|
|
// Check whether the file's magic number indicates little-endian data.
|
|
bool isLittleEndian = !memcmp(magic, ircam_vax_le_magic, 4) ||
|
|
!memcmp(magic, ircam_sun_le_magic, 4) ||
|
|
!memcmp(magic, ircam_mips_le_magic, 4) ||
|
|
!memcmp(magic, ircam_next_le_magic, 4);
|
|
|
|
setFormatByteOrder(isLittleEndian ? AF_BYTEORDER_LITTLEENDIAN :
|
|
AF_BYTEORDER_BIGENDIAN);
|
|
|
|
float rate;
|
|
readFloat(&rate);
|
|
uint32_t channels;
|
|
readU32(&channels);
|
|
uint32_t packMode;
|
|
readU32(&packMode);
|
|
|
|
Track *track = allocateTrack();
|
|
if (!track)
|
|
return AF_FAIL;
|
|
|
|
track->f.sampleRate = rate;
|
|
track->f.compressionType = AF_COMPRESSION_NONE;
|
|
|
|
if (isLittleEndian)
|
|
track->f.byteOrder = AF_BYTEORDER_LITTLEENDIAN;
|
|
else
|
|
track->f.byteOrder = AF_BYTEORDER_BIGENDIAN;
|
|
|
|
if (channels != 1 && channels != 2 && channels != 4)
|
|
{
|
|
_af_error(AF_BAD_FILEFMT,
|
|
"invalid channel count (%d) for BICSF format (1, 2, or 4 only)",
|
|
channels);
|
|
return AF_FAIL;
|
|
}
|
|
|
|
track->f.channelCount = channels;
|
|
|
|
track->f.framesPerPacket = 1;
|
|
|
|
switch (packMode)
|
|
{
|
|
case SF_CHAR:
|
|
track->f.byteOrder = _AF_BYTEORDER_NATIVE;
|
|
track->f.sampleFormat = AF_SAMPFMT_TWOSCOMP;
|
|
track->f.sampleWidth = 8;
|
|
break;
|
|
case SF_SHORT:
|
|
track->f.sampleFormat = AF_SAMPFMT_TWOSCOMP;
|
|
track->f.sampleWidth = 16;
|
|
break;
|
|
case SF_24INT:
|
|
track->f.sampleFormat = AF_SAMPFMT_TWOSCOMP;
|
|
track->f.sampleWidth = 24;
|
|
break;
|
|
case SF_LONG:
|
|
track->f.sampleFormat = AF_SAMPFMT_TWOSCOMP;
|
|
track->f.sampleWidth = 32;
|
|
break;
|
|
case SF_FLOAT:
|
|
track->f.sampleFormat = AF_SAMPFMT_FLOAT;
|
|
track->f.sampleWidth = 32;
|
|
break;
|
|
case SF_DOUBLE:
|
|
track->f.sampleFormat = AF_SAMPFMT_DOUBLE;
|
|
track->f.sampleWidth = 64;
|
|
break;
|
|
case SF_ALAW:
|
|
track->f.compressionType = AF_COMPRESSION_G711_ALAW;
|
|
track->f.byteOrder = _AF_BYTEORDER_NATIVE;
|
|
track->f.sampleFormat = AF_SAMPFMT_TWOSCOMP;
|
|
track->f.sampleWidth = 16;
|
|
track->f.bytesPerPacket = channels;
|
|
break;
|
|
case SF_ULAW:
|
|
track->f.compressionType = AF_COMPRESSION_G711_ULAW;
|
|
track->f.byteOrder = _AF_BYTEORDER_NATIVE;
|
|
track->f.sampleFormat = AF_SAMPFMT_TWOSCOMP;
|
|
track->f.sampleWidth = 16;
|
|
track->f.bytesPerPacket = channels;
|
|
break;
|
|
default:
|
|
_af_error(AF_BAD_NOT_IMPLEMENTED,
|
|
"BICSF data format %d not supported", packMode);
|
|
return AF_FAIL;
|
|
}
|
|
|
|
if (track->f.isUncompressed())
|
|
track->f.computeBytesPerPacketPCM();
|
|
|
|
if (_af_set_sample_format(&track->f, track->f.sampleFormat,
|
|
track->f.sampleWidth) == AF_FAIL)
|
|
{
|
|
return AF_FAIL;
|
|
}
|
|
|
|
if (track->f.sampleFormat == AF_SAMPFMT_FLOAT)
|
|
track->f.pcm.slope = maxAmp;
|
|
|
|
track->data_size = m_fh->length() - SIZEOF_BSD_HEADER;
|
|
track->computeTotalFileFrames();
|
|
|
|
track->fpos_first_frame = SIZEOF_BSD_HEADER;
|
|
track->nextfframe = 0;
|
|
track->fpos_next_frame = track->fpos_first_frame;
|
|
|
|
return AF_SUCCEED;
|
|
}
|
|
|
|
/* We write IRCAM files using the native byte order. */
|
|
status IRCAMFile::writeInit(AFfilesetup setup)
|
|
{
|
|
if (initFromSetup(setup) == AF_FAIL)
|
|
return AF_FAIL;
|
|
|
|
uint32_t dataOffset = SIZEOF_BSD_HEADER;
|
|
|
|
Track *track = getTrack();
|
|
track->totalfframes = 0;
|
|
track->fpos_first_frame = dataOffset;
|
|
track->nextfframe = 0;
|
|
track->fpos_next_frame = track->fpos_first_frame;
|
|
|
|
/* Choose the magic number appropriate for the byte order. */
|
|
const uint8_t *magic;
|
|
#ifdef WORDS_BIGENDIAN
|
|
magic = ircam_sun_be_magic;
|
|
#else
|
|
magic = ircam_vax_le_magic;
|
|
#endif
|
|
|
|
uint32_t channels = track->f.channelCount;
|
|
float rate = track->f.sampleRate;
|
|
|
|
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,
|
|
"unsupported compression type %d in IRCAM sound file",
|
|
track->f.compressionType);
|
|
return AF_FAIL;
|
|
}
|
|
|
|
uint32_t packMode = 0;
|
|
if (track->f.compressionType == AF_COMPRESSION_G711_ULAW)
|
|
packMode = SF_ULAW;
|
|
else if (track->f.compressionType == AF_COMPRESSION_G711_ALAW)
|
|
packMode = SF_ALAW;
|
|
else if (track->f.isSigned())
|
|
{
|
|
switch (track->f.bytesPerSample(false))
|
|
{
|
|
case 1: packMode = SF_CHAR; break;
|
|
case 2: packMode = SF_SHORT; break;
|
|
case 3: packMode = SF_24INT; break;
|
|
case 4: packMode = SF_LONG; break;
|
|
default:
|
|
_af_error(AF_BAD_SAMPFMT,
|
|
"unsupported sample width %d for two's complement BICSF file",
|
|
track->f.sampleWidth);
|
|
return AF_FAIL;
|
|
}
|
|
}
|
|
else if (track->f.isFloat())
|
|
{
|
|
if (track->f.sampleWidth == 32)
|
|
packMode = SF_FLOAT;
|
|
else if (track->f.sampleWidth == 64)
|
|
packMode = SF_DOUBLE;
|
|
else
|
|
{
|
|
_af_error(AF_BAD_SAMPFMT,
|
|
"unsupported sample width %d for BICSF file",
|
|
track->f.sampleWidth);
|
|
return AF_FAIL;
|
|
}
|
|
}
|
|
else if (track->f.isUnsigned())
|
|
{
|
|
_af_error(AF_BAD_SAMPFMT, "BICSF format does not support unsigned integer audio data");
|
|
return AF_FAIL;
|
|
}
|
|
|
|
m_fh->seek(0, File::SeekFromBeginning);
|
|
m_fh->write(magic, 4);
|
|
writeFloat(&rate);
|
|
writeU32(&channels);
|
|
writeU32(&packMode);
|
|
|
|
/* Zero the entire description block. */
|
|
uint8_t zeros[SIZEOF_BSD_HEADER];
|
|
memset(zeros, 0, SIZEOF_BSD_HEADER);
|
|
m_fh->write(zeros, SIZEOF_BSD_HEADER - 4*4);
|
|
|
|
return AF_SUCCEED;
|
|
}
|
|
|
|
status IRCAMFile::update()
|
|
{
|
|
return AF_SUCCEED;
|
|
}
|