summaryrefslogtreecommitdiffstats
path: root/src/sound/RIFFAudioFile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/sound/RIFFAudioFile.cpp')
-rw-r--r--src/sound/RIFFAudioFile.cpp686
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;
+ }
+}
+
+}
+