/* $Id$ */ /* * 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. * * * 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 */ 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 */ 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 */ 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); } Sample::Sample(FileReader &reader) : sample_data(NULL) { this->offset = reader.ReadDword() & 0x7FFFFFFF; this->size = reader.ReadDword(); } Sample::Sample(string filename, string name) : offset(0), name(name), filename(filename), sample_data(NULL) { FileReader sample_reader(filename); this->ReadSample(sample_reader, false); } Sample::~Sample() { free(this->sample_data); } 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); } 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); } 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); } 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); } string Sample::GetName() const { return this->name; } string Sample::GetFilename() const { return this->filename; } void Sample::SetOffset(uint32_t offset) { assert(this->offset == 0); this->offset = offset; } 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' } uint32_t Sample::GetOffset() const { return this->offset; } uint32_t Sample::GetSize() const { return this->size; }