diff options
Diffstat (limited to 'src/sound/RIFFAudioFile.cpp')
-rw-r--r-- | src/sound/RIFFAudioFile.cpp | 686 |
1 files changed, 686 insertions, 0 deletions
diff --git a/src/sound/RIFFAudioFile.cpp b/src/sound/RIFFAudioFile.cpp new file mode 100644 index 0000000..c34435f --- /dev/null +++ b/src/sound/RIFFAudioFile.cpp @@ -0,0 +1,686 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent <[email protected]>, + Chris Cannam <[email protected]>, + Richard Bown <[email protected]> + + The moral right of the authors to claim authorship of this work + has been asserted. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "RIFFAudioFile.h" +#include "RealTime.h" +#include "Profiler.h" + +using std::cout; +using std::cerr; +using std::endl; + +//#define DEBUG_RIFF + + +namespace Rosegarden +{ + +RIFFAudioFile::RIFFAudioFile(unsigned int id, + const std::string &name, + const std::string &fileName): + AudioFile(id, name, fileName), + m_subFormat(PCM), + m_bytesPerSecond(0), + m_bytesPerFrame(0) +{} + +RIFFAudioFile::RIFFAudioFile(const std::string &fileName, + unsigned int channels = 1, + unsigned int sampleRate = 48000, + unsigned int bytesPerSecond = 6000, + unsigned int bytesPerFrame = 2, + unsigned int bitsPerSample = 16): + AudioFile(0, "", fileName) +{ + m_bitsPerSample = bitsPerSample; + m_sampleRate = sampleRate; + m_bytesPerSecond = bytesPerSecond; + m_bytesPerFrame = bytesPerFrame; + m_channels = channels; + + if (bitsPerSample == 16) + m_subFormat = PCM; + else if (bitsPerSample == 32) + m_subFormat = FLOAT; + else + throw(BadSoundFileException(m_fileName, "Rosegarden currently only supports 16 or 32-bit PCM or IEEE floating-point RIFF files for writing")); + +} + +RIFFAudioFile::~RIFFAudioFile() +{} + + +// Show some stats on this file +// +void +RIFFAudioFile::printStats() +{ + cout << "filename : " << m_fileName << endl + << "channels : " << m_channels << endl + << "sample rate : " << m_sampleRate << endl + << "bytes per second : " << m_bytesPerSecond << endl + << "bits per sample : " << m_bitsPerSample << endl + << "bytes per frame : " << m_bytesPerFrame << endl + << "file length : " << m_fileSize << " bytes" << endl + << endl; +} + +bool +RIFFAudioFile::appendSamples(const std::string &buffer) +{ + /* + if (m_outFile == 0 || m_type != WAV) + return false; + */ + + // write out + putBytes(m_outFile, buffer); + + return true; +} + +bool +RIFFAudioFile::appendSamples(const char *buf, unsigned int frames) +{ + putBytes(m_outFile, buf, frames * m_bytesPerFrame); + return true; +} + +// scan on from a descriptor position +bool +RIFFAudioFile::scanForward(std::ifstream *file, const RealTime &time) +{ + // sanity + if (file == 0) + return false; + + unsigned int totalSamples = m_sampleRate * time.sec + + ( ( m_sampleRate * time.usec() ) / 1000000 ); + unsigned int totalBytes = totalSamples * m_bytesPerFrame; + + m_loseBuffer = true; + + // do the seek + file->seekg(totalBytes, std::ios::cur); + + if (file->eof()) + return false; + + return true; +} + +bool +RIFFAudioFile::scanForward(const RealTime &time) +{ + if (*m_inFile) + return scanForward(m_inFile, time); + else + return false; +} + +bool +RIFFAudioFile::scanTo(const RealTime &time) +{ + if (*m_inFile) + return scanTo(m_inFile, time); + else + return false; + +} + +bool +RIFFAudioFile::scanTo(std::ifstream *file, const RealTime &time) +{ + // sanity + if (file == 0) + return false; + + // whatever we do here we invalidate the read buffer + // + m_loseBuffer = true; + + file->clear(); + + // seek past header - don't hardcode this - use the file format + // spec to get header length and then scoot to that. + // + file->seekg(16, std::ios::beg); + + unsigned int lengthOfFormat = 0; + + try { + lengthOfFormat = getIntegerFromLittleEndian(getBytes(file, 4)); + file->seekg(lengthOfFormat, std::ios::cur); + + // check we've got data chunk start + std::string chunkName; + int chunkLength = 0; + + while ((chunkName = getBytes(file, 4)) != "data") { + if (file->eof()) { + std::cerr << "RIFFAudioFile::scanTo(): failed to find data " + << std::endl; + return false; + } +//#ifdef DEBUG_RIFF + std::cerr << "RIFFAudioFile::scanTo(): skipping chunk: " + << chunkName << std::endl; +//#endif + chunkLength = getIntegerFromLittleEndian(getBytes(file, 4)); + if (chunkLength < 0) { + std::cerr << "RIFFAudioFile::scanTo(): negative chunk length " + << chunkLength << " for chunk " << chunkName << std::endl; + return false; + } + file->seekg(chunkLength, std::ios::cur); + } + + // get the length of the data chunk, and scan past it as a side-effect + chunkLength = getIntegerFromLittleEndian(getBytes(file, 4)); +#ifdef DEBUG_RIFF + + std::cout << "RIFFAudioFile::scanTo() - data chunk size = " + << chunkLength << std::endl; +#endif + + } catch (BadSoundFileException s) { +#ifdef DEBUG_RIFF + std::cerr << "RIFFAudioFile::scanTo - EXCEPTION - \"" + << s.getMessage() << "\"" << std::endl; +#endif + + return false; + } + + // Ok, we're past all the header information in the data chunk. + // Now, how much do we scan forward? + // + size_t totalFrames = RealTime::realTime2Frame(time, m_sampleRate); + + unsigned int totalBytes = totalFrames * m_bytesPerFrame; + + // When using seekg we have to keep an eye on the boundaries ourselves + // + if (totalBytes > m_fileSize - (lengthOfFormat + 16 + 8)) { +#ifdef DEBUG_RIFF + std::cerr << "RIFFAudioFile::scanTo() - attempting to move past end of " + << "data block" << std::endl; +#endif + + return false; + } + +#ifdef DEBUG_RIFF + std::cout << "RIFFAudioFile::scanTo - seeking to " << time + << " (" << totalBytes << " bytes from current " << file->tellg() + << ")" << std::endl; +#endif + + file->seekg(totalBytes, std::ios::cur); + + return true; +} + +// Get a certain number of sample frames - a frame is a set +// of samples (all channels) for a given sample quanta. +// +// For example, getting one frame of 16-bit stereo will return +// four bytes of data (two per channel). +// +// +std::string +RIFFAudioFile::getSampleFrames(std::ifstream *file, unsigned int frames) +{ + // sanity + if (file == 0) + return std::string(""); + + // Bytes per sample already takes into account the number + // of channels we're using + // + long totalBytes = frames * m_bytesPerFrame; + + try { + return getBytes(file, totalBytes); + } catch (BadSoundFileException s) { + return ""; + } +} + +unsigned int +RIFFAudioFile::getSampleFrames(std::ifstream *file, char *buf, + unsigned int frames) +{ + if (file == 0) + return 0; + try { + return getBytes(file, buf, frames * m_bytesPerFrame) / m_bytesPerFrame; + } catch (BadSoundFileException s) { + return 0; + } +} + +std::string +RIFFAudioFile::getSampleFrames(unsigned int frames) +{ + if (*m_inFile) { + return getSampleFrames(m_inFile, frames); + } else { + return std::string(""); + } +} + +// Return a slice of frames over a time period +// +std::string +RIFFAudioFile::getSampleFrameSlice(std::ifstream *file, const RealTime &time) +{ + // sanity + if (file == 0) + return std::string(""); + + long totalFrames = RealTime::realTime2Frame(time, m_sampleRate); + long totalBytes = totalFrames * m_bytesPerFrame; + + try { + return getBytes(file, totalBytes); + } catch (BadSoundFileException s) { + return ""; + } +} + +std::string +RIFFAudioFile::getSampleFrameSlice(const RealTime &time) +{ + if (*m_inFile) { + return getSampleFrameSlice(m_inFile, time); + } else { + return std::string(""); + } +} + +RealTime +RIFFAudioFile::getLength() +{ + // Fixed header size = 44 but prove by getting it from the file too + // + unsigned int headerLength = 44; + + if (m_inFile) { + m_inFile->seekg(16, std::ios::beg); + headerLength = getIntegerFromLittleEndian(getBytes(m_inFile, 4)); + m_inFile->seekg(headerLength, std::ios::cur); + headerLength += (16 + 8); + } + + if (!m_bytesPerFrame || !m_sampleRate) return RealTime::zeroTime; + + double frames = (m_fileSize - headerLength) / m_bytesPerFrame; + double seconds = frames / ((double)m_sampleRate); + + int secs = int(seconds); + int nsecs = int((seconds - secs) * 1000000000.0); + + return RealTime(secs, nsecs); +} + + +// The RIFF file format chunk defines our internal meta data. +// +// Courtesy of: +// http://www.technology.niagarac.on.ca/courses/comp630/WavFileFormat.html +// +// 'The WAV file itself consists of three "chunks" of information: +// The RIFF chunk which identifies the file as a WAV file, The FORMAT +// chunk which identifies parameters such as sample rate and the DATA +// chunk which contains the actual data (samples).' +// +// +void +RIFFAudioFile::readFormatChunk() +{ + if (m_inFile == 0) + return ; + + m_loseBuffer = true; + + // seek to beginning + m_inFile->seekg(0, std::ios::beg); + + // get the header string + // + std::string hS = getBytes(36); + + // Look for the RIFF identifier and bomb out if we don't find it + // +#if (__GNUC__ < 3) + + if (hS.compare(AUDIO_RIFF_ID, 0, 4) != 0) +#else + + if (hS.compare(0, 4, AUDIO_RIFF_ID) != 0) +#endif + + { +#ifdef DEBUG_RIFF + std::cerr << "RIFFAudioFile::readFormatChunk - " + << "can't find RIFF identifier\n"; +#endif + + throw(BadSoundFileException(m_fileName, "RIFFAudioFile::readFormatChunk - can't find RIFF identifier")); + } + + // Look for the WAV identifier + // +#if (__GNUC__ < 3) + if (hS.compare(AUDIO_WAVE_ID, 8, 4) != 0) +#else + + if (hS.compare(8, 4, AUDIO_WAVE_ID) != 0) +#endif + + { +#ifdef DEBUG_RIFF + std::cerr << "Can't find WAV identifier\n"; +#endif + + throw(BadSoundFileException(m_fileName, "Can't find WAV identifier")); + } + + // Look for the FORMAT identifier - note that this doesn't actually + // have to be in the first chunk we come across, but for the moment + // this is the only place we check for it because I'm lazy. + // + // +#if (__GNUC__ < 3) + if (hS.compare(AUDIO_FORMAT_ID, 12, 4) != 0) +#else + + if (hS.compare(12, 4, AUDIO_FORMAT_ID) != 0) +#endif + + { +#ifdef DEBUG_RIFF + std::cerr << "Can't find FORMAT identifier\n"; +#endif + + throw(BadSoundFileException(m_fileName, "Can't find FORMAT identifier")); + } + + // Little endian conversion of length bytes into file length + // (add on eight for RIFF id and length field and compare to + // real file size). + // + unsigned int length = getIntegerFromLittleEndian(hS.substr(4, 4)) + 8; + + if (length != m_fileSize) { + std::cerr << "WARNING: RIFFAudioFile: incorrect length (" + << length << ", file size is " << m_fileSize << "), ignoring" + << std::endl; + length = m_fileSize; + } + + // Check the format length + // + unsigned int lengthOfFormat = getIntegerFromLittleEndian(hS.substr(16, 4)); + + // Make sure we step to the end of the format chunk ignoring the + // tail if it exists + // + if (lengthOfFormat > 0x10) { +#ifdef DEBUG_RIFF + std::cerr << "RIFFAudioFile::readFormatChunk - " + << "extended Format Chunk (" << lengthOfFormat << ")" + << std::endl; +#endif + + // ignore any overlapping bytes + m_inFile->seekg(lengthOfFormat - 0x10, std::ios::cur); + } else if (lengthOfFormat < 0x10) { +#ifdef DEBUG_RIFF + std::cerr << "RIFFAudioFile::readFormatChunk - " + << "truncated Format Chunk (" << lengthOfFormat << ")" + << std::endl; +#endif + + m_inFile->seekg(lengthOfFormat - 0x10, std::ios::cur); + //throw(BadSoundFileException(m_fileName, "Format chunk too short")); + } + + + // Check sub format - we support PCM or IEEE floating point. + // + unsigned int subFormat = getIntegerFromLittleEndian(hS.substr(20, 2)); + + if (subFormat == 0x01) { + m_subFormat = PCM; + } else if (subFormat == 0x03) { + m_subFormat = FLOAT; + } else { + throw(BadSoundFileException(m_fileName, "Rosegarden currently only supports PCM or IEEE floating-point RIFF files")); + } + + // We seem to have a good looking .WAV file - extract the + // sample information and populate this locally + // + unsigned int channelNumbers = getIntegerFromLittleEndian(hS.substr(22, 2)); + + switch (channelNumbers) { + case 0x01: + case 0x02: + m_channels = channelNumbers; + break; + + default: { + throw(BadSoundFileException(m_fileName, "Unsupported number of channels")); + } + break; + } + + // Now the rest of the information + // + m_sampleRate = getIntegerFromLittleEndian(hS.substr(24, 4)); + m_bytesPerSecond = getIntegerFromLittleEndian(hS.substr(28, 4)); + m_bytesPerFrame = getIntegerFromLittleEndian(hS.substr(32, 2)); + m_bitsPerSample = getIntegerFromLittleEndian(hS.substr(34, 2)); + + if (m_subFormat == PCM) { + if (m_bitsPerSample != 8 && m_bitsPerSample != 16 && m_bitsPerSample != 24) { + throw BadSoundFileException("Rosegarden currently only supports 8-, 16- or 24-bit PCM in RIFF files"); + } + } else if (m_subFormat == FLOAT) { + if (m_bitsPerSample != 32) { + throw BadSoundFileException("Rosegarden currently only supports 32-bit floating-point in RIFF files"); + } + } + + // printStats(); + +} + +// Write out the format chunk from our internal data +// +void +RIFFAudioFile::writeFormatChunk() +{ + if (m_outFile == 0 || m_type != WAV) + return ; + + std::string outString; + + // RIFF type is all we support for the moment + outString += AUDIO_RIFF_ID; + + // Now write the total length of the file minus these first 8 bytes. + // We won't know this until we've finished recording the file. + // + outString += "0000"; + + // WAV file is all we support + // + outString += AUDIO_WAVE_ID; + + // Begin the format chunk + outString += AUDIO_FORMAT_ID; + + // length + //cout << "LENGTH = " << getLittleEndianFromInteger(0x10, 4) << endl; + outString += getLittleEndianFromInteger(0x10, 4); + + // 1 for PCM, 3 for float + if (m_subFormat == PCM) { + outString += getLittleEndianFromInteger(0x01, 2); + } else { + outString += getLittleEndianFromInteger(0x03, 2); + } + + // channel + outString += getLittleEndianFromInteger(m_channels, 2); + + // sample rate + outString += getLittleEndianFromInteger(m_sampleRate, 4); + + // bytes per second + outString += getLittleEndianFromInteger(m_bytesPerSecond, 4); + + // bytes per sample + outString += getLittleEndianFromInteger(m_bytesPerFrame, 2); + + // bits per sample + outString += getLittleEndianFromInteger(m_bitsPerSample, 2); + + // Now mark the beginning of the "data" chunk and leave the file + // open for writing. + outString += "data"; + + // length of data to follow - again needs to be written after + // we've completed the file. + // + outString += "0000"; + + // write out + // + putBytes(m_outFile, outString); +} + + +AudioFileType +RIFFAudioFile::identifySubType(const std::string &filename) +{ + std::ifstream *testFile = + new std::ifstream(filename.c_str(), std::ios::in | std::ios::binary); + + if (!(*testFile)) + return UNKNOWN; + + std::string hS; + unsigned int numberOfBytes = 36; + char *bytes = new char[numberOfBytes]; + + testFile->read(bytes, numberOfBytes); + for (unsigned int i = 0; i < numberOfBytes; i++) + hS += (unsigned char)bytes[i]; + + AudioFileType type = UNKNOWN; + + // Test for BWF first because it's an extension of a plain WAV + // +#if (__GNUC__ < 3) + + if (hS.compare(AUDIO_RIFF_ID, 0, 4) == 0 && + hS.compare(AUDIO_WAVE_ID, 8, 4) == 0 && + hS.compare(AUDIO_BWF_ID, 12, 4) == 0) +#else + + if (hS.compare(0, 4, AUDIO_RIFF_ID) == 0 && + hS.compare(8, 4, AUDIO_WAVE_ID) == 0 && + hS.compare(12, 4, AUDIO_BWF_ID) == 0) +#endif + + { + type = BWF; + } + // Now for a WAV +#if (__GNUC__ < 3) + else if (hS.compare(AUDIO_RIFF_ID, 0, 4) == 0 && + hS.compare(AUDIO_WAVE_ID, 8, 4) == 0) +#else + + else if (hS.compare(0, 4, AUDIO_RIFF_ID) == 0 && + hS.compare(8, 4, AUDIO_WAVE_ID) == 0) +#endif + + { + type = WAV; + } else + type = UNKNOWN; + + testFile->close(); + delete [] bytes; + + return type; +} + +float +RIFFAudioFile::convertBytesToSample(const unsigned char *ubuf) +{ + switch (getBitsPerSample()) { + + case 8: { + // WAV stores 8-bit samples unsigned, other sizes signed. + return (float)(ubuf[0] - 128.0) / 128.0; + } + + case 16: { + // Two's complement little-endian 16-bit integer. + // We convert endianness (if necessary) but assume 16-bit short. + unsigned char b2 = ubuf[0]; + unsigned char b1 = ubuf[1]; + unsigned int bits = (b1 << 8) + b2; + return (float)(short(bits)) / 32767.0; + } + + case 24: { + // Two's complement little-endian 24-bit integer. + // Again, convert endianness but assume 32-bit int. + unsigned char b3 = ubuf[0]; + unsigned char b2 = ubuf[1]; + unsigned char b1 = ubuf[2]; + // Rotate 8 bits too far in order to get the sign bit + // in the right place; this gives us a 32-bit value, + // hence the larger float divisor + unsigned int bits = (b1 << 24) + (b2 << 16) + (b3 << 8); + return (float)(int(bits)) / 2147483647.0; + } + + case 32: { + // IEEE floating point + return *(float *)ubuf; + } + + default: + return 0.0f; + } +} + +} + |