Logo Search packages:      
Sourcecode: catcodec version File versions  Download package

sample.cpp

Go to the documentation of this file.
/* $Id: sample.cpp 20614 2010-08-25 16:39:16Z rubidium $ */

/*
 * catcodec is a tool to decode/encode the sample catalogue for OpenTTD.
 * Copyright (C) 2009  Remko Bijker
 *
 * 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, version 2.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/**
 * @file sample.cpp Implementation of Samples
 *
 * The format of .wav PCM encoded files is fairly simple. The "verbatim"
 * 'RIFF' etc. is written as is in the file. The numbers are always
 * written in the little endian way.
 * <ul>
 *  <li>'RIFF'</li>
 *  <li>dword with number of bytes following</li>
 *  <li>'WAVE'</li>
 *  <li>'fmt '</li>
 *  <li>dword with size of 'fmt ' chunk, always 16</li>
 *  <li>word with audio format, always 1 (PCM)</li>
 *  <li>word with number of channels, always 1</li>
 *  <li>dword with sample rate, always 11025, 22050 or 44100</li>
 *  <li>dword with byte rate, always sample rate * number of channels * bits per sample / 8</li>
 *  <li>word with block alignment, always number of channels * bits per sample / 8</li>
 *  <li>word with bits per sample, always 8 or 16</li>
 *  <li>'data'</li>
 *  <li>dword with number of bytes following</li>
 *  <li>actual raw PCM data</li>
 * </ul>
 *
 * This makes the whole thing 44 bytes + the actual payload long.
 */

#include "stdafx.h"
#include "sample.hpp"

/** The size of the RIFF headers of a WAV file */
00051 static const uint32_t RIFF_HEADER_SIZE = 44;

/**
 * Read a string (byte length including termination, actual data) from a reader.
 * @param reader the reader to read from
 * @return the read string
 */
00058 static string ReadString(FileReader &reader)
{
      uint8_t name_len = reader.ReadByte();
      char buffer[256];

      reader.ReadRaw((uint8_t *)buffer, name_len);
      buffer[name_len - 1] = '\0';

      return buffer;
}

/**
 * Write a string (byte length including termination, actual data) to a writer.
 * @param str    the string to write
 * @param writer the writer to write to
 */
00074 static void WriteString(const string str, FileWriter &writer)
{
      uint8_t str_len = (uint8_t)(str.length() + 1);
      writer.WriteByte(str_len);
      writer.WriteRaw((const uint8_t *)str.c_str(), str_len);
}


00082 Sample::Sample(FileReader &reader) :
      sample_data(NULL)
{
      this->offset = reader.ReadDword() & 0x7FFFFFFF;
      this->size   = reader.ReadDword();
}

00089 Sample::Sample(string filename, string name) :
      offset(0),
      name(name),
      filename(filename),
      sample_data(NULL)
{
      FileReader sample_reader(filename);
      this->ReadSample(sample_reader, false);
}

00099 Sample::~Sample()
{
      free(this->sample_data);
}

00104 void Sample::ReadSample(FileReader &reader, bool check_size)
{
      assert(this->sample_data == NULL);

      if (reader.ReadDword() != 'FFIR') throw "Unexpected chunk; expected \"RIFF\" in " + reader.GetFilename();

      if (check_size) {
            if (reader.ReadDword() + 8  != size  ) throw "Unexpected RIFF chunk size in " + reader.GetFilename();
      } else {
            this->size = reader.ReadDword() + 8;
      }
      if (reader.ReadDword() != 'EVAW') throw "Unexpected format; expected \"WAVE\" in " + reader.GetFilename();
      if (reader.ReadDword() != ' tmf') throw "Unexpected format; expected \"fmt \" in " + reader.GetFilename();
      if (reader.ReadDword() != 16    ) throw "Unexpected fmt chunk size in " + reader.GetFilename();
      if (reader.ReadWord()  != 1     ) throw "Unexpected audio format; expected \"PCM\" in " + reader.GetFilename();

      this->num_channels = reader.ReadWord();
      if (this->num_channels != 1) throw "Unexpected number of audio channels; expected 1 in " + reader.GetFilename();

      this->sample_rate = reader.ReadDword();
      if (this->sample_rate != 11025 && this->sample_rate != 22050 && this->sample_rate != 44100) throw "Unexpected same rate; expected 11025, 22050 or 44100 in " + reader.GetFilename();

      /* Read these and validate them later on.
       * Saving them is unnecesary as they can be easily calucated. */
      uint32_t byte_rate   = reader.ReadDword();
      uint16_t block_align = reader.ReadWord();

      this->bits_per_sample = reader.ReadWord();
      if (this->bits_per_sample != 8 && this->bits_per_sample != 16) throw "Unexpected number of bits per channel; expected 8 or 16 in " + reader.GetFilename();

      if (byte_rate != this->sample_rate * this->num_channels * this->bits_per_sample / 8) throw "Unexpected byte rate in " + reader.GetFilename();
      if (block_align != this->num_channels * this->bits_per_sample / 8) throw "Unexpected block align in " + reader.GetFilename();

      if (reader.ReadDword() != 'atad') throw "Unexpected chunk; expected \"data\" in " + reader.GetFilename();

      /* Sometimes the files are padded, which causes them to start at the
       * wrong offset further on, so just read whatever amount of data was
       * specified in the top RIFF as long as sample size is within those
       * boundaries, i.e. within the RIFF. */
      this->sample_size = reader.ReadDword();
      if (this->sample_size + RIFF_HEADER_SIZE > size) throw "Unexpected data chunk size in " + reader.GetFilename();

      this->sample_data = (uint8_t *)malloc(this->size - RIFF_HEADER_SIZE);
      reader.ReadRaw(this->sample_data, this->size - RIFF_HEADER_SIZE);
}

00150 void Sample::ReadCatEntry(FileReader &reader, bool new_format)
{
      assert(this->sample_data == NULL);

      if (reader.GetPos() != this->GetOffset()) throw "Invalid offset in file " + reader.GetFilename();

      this->name = ReadString(reader);

      if (!new_format && this->GetName().compare("Corrupt sound") == 0) {
            /* In the old format there was one sample that was raw PCM. */
            this->sample_size     = this->size;
            this->sample_data     = (uint8_t *)malloc(this->sample_size);
            reader.ReadRaw(this->sample_data, this->sample_size);

            this->size += RIFF_HEADER_SIZE;
      } else {
            this->ReadSample(reader);
      }

      if (!new_format) {
            /* The old format had sometimes the wrong values for e.g.
             * sample rate which made the playback too fast. */
            this->num_channels    = 1;
            this->sample_rate     = 11025;
            this->bits_per_sample = 8;
      }

      /* Some kind of data byte, unused */
      reader.ReadByte();

      this->filename = ReadString(reader);
}

00183 void Sample::WriteSample(FileWriter &writer) const
{
      assert(this->sample_data != NULL);

      writer.WriteDword('FFIR');
      writer.WriteDword(this->size - 8);
      writer.WriteDword('EVAW');

      writer.WriteDword(' tmf');
      writer.WriteDword(16);
      writer.WriteWord(1);
      writer.WriteWord(this->num_channels);
      writer.WriteDword(this->sample_rate);
      writer.WriteDword(this->sample_rate * this->num_channels * this->bits_per_sample / 8);
      writer.WriteWord(this->num_channels * this->bits_per_sample / 8);
      writer.WriteWord(this->bits_per_sample);

      writer.WriteDword('atad');
      writer.WriteDword(this->sample_size);
      writer.WriteRaw(this->sample_data, this->size - RIFF_HEADER_SIZE);
}

00205 void Sample::WriteCatEntry(FileWriter &writer) const
{
      assert(this->sample_data != NULL);

      if (writer.GetPos() != this->GetOffset()) throw "Invalid offset when writing file " + writer.GetFilename();

      WriteString(this->GetName(), writer);
      this->WriteSample(writer);

      /* Some kind of separator byte */
      writer.WriteByte(0);

      WriteString(this->GetFilename(), writer);
}

00220 string Sample::GetName() const
{
      return this->name;
}

00225 string Sample::GetFilename() const
{
      return this->filename;
}

00230 void Sample::SetOffset(uint32_t offset)
{
      assert(this->offset == 0);
      this->offset = offset;
}

00236 uint32_t Sample::GetNextOffset() const
{
      return this->offset +
                  1 +                            // length of the name
                  (this->name.length() + 1) +    // the name + '\0'
                  this->size +                   // size of the data
                  1 +                            // the delimiter
                  1 +                            // length of the filename
                  (this->filename.length() + 1); // the filename + '\0'
}

00247 uint32_t Sample::GetOffset() const
{
      return this->offset;
}

00252 uint32_t Sample::GetSize() const
{
      return this->size;
}

Generated by  Doxygen 1.6.0   Back to index