Logo Search packages:      
Sourcecode: rosegarden version File versions

RIFFAudioFile.cpp

// -*- c-basic-offset: 4 -*-

/*
    Rosegarden-4
    A sequencer and musical notation editor.

    This program is Copyright 2000-2006
        Guillaume Laurent   <glaurent@telegraph-road.org>,
        Chris Cannam        <cannam@all-day-breakfast.com>,
        Richard Bown        <bownie@bownie.com>

    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 "Audio.h"
#include "RIFFAudioFile.h"
#include "RealTime.h"
#include "Sound.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)
{
}

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-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
        if (getBytes(file, 4) != "data")
        {
#ifdef DEBUG_RIFF
            std::cerr << "RIFFAudioFile::scanTo() - can't find data chunk where "
                      << "it was expected" << std::endl;
#endif
            return false;
        }

        // get the length of the data chunk, and scan past it as a side-effect
      int dataChunkLength = getIntegerFromLittleEndian(getBytes(file, 4));
#ifdef DEBUG_RIFF
        std::cout << "RIFFAudioFile::scanTo() - data chunk size = "
                  << dataChunkLength << 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);
    }

    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(Rosegarden::AUDIO_RIFF_ID, 0, 4) != 0)
#else
    if (hS.compare(0, 4, Rosegarden::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(Rosegarden::AUDIO_WAVE_ID, 8, 4) != 0)
#else
    if (hS.compare(8, 4, Rosegarden::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(Rosegarden::AUDIO_FORMAT_ID, 12, 4) != 0)
#else
    if (hS.compare(12, 4, Rosegarden::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(Rosegarden::AUDIO_RIFF_ID, 0, 4) == 0 &&
        hS.compare(Rosegarden::AUDIO_WAVE_ID, 8, 4) == 0 &&
        hS.compare(Rosegarden::AUDIO_BWF_ID, 12, 4) == 0)
#else
    if (hS.compare(0, 4, Rosegarden::AUDIO_RIFF_ID) == 0 &&
        hS.compare(8, 4, Rosegarden::AUDIO_WAVE_ID) == 0 &&
        hS.compare(12, 4, Rosegarden::AUDIO_BWF_ID) == 0)
#endif
    {
        type = BWF;
    }
    // Now for a WAV
#if (__GNUC__ < 3)
    else if (hS.compare(Rosegarden::AUDIO_RIFF_ID, 0, 4) == 0 &&
             hS.compare(Rosegarden::AUDIO_WAVE_ID, 8, 4) == 0)
#else
    else if (hS.compare(0, 4, Rosegarden::AUDIO_RIFF_ID) == 0 &&
             hS.compare(8, 4, Rosegarden::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;
    }
}

}


Generated by  Doxygen 1.6.0   Back to index