/* Audio File Library Copyright (C) 2012, Michael Pruett 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 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; }