334 lines
8.0 KiB
C++
334 lines
8.0 KiB
C++
|
/*
|
||
|
Audio File Library
|
||
|
Copyright (C) 2012, 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 "SampleVision.h"
|
||
|
|
||
|
#include "File.h"
|
||
|
#include "Setup.h"
|
||
|
#include "Track.h"
|
||
|
#include "afinternal.h"
|
||
|
#include "util.h"
|
||
|
#include <string.h>
|
||
|
|
||
|
static const char *kSMPMagic = "SOUND SAMPLE DATA ";
|
||
|
static const unsigned kSMPMagicLength = 18;
|
||
|
static const char *kSMPVersion = "2.1 ";
|
||
|
static const unsigned kSMPVersionLength = 4;
|
||
|
static const unsigned kSMPNameLength = 30;
|
||
|
static const unsigned kSMPCommentLength = 60;
|
||
|
static const unsigned kSMPMarkerNameLength = 10;
|
||
|
static const uint32_t kSMPInvalidSamplePosition = 0xffffffffu;
|
||
|
|
||
|
static const uint8_t kSMPMIDIUnityPlaybackNote = 60;
|
||
|
|
||
|
static const _AFfilesetup sSampleVisionDefaultFileSetup =
|
||
|
{
|
||
|
_AF_VALID_FILESETUP,
|
||
|
AF_FILE_SAMPLEVISION,
|
||
|
true, // trackSet
|
||
|
true, // instrumentSet
|
||
|
true, // miscellaneousSet
|
||
|
1, // trackCount
|
||
|
NULL, // tracks
|
||
|
1, // instrumentCount
|
||
|
NULL, // instruments
|
||
|
0, // miscellaneousCount
|
||
|
NULL // miscellaneous
|
||
|
};
|
||
|
|
||
|
static void trimTrailingSpaces(char *s)
|
||
|
{
|
||
|
int n = strlen(s);
|
||
|
if (!n)
|
||
|
return;
|
||
|
while (--n > 0 && s[n] == ' ')
|
||
|
;
|
||
|
s[n+1] = '\0';
|
||
|
}
|
||
|
|
||
|
SampleVisionFile::SampleVisionFile() :
|
||
|
m_frameCountOffset(-1)
|
||
|
{
|
||
|
setFormatByteOrder(AF_BYTEORDER_LITTLEENDIAN);
|
||
|
}
|
||
|
|
||
|
SampleVisionFile::~SampleVisionFile()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
bool SampleVisionFile::recognize(File *fh)
|
||
|
{
|
||
|
fh->seek(0, File::SeekFromBeginning);
|
||
|
char magic[kSMPMagicLength];
|
||
|
if (fh->read(magic, kSMPMagicLength) != (ssize_t) kSMPMagicLength)
|
||
|
return false;
|
||
|
return !strncmp(magic, kSMPMagic, kSMPMagicLength);
|
||
|
}
|
||
|
|
||
|
AFfilesetup SampleVisionFile::completeSetup(AFfilesetup setup)
|
||
|
{
|
||
|
if (setup->trackSet && setup->trackCount != 1)
|
||
|
{
|
||
|
_af_error(AF_BAD_NUMTRACKS, "SampleVision file must have 1 track");
|
||
|
return AF_NULL_FILESETUP;
|
||
|
}
|
||
|
|
||
|
TrackSetup *track = &setup->tracks[0];
|
||
|
if (track->sampleFormatSet)
|
||
|
{
|
||
|
if (!track->f.isSigned() || track->f.sampleWidth != 16)
|
||
|
{
|
||
|
_af_error(AF_BAD_SAMPFMT,
|
||
|
"SampleVision format supports only 16-bit signed integer audio data");
|
||
|
return AF_NULL_FILESETUP;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
_af_set_sample_format(&track->f, AF_SAMPFMT_TWOSCOMP,
|
||
|
track->f.sampleWidth);
|
||
|
|
||
|
if (track->byteOrderSet && track->f.byteOrder != AF_BYTEORDER_LITTLEENDIAN)
|
||
|
{
|
||
|
// Treat error as correctable.
|
||
|
_af_error(AF_BAD_BYTEORDER, "SampleVision supports only little-endian data");
|
||
|
}
|
||
|
|
||
|
track->f.byteOrder = AF_BYTEORDER_LITTLEENDIAN;
|
||
|
|
||
|
if (track->compressionSet && !track->f.isUncompressed())
|
||
|
{
|
||
|
_af_error(AF_BAD_COMPTYPE, "SampleVision does not support compressed audio data");
|
||
|
return AF_NULL_FILESETUP;
|
||
|
}
|
||
|
|
||
|
if (track->markersSet && track->markerCount)
|
||
|
{
|
||
|
_af_error(AF_BAD_NUMMARKS, "SampleVision does not support markers");
|
||
|
return AF_NULL_FILESETUP;
|
||
|
}
|
||
|
|
||
|
if (track->aesDataSet)
|
||
|
{
|
||
|
_af_error(AF_BAD_FILESETUP, "SampleVision does not support AES data");
|
||
|
return AF_NULL_FILESETUP;
|
||
|
}
|
||
|
|
||
|
return _af_filesetup_copy(setup, &sSampleVisionDefaultFileSetup, true);
|
||
|
}
|
||
|
|
||
|
status SampleVisionFile::readInit(AFfilesetup)
|
||
|
{
|
||
|
m_fh->seek(0, File::SeekFromBeginning);
|
||
|
|
||
|
char magic[kSMPMagicLength];
|
||
|
if (m_fh->read(magic, kSMPMagicLength) != (ssize_t) kSMPMagicLength)
|
||
|
return AF_FAIL;
|
||
|
if (strncmp(magic, kSMPMagic, kSMPMagicLength) != 0)
|
||
|
return AF_FAIL;
|
||
|
|
||
|
char version[kSMPVersionLength];
|
||
|
if (m_fh->read(version, kSMPVersionLength) != (ssize_t) kSMPVersionLength)
|
||
|
return AF_FAIL;
|
||
|
if (strncmp(version, kSMPVersion, kSMPVersionLength) != 0)
|
||
|
return AF_FAIL;
|
||
|
|
||
|
Track *track = allocateTrack();
|
||
|
|
||
|
char name[kSMPNameLength + 1];
|
||
|
m_fh->read(name, kSMPNameLength);
|
||
|
name[kSMPNameLength] = '\0';
|
||
|
trimTrailingSpaces(name);
|
||
|
if (strlen(name) > 0)
|
||
|
addMiscellaneous(AF_MISC_NAME, name);
|
||
|
|
||
|
char comment[kSMPCommentLength + 1];
|
||
|
m_fh->read(comment, kSMPCommentLength);
|
||
|
comment[kSMPCommentLength] = '\0';
|
||
|
trimTrailingSpaces(comment);
|
||
|
if (strlen(comment) > 0)
|
||
|
addMiscellaneous(AF_MISC_COMMENT, comment);
|
||
|
|
||
|
uint32_t frameCount;
|
||
|
readU32(&frameCount);
|
||
|
track->totalfframes = frameCount;
|
||
|
track->fpos_first_frame = m_fh->tell();
|
||
|
track->data_size = 2 * frameCount;
|
||
|
|
||
|
m_fh->seek(track->data_size, File::SeekFromCurrent);
|
||
|
|
||
|
uint16_t reserved;
|
||
|
readU16(&reserved);
|
||
|
|
||
|
parseLoops();
|
||
|
parseMarkers();
|
||
|
|
||
|
uint8_t midiNote;
|
||
|
uint32_t sampleRate;
|
||
|
uint32_t smpteOffset;
|
||
|
uint32_t cycleLength;
|
||
|
|
||
|
readU8(&midiNote);
|
||
|
readU32(&sampleRate);
|
||
|
readU32(&smpteOffset);
|
||
|
readU32(&cycleLength);
|
||
|
|
||
|
track->f.byteOrder = AF_BYTEORDER_LITTLEENDIAN;
|
||
|
track->f.sampleRate = sampleRate;
|
||
|
track->f.channelCount = 1;
|
||
|
track->f.compressionType = AF_COMPRESSION_NONE;
|
||
|
track->f.framesPerPacket = 1;
|
||
|
_af_set_sample_format(&track->f, AF_SAMPFMT_TWOSCOMP, 16);
|
||
|
track->f.computeBytesPerPacketPCM();
|
||
|
|
||
|
return AF_SUCCEED;
|
||
|
}
|
||
|
|
||
|
status SampleVisionFile::parseLoops()
|
||
|
{
|
||
|
for (int i=0; i<8; i++)
|
||
|
{
|
||
|
uint32_t startFrame, endFrame;
|
||
|
uint8_t type;
|
||
|
uint16_t count;
|
||
|
readU32(&startFrame);
|
||
|
readU32(&endFrame);
|
||
|
readU8(&type);
|
||
|
readU16(&count);
|
||
|
}
|
||
|
return AF_SUCCEED;
|
||
|
}
|
||
|
|
||
|
status SampleVisionFile::parseMarkers()
|
||
|
{
|
||
|
for (int i=0; i<8; i++)
|
||
|
{
|
||
|
char name[kSMPMarkerNameLength + 1];
|
||
|
m_fh->read(name, kSMPMarkerNameLength);
|
||
|
name[kSMPMarkerNameLength] = '\0';
|
||
|
uint32_t position;
|
||
|
readU32(&position);
|
||
|
}
|
||
|
return AF_SUCCEED;
|
||
|
}
|
||
|
|
||
|
void SampleVisionFile::addMiscellaneous(int type, const char *data)
|
||
|
{
|
||
|
m_miscellaneousCount++;
|
||
|
m_miscellaneous = (Miscellaneous *) _af_realloc(m_miscellaneous,
|
||
|
m_miscellaneousCount * sizeof (Miscellaneous));
|
||
|
|
||
|
Miscellaneous &m = m_miscellaneous[m_miscellaneousCount - 1];
|
||
|
m.id = m_miscellaneousCount;
|
||
|
m.type = type;
|
||
|
m.size = strlen(data);
|
||
|
m.position = 0;
|
||
|
m.buffer = _af_malloc(m.size);
|
||
|
memcpy(m.buffer, data, m.size);
|
||
|
}
|
||
|
|
||
|
status SampleVisionFile::writeInit(AFfilesetup setup)
|
||
|
{
|
||
|
if (initFromSetup(setup) == AF_FAIL)
|
||
|
return AF_FAIL;
|
||
|
|
||
|
m_fh->write(kSMPMagic, kSMPMagicLength);
|
||
|
m_fh->write(kSMPVersion, kSMPVersionLength);
|
||
|
|
||
|
char name[kSMPNameLength + 1];
|
||
|
char comment[kSMPCommentLength + 1];
|
||
|
memset(name, ' ', kSMPNameLength);
|
||
|
memset(comment, ' ', kSMPCommentLength);
|
||
|
m_fh->write(name, kSMPNameLength);
|
||
|
m_fh->write(comment, kSMPCommentLength);
|
||
|
|
||
|
uint32_t frameCount = 0;
|
||
|
m_frameCountOffset = m_fh->tell();
|
||
|
writeU32(&frameCount);
|
||
|
|
||
|
Track *track = getTrack();
|
||
|
track->fpos_first_frame = m_fh->tell();
|
||
|
|
||
|
return AF_SUCCEED;
|
||
|
}
|
||
|
|
||
|
status SampleVisionFile::update()
|
||
|
{
|
||
|
m_fh->seek(m_frameCountOffset, File::SeekFromBeginning);
|
||
|
Track *track = getTrack();
|
||
|
uint32_t frameCount = track->totalfframes;
|
||
|
writeU32(&frameCount);
|
||
|
writeTrailer();
|
||
|
return AF_SUCCEED;
|
||
|
}
|
||
|
|
||
|
status SampleVisionFile::writeTrailer()
|
||
|
{
|
||
|
Track *track = getTrack();
|
||
|
|
||
|
m_fh->seek(track->fpos_after_data, File::SeekFromBeginning);
|
||
|
|
||
|
uint16_t reserved = 0;
|
||
|
writeU16(&reserved);
|
||
|
|
||
|
writeLoops();
|
||
|
writeMarkers();
|
||
|
|
||
|
uint8_t midiNote = kSMPMIDIUnityPlaybackNote;
|
||
|
uint32_t sampleRate = track->f.sampleRate;
|
||
|
uint32_t smpteOffset = 0;
|
||
|
uint32_t cycleLength = 0;
|
||
|
|
||
|
writeU8(&midiNote);
|
||
|
writeU32(&sampleRate);
|
||
|
writeU32(&smpteOffset);
|
||
|
writeU32(&cycleLength);
|
||
|
return AF_SUCCEED;
|
||
|
}
|
||
|
|
||
|
status SampleVisionFile::writeLoops()
|
||
|
{
|
||
|
for (int i=0; i<8; i++)
|
||
|
{
|
||
|
uint32_t startFrame = kSMPInvalidSamplePosition, endFrame = 0;
|
||
|
uint8_t type = 0;
|
||
|
uint16_t count = 0;
|
||
|
writeU32(&startFrame);
|
||
|
writeU32(&endFrame);
|
||
|
writeU8(&type);
|
||
|
writeU16(&count);
|
||
|
}
|
||
|
return AF_SUCCEED;
|
||
|
}
|
||
|
|
||
|
status SampleVisionFile::writeMarkers()
|
||
|
{
|
||
|
for (int i=0; i<8; i++)
|
||
|
{
|
||
|
char name[kSMPMarkerNameLength + 1];
|
||
|
memset(name, ' ', kSMPMarkerNameLength);
|
||
|
name[kSMPMarkerNameLength] = '\0';
|
||
|
m_fh->write(name, kSMPMarkerNameLength);
|
||
|
uint32_t position = kSMPInvalidSamplePosition;
|
||
|
writeU32(&position);
|
||
|
}
|
||
|
return AF_SUCCEED;
|
||
|
}
|