/* rifffile.h - Copyright (c) 1996, 1998 by Timothy J. Weber */
#ifndef __RIFFFILE_H
#define __RIFFFILE_H
#include <stack>
#include <string>
#include <vector>
#include <stdio.h>
#pragma warning(disable : 4996) // fopen warning problem Visual Studio .NET 2005
class RiffFile;
class RiffChunk {
public:
char name[5];
unsigned long size; // the length, read from the second chunk header entry
char subType[5]; // valid for RIFF and LIST chunks
long start; // the file offset in bytes of the chunk contents
long after; // the start of what comes after this chunk
// initialize at the file's current read position, and mark the file as bad
// if there's an error.
RiffChunk()
{};
RiffChunk(RiffFile& file);
bool operator < (const RiffChunk& other) const
{ return start < other.start; };
bool operator == (const RiffChunk& other) const
{ return strcmp(name, other.name) == 0
&& size == other.size
&& strcmp(subType, other.subType) == 0
&& start == other.start; };
};
class RiffFile {
FILE* fp;
unsigned long formSize;
std::stack<RiffChunk, std::vector<RiffChunk> > chunks;
public:
RiffFile(const char *name);
~RiffFile();
bool rewind();
bool push(const char* chunkType = 0);
bool pop();
long chunkSize() const;
const char* chunkName() const;
const char* subType() const;
bool getNextExtraItem(std::string& type, std::string& value);
FILE* filep()
{ return fp; };
protected:
bool readExtraItem(std::string& type, std::string& value);
};
#endif
/* __RIFFFILE_H */
// ---------------------------------------- EOF -----///
/* rifffile.cpp
Copyright (c) 1996, 1988 by Timothy J. Weber.
See rifffile.txt for documentation:
<rifffile.txt>
Documentation for module RiffFile.
Version 1.3.
Copyright (c) 1996, 1998 by Timothy J. Weber.
Contact: tjweber@lightlink.com, http://www.lightlink.com/tjweber
Requires:
Standard Template Library stacks and vectors
ANSI strings
Change history:
tjw 19 Aug 96: started
tjw 01 Sep 96: finished initial version
tjw 13 Mar 97: fixed bug that left file handle open upon destruction.
tjw 20 May 97: v. 1.2: added extra data methods
tjw 27 Sep 98: v. 1.3: updated to support current versions of STL.
tjw 23 Mar 01: made compatible with gcc.
-------------------------------------------------------------------------------
SUMMARY
A platform-independent way to read a RIFF file (e.g., WAVE).
-------------------------------------------------------------------------------
EXTERNAL INTERFACE
class RiffFile:
RiffFile(const char *name)
Constructor. Opens the named file, and calls rewind(); if rewind()
returns false, it sets the file pointer to null.
The file is closed when the instance is destroyed.
bool rewind()
Moves to the first chunk of the file.
Returns false if the file is not a RIFF file.
const char* formType() const
Returns the form type, or 0 if the file is not a RIFF file.
bool push(const char* chunkType = 0)
Descends from the current chunk to a subchunk with the specified type.
If the type is 0, descends into the next available chunk, starting at
the current file position.
Assumes chunkType is four characters long.
Leaves the get and put pointers pointing to the beginning of the
chunk's contents.
Returns false if the specified chunk type can't be found.
bool pop()
Pops up to the containing chunk, and positions after the end of of the
current subchunk. If the current chunk is a top-level chunk, returns
false.
long chunkSize() const
Returns the size of the current chunk, or 0 if there is none.
const char* chunkName() const
Returns the name of the current chunk, or 0 if there is none.
const char* subType() const
Returns the subtype of the current chunk, or 0 if there is none.
(E.g.: a LIST chunk might have a subtype of INFO, or a top-level
RIFF chunk a subtype of WAVE.)
bool getNextExtraItem(string& type, string& value)
Looks for the next "extra" data present in the file, from
LIST/INFO and DISP chunks. Sets the two arguments on success; returns
false on failure.
FILE* filep()
Returns a file pointer that can be used for reading. May return 0.
-------------------------------------------------------------------------------
PERMISSION
Copyright is retained by Timothy J. Weber. License is granted to use this
source code for any purpose.
-------------------------------------------------------------------------------
IMPLEMENTATION NOTES
-------------------------------------------------------------------------------
WISH LIST
Would be better to change the recursive algorithm in getNextExtraItem() to an
iterative one.
Should use Win32 Multimedia API calls if they're available, for complete
compatibility with other programs on those platforms.
Expand to handle RIFX transparently.
*/
#include "rifffile.h"
using namespace std;
/***************************************************************************
macros and constants
***************************************************************************/
// define REVERSE_ENDIANISM if the endianism of the host platform is not Intel
// (Intel is little-endian)
#ifdef REVERSE_ENDIANISM
#define SWAP_32(int32) ( \
((((DWORD) int32) & 0x000000FFL) << 24) + \
((((DWORD) int32) & 0x0000FF00L) << 8) + \
((((DWORD) int32) & 0x00FF0000L) >> 8) + \
((((DWORD) int32) & 0xFF000000L) >> 24))
#endif
struct TypeRecord {
char* typeName; // four-letter name
char* realName; // English name
};
const int numExtraTypes = 24;
const TypeRecord extraTypes[numExtraTypes] = {
{ "DISP", "Display name" },
{ "IARL", "Archival location" },
{ "IART", "Artist" },
{ "ICMS", "Commissioned" },
{ "ICMT", "Comments" },
{ "ICOP", "Copyright" },
{ "ICRD", "Creation date" },
{ "ICRP", "Cropped" },
{ "IDIM", "Dimensions" },
{ "IDPI", "Dots Per Inch" },
{ "IENG", "Engineer" },
{ "IGNR", "Genre" },
{ "IKEY", "Keywords" },
{ "ILGT", "Lightness" },
{ "IMED", "Medium" },
{ "INAM", "Name" },
{ "IPLT", "Palette Setting" },
{ "IPRD", "Product" },
{ "ISBJ", "Subject" },
{ "ISFT", "Software" },
{ "ISHP", "Sharpness" },
{ "ISRC", "Source" },
{ "ISRF", "Source Form" },
{ "ITCH", "Technician" },
};
/***************************************************************************
member functions for RiffFile
***************************************************************************/
RiffFile::RiffFile(const char *name):
fp(fopen(name, "rb"))
{
if (fp && !rewind()) {
fclose(fp);
fp = 0;
}
}
RiffFile::~RiffFile()
{
if (fp)
fclose(fp);
}
bool RiffFile::rewind()
{
// clear the chunk stack
while (!chunks.empty())
chunks.pop();
// rewind to the start of the file
if (fseek(fp, 0, SEEK_SET))
return false;
// look for a valid RIFF header
RiffChunk topChunk(*this);
if (feof(fp) || strcmp(topChunk.name, "RIFF"))
return false;
// found; push it on the stack, and leave the put pointer in the same place
// as the get pointer.
formSize = topChunk.size;
chunks.push(topChunk);
return true;
}
bool RiffFile::push(const char* chunkType)
{
// can't descend if we haven't started out yet.
if (chunks.empty())
return false;
// first, go to the start of the current chunk, if we're looking for a named
// chunk.
if (chunkType)
if (fseek(fp, chunks.top().start, SEEK_SET))
return false;
// read chunks until one matches or we exhaust this chunk
while (!feof(fp) && ftell(fp) < chunks.top().after) {
RiffChunk chunk(*this);
if (!feof(fp)) {
// see if the subchunk type matches
if (!chunkType || strcmp(chunk.name, chunkType) == 0) {
// found; synchronize the put pointer, push the chunk, and succeed
chunks.push(chunk);
return true;
} else {
// not found; go to the next one.
if (fseek(fp, chunk.after, SEEK_SET))
return false;
}
}
}
// couldn't find it; synchronize the put pointer and return error.
fseek(fp, chunks.top().start, SEEK_SET);
return false;
}
bool RiffFile::pop()
{
// if we've only got the top level chunk (or not even that), then we can't
// go up.
if (chunks.size() < 2)
return false;
// Position the get and put pointers at the end of the current subchunk.
fseek(fp, chunks.top().after, SEEK_SET);
// Pop up the stack.
chunks.pop();
return true;
}
long RiffFile::chunkSize() const
{
if (!chunks.empty())
return chunks.top().size;
else
return 0;
}
const char* RiffFile::chunkName() const
{
if (!chunks.empty())
return chunks.top().name;
else
return 0;
}
const char* RiffFile::subType() const
{
if (!chunks.empty() && chunks.top().subType[0])
return chunks.top().subType;
else
return 0;
}
bool RiffFile::getNextExtraItem(string& type, string& value)
{
// if the current chunk is LIST/INFO, then try to read another subchunk.
if (strcmp(chunkName(), "LIST") == 0
&& strcmp(subType(), "INFO") == 0)
{
if (push()) {
if (readExtraItem(type, value))
return true;
else
// unrecognized type. Continue on.
return getNextExtraItem(type, value);
} else {
// got to the end of the LIST/INFO chunk. Pop back out and continue
// looking.
pop();
return getNextExtraItem(type, value);
}
// we're not in a LIST/INFO chunk, so look for the next DISP or LIST/INFO.
} else {
push();
if (strcmp(chunkName(), "DISP") == 0) {
// DISP chunk: read and pop back out.
return readExtraItem(type, value);
} else if (strcmp(chunkName(), "LIST") == 0
&& strcmp(subType(), "INFO") == 0)
{
// LIST/INFO chunk: read first element
return getNextExtraItem(type, value);
} else {
// Some other chunk. Pop back out and move on.
if (pop())
return getNextExtraItem(type, value);
else
return false;
}
}
}
// Reads extra data from the current chunk, and pops out of it.
bool RiffFile::readExtraItem(string& type, string& value)
{
// see if it's one we recognize
bool found = false;
for (int i = 0; i < numExtraTypes; i++) {
if (strcmp(chunkName(), extraTypes[i].typeName) == 0) {
type = extraTypes[i].realName;
found = true;
}
}
// DISP chunks skip four bytes before the display name starts.
if (strcmp(chunkName(), "DISP") == 0) {
fgetc(filep());
fgetc(filep());
fgetc(filep());
fgetc(filep());
}
// read the value, if we recognize the type
if (found) {
int c;
value = "";
while ((c = fgetc(filep())) != '\0' && c != EOF)
value += char(c);
}
// whether we recognize it or not, pop back out.
pop();
return found;
}
/***************************************************************************
member functions for RiffChunk
***************************************************************************/
RiffChunk::RiffChunk(RiffFile& parent)
{
// read the chunk name
fread(name, 1, 4, parent.filep());
name[4] = '\0';
// read the chunk size
fread(&size, 4, 1, parent.filep());
#ifdef REVERSE_ENDIANISM
// reverse the endianism of the chunk size.
size = SWAP_32(size);
#endif
// if this is a RIFF or LIST chunk, read its subtype.
if (strcmp(name, "RIFF") == 0
|| strcmp(name, "LIST") == 0)
{
fread(subType, 1, 4, parent.filep());
subType[4] = '\0';
// subtract the subtype from the size of the data.
size -= 4;
} else
*subType = '\0';
// the chunk starts after the name and size.
start = ftell(parent.filep());
// the next chunk starts after this one, but starts on a word boundary.
after = start + size;
if (after % 2)
after++;
}
// ---------------------------------------- EOF -----///
/* wave.h - Copyright (c) 1996-2002 by Timothy J. Weber */
#ifndef __WAVE_H
#define __WAVE_H
#include <stdio.h>
#include <iostream>
#include "rifffile.h"
#pragma warning(disable : 4996) // fopen warning problem
class WaveFile {
public:
WaveFile();
~WaveFile();
bool OpenRead(const char* name);
bool OpenWrite(const char* name);
bool ResetToStart();
bool Close();
unsigned short GetFormatType() const
{ return formatType; };
void SetFormatType(unsigned short type)
{ formatType = type; changed = true; };
bool IsCompressed() const
{ return formatType != 1; };
unsigned short GetNumChannels() const
{ return numChannels; };
void SetNumChannels(unsigned short num)
{ numChannels = num; changed = true; };
unsigned long GetSampleRate() const
{ return sampleRate; };
void SetSampleRate(unsigned long rate)
{ sampleRate = rate; changed = true; };
unsigned long GetBytesPerSecond() const
{ return bytesPerSecond; };
void SetBytesPerSecond(unsigned long bytes)
{ bytesPerSecond = bytes; changed = true; };
unsigned short GetBytesPerSample() const
{ return bytesPerSample; };
void SetBytesPerSample(unsigned short bytes)
{ bytesPerSample = bytes; changed = true; };
unsigned short GetBitsPerChannel() const
{ return bitsPerChannel; };
void SetBitsPerChannel(unsigned short bits)
{ bitsPerChannel = bits; changed = true; };
unsigned long GetNumSamples() const
{ return (GetBytesPerSample())?
GetDataLength() / GetBytesPerSample(): 0; };
void SetNumSamples(unsigned long num)
{ SetDataLength(num * GetBytesPerSample()); };
float GetNumSeconds() const
{ return GetBytesPerSecond()?
float(GetDataLength()) / GetBytesPerSecond(): 0; };
unsigned long GetDataLength() const
{ return dataLength; };
void SetDataLength(unsigned long numBytes)
{ dataLength = numBytes; changed = true; };
bool FormatMatches(const WaveFile& other);
void CopyFormatFrom(const WaveFile& other);
void SetupFormat(int sampleRate = 44100, short bitsPerChannel = 16, short channels = 1);
FILE* GetFile()
{ return readFile? readFile->filep(): writeFile; };
RiffFile* GetRiffFile()
{ return readFile? readFile : 0; };
bool WriteHeaderToFile(FILE* fp);
bool ReadSample(unsigned char& sample);
bool WriteSample(unsigned char sample);
bool ReadSample(short& sample);
bool WriteSample(short sample);
bool ReadSample(float& sample);
bool WriteSample(float sample);
bool ReadSample(double& sample);
bool WriteSample(double sample);
bool ReadSamples(unsigned char* samples, size_t count = 1);
bool WriteSamples(unsigned char* samples, size_t count = 1);
bool ReadSamples(short* samples, size_t count = 1);
bool WriteSamples(short* samples, size_t count = 1);
bool ReadRaw(char* buffer, size_t numBytes = 1);
bool WriteRaw(char* buffer, size_t numBytes = 1);
bool GetFirstExtraItem(std::string& type, std::string& value);
bool GetNextExtraItem(std::string& type, std::string& value);
bool CopyFrom(WaveFile& other);
const char* GetError() const
{ return error; };
void ClearError()
{ error = 0; };
void ShowFormat(bool details = true);
protected:
RiffFile* readFile;
FILE* writeFile;
unsigned short formatType;
unsigned short numChannels;
unsigned long sampleRate;
unsigned long bytesPerSecond;
unsigned short bytesPerSample;
unsigned short bitsPerChannel;
unsigned long dataLength;
const char* error;
bool changed; // true if any parameters changed since the header was last written
};
#endif
/* __WAVE_H */
// ---------------------------------------- EOF -----///
/* wave.cpp
Copyright (c) 1996-2002 by Timothy J. Weber.
See WAVE.txt for documentation:
<WAVE.txt>
Documentation for module WAVE.
Version 1.4.
Copyright (c) 1996-2002 by Timothy J. Weber.
Contact: tjweber@lightlink.com, http://www.lightlink.com/tjweber
Requires:
RiffFile (also by the author)
ANSI strings
Standard Template Library
Change history:
tjw 12 Nov 96: started
tjw 5 Feb 97: version 1.0, shipped with WavCat
tjw 20 May 97: added extra data reporting and RiffFile exporting, for use
in StripWav.
tjw 27 Sep 98: version 1.1, updated to support current versions of STL.
tjw 20 Dec 00: version 1.2, added more convenience functions and improved docs.
Tested with Borland C++Builder 3 and Visual C++ 6.
tjw 3 Feb 01: version 1.21, fixed a bug when using WriteSample() with
a stereo file.
tjw 15 Aug 01: Fixed a number of bugs with the sample-writing convenience functions.
tjw 28 Sep 02: Fixed a bug in ShowFormat() (affecting the test code only) that moved
the read pointer.
-------------------------------------------------------------------------------
SUMMARY
A platform-independent way to read and write RIFF WAVE files.
When a file is opened for reading, its attributes are set to correspond with
the specified file. Setting the attributes in this state has no effect.
When it's opened for writing, the file pointer is positioned to the approprate
point to start writing sample data. When it's closed, any attribute settings
that have been changed will be fixed in the file header.
For 8- and 16-bit PCM formats, you can use the supplied methods to read and
write samples. If you know the sample width you're expecting, use the
appropriate integral type (unsigned char or short) for speed; otherwise, use
floats, or doubles if you really want the extra precision (probably not helpful).
To read and write compressed formats (non-PCM), or other sample widths, you'll
have to implement your own compression/decompression and use the C-style I/O
functions (e.g., fopen, fread, fwrite) with the FILE* returned by GetFile().
If you write samples using this FILE*, you must update the data length by
calling SetDataLength(), or the file will not be written correctly.
-------------------------------------------------------------------------------
PERMISSION
Copyright is retained by Timothy J. Weber. License is granted to use this
source code for any purpose.
-------------------------------------------------------------------------------
EXTERNAL INTERFACE
class WaveFile:
bool OpenRead(const char* name)
Opens the named file for reading. If it doesn't exist, can't be opened,
or isn't a valid WAVE file, returns false. Otherwise, sets all the
attributes and positions the file at the start of the sample data chunk.
bool OpenWrite(const char* name)
Opens the named file for writing. If it already exists, overwrites it.
Returns false if it can't be opened.
bool Close()
Closes the file. If the file is open for output, also updates the
header.
The file is automatically closed when the object is destroyed.
High-level access methods:
bool GetFirstExtraItem(string& type, string& value)
bool GetNextExtraItem(string& type, string& value)
Iterates through the list of additional data present in the file, from
LIST/INFO and DISP chunks. Sets the two arguments on success;
returns false on failure. After GetFirstExtraItem() returns
true, ResetToStart() should be called before attempting to read sample
data.
bool CopyFrom(WaveFile& other)
Copies all the sample data from the other wave file to this one,
starting at the start of the data chunk in the other file and at the
current file position in this file. The DataLength attribute for this
file is incremented to reflect the bytes copied. No format checking is
done. Returns false on error, and sets the LastError flag if the
error was while writing to this file (as opposed to reading the other).
If either file pointer is invalid, returns false and sets LastError.
const char* GetError() const
void ClearError()
Inspects and clears the error description. If no error has occurred,
HadError() returns 0; otherwise, it returns a description of the error.
The following methods are for dealing with the format as an aggregate, instead
of manipulating individual elements directly:
bool FormatMatches(const WaveFile& other)
Returns true if all the format attributes of the other wave file are
identical to this one's.
void CopyFormatFrom(const WaveFile& other)
Copies all the format attributes from the specified WaveFile.
void SetupFormat(int sampleRate = 44100, short bitsPerChannel = 16, short channels = 1)
Sets up the format attributes for uncompressed (PCM) audio with the
given attributes. Computes the remaining attributes accordingly.
The following methods are for reading and writing samples in common formats.
bool ReadSample(T& sample)
bool WriteSample(T sample)
Overloads for reading/writing individual samples from numeric types,
where T is unsigned char (for 8-bit), short (for 16-bit), float, or
double.
For float and double, the assumed sample range is from -1.0 to +1.0, and
samples are converted to the format appropriate for the current sample
width.
Updates the file pointer and the data length.
Returns false if the argument size doesn't match the sample size, or
if there aren't as many samples left to read as requested.
Note that samples are interleaved for files with multiple channels.
I.e., the first call will read or write the left sample, the second
will read or write the right channel, etc.
bool ReadSamples(T* samples, size_t count = 1)
bool WriteSamples(T* samples, size_t count = 1)
Overloads for reading/writing your own provided buffers of samples,
where T is unsigned char (for 8-bit) or short (for 16-bit).
Note that count is the number of cross-channel samples. E.g., if you do:
ReadSamples(pShort, 1);
on a stereo file, pShort must be able to hold two integers, or four
bytes. In a stereo file, samples for the left channel come first.
Updates the file pointer and the data length.
Returns false if the buffer unit size doesn't match the sample size, or
if there aren't as many samples left to read as requested.
Note that samples are interleaved for files with multiple channels.
I.e., samples[0] contains the first left sample, samples[1] the first
right sample, samples[2] the second left sample, etc.
The following methods are for inspecting and changing individual aspects of the
data format. Use these if you're implementing a compressed format, or if you
need more control over individual values than SetupFormat() affords.
unsigned short GetFormatType() const
void SetFormatType(unsigned short type)
Inspect and set the format type.
This should be 1 for PCM.
bool IsCompressed() const
Returns true if the sample format uses compression, false if it's PCM.
unsigned short GetNumChannels() const
void SetNumChannels(unsigned short numChannels)
Inspect and set the number of channels (e.g., 2 for stereo, 1 for mono).
unsigned long GetSampleRate() const
void SetSampleRate(unsigned long sampleRate)
Inspect and set the sample rate, in Hertz.
unsigned long GetBytesPerSecond() const
void SetBytesPerSecond(unsigned long bytesPerSecond)
Inspect and set the bytes per second. For PCM formats, this is equal
to SampleRate * NumChannels * BytesPerSample; for compressed formats, it
may differ.
unsigned short GetBytesPerSample() const
void SetBytesPerSample(unsigned short bytesPerSample)
Inspect and set the bytes per sample. This will typically be 1 or 2 for
mono, 2 or 4 for stereo.
unsigned short GetBitsPerChannel() const
void SetBitsPerChannel(unsigned short bitsPerChannel)
Inspect and set the bits per channel. This is equal to BytesPerSample
* 8 / NumChannels. Typically 8 or 16.
unsigned long GetNumSamples() const
void SetNumSamples(unsigned long numSamples)
Inspect and set the number of samples. Note that this is really a
wrapper around the DataLength attribute, that factors in the sample
width. The samples reported here are cross-channel samples; i.e., a
sample in the left channel of a stereo file and its corresponding right-
channel sample are counted as a single sample.
float GetNumSeconds() const
Inspect the length in seconds. This is calculated from DataLength
and BytesPerSecond.
unsigned long GetDataLength() const
void SetDataLength(unsigned long numBytes)
Inspect and set the number of bytes in the data chunk.
void ShowFormat(bool details)
Using cout diplay wav format to the screen.
The following methods are for reading and writing to the underlying file:
bool ResetToStart()
Moves the file pointer to the start of the sample data. This is
done automatically on OpenRead() or OpenWrite().
FILE* GetFile()
Returns a file pointer that can be used for reading or writing. May
return 0.
RiffFile* GetRiffFile()
Returns a pointer to the currently opened RIFF file object.
Returns 0 if the file is not open for reading.
bool WriteHeaderToFile(FILE* fp)
Writes the RIFF/WAVE, fmt, and start of the data chunk in the canonical
format to the specified file.
You don't normally need to call this method, since it's automatically
called on close. You might want to call it explicitly in certain
situations to streamline writes for performance.
-------------------------------------------------------------------------------
EXAMPLE USAGE
Concatenating two WAVE files:
WaveFile in1;
in1.OpenRead("in1.wav");
WaveFile in2;
in2.OpenRead("in2.wav");
WaveFile out;
out.OpenWrite("out.wav");
out.CopyFormatFrom(in1);
out.CopyFrom(in1);
out.CopyFrom(in2);
Adjusting the volume of a WAVE file:
WaveFile in;
in.OpenRead("in.wav");
WaveFile out;
out.CopyFormatFrom(in);
out.OpenWrite("out.wav");
for (size_t i = 0; i < in.GetNumSamples(); i++) {
float sample;
in.ReadSample(sample);
out.WriteSample(sample * 0.6);
}
Mixing two WAVE files together to a third:
WaveFile in1;
in1.OpenRead("in1.wav");
WaveFile in2;
in2.OpenRead("in2.wav");
WaveFile out;
out.CopyFormatFrom(in1);
out.OpenWrite("out.wav");
for (size_t i = 0; i < in1.GetNumSamples() || i < in2.GetNumSamples(); i++) {
float sample1 = 0;
if (i < in1.GetNumSamples())
in1.ReadSample(sample1);
float sample2 = 0;
if (i < in2.GetNumSamples())
in2.ReadSample(sample2);
out.WriteSample(sample1 / 2 + sample2 / 2);
}
-------------------------------------------------------------------------------
IMPLEMENTATION NOTES
-------------------------------------------------------------------------------
WISH LIST
*/
#include "wave.h"
using namespace std;
/***************************************************************************
macros and constants
***************************************************************************/
// constants for the canonical WAVE format
const int fmtChunkLength = 16; // length of fmt contents
const int waveHeaderLength = 4 + 8 + fmtChunkLength + 8; // from "WAVE" to sample data
/***************************************************************************
member functions for WAVE
***************************************************************************/
WaveFile::WaveFile():
readFile(0),
writeFile(0),
formatType(0),
numChannels(0),
sampleRate(0),
bytesPerSecond(0),
bytesPerSample(0),
bitsPerChannel(0),
dataLength(0),
error(0),
changed(true)
{
}
WaveFile::~WaveFile()
{
Close();
}
bool WaveFile::OpenRead(const char* name)
{
if (readFile || writeFile)
Close();
try {
// open the RIFF file
readFile = new RiffFile(name);
if (!readFile->filep())
throw error = "Couldn't open file";
// read the header information
if (strcmp(readFile->chunkName(), "RIFF")
|| strcmp(readFile->subType(), "WAVE")
|| !readFile->push("fmt "))
throw error = "Couldn't find RIFF, WAVE, or fmt";
size_t dwFmtSize = size_t(readFile->chunkSize());
char* fmtChunk = new char[dwFmtSize];
try {
if (fread(fmtChunk, dwFmtSize, 1, readFile->filep()) != 1)
throw error = "Error reading format chunk";
readFile->pop();
// set the format attribute members
formatType = *((short*) fmtChunk);
numChannels = *((short*) (fmtChunk + 2));
sampleRate = *((long*) (fmtChunk + 4));
bytesPerSecond = *((long*) (fmtChunk + 8));
bytesPerSample = *((short*) (fmtChunk + 12));
bitsPerChannel = *((short*) (fmtChunk + 14));
// position at the data chunk
if (!readFile->push("data"))
throw error = "Couldn't find data chunk";
// get the size of the data chunk
dataLength = readFile->chunkSize();
delete[] fmtChunk;
} catch (...) {
delete[] fmtChunk;
throw error;
}
} catch (...) {
Close();
return false;
}
return true;
}
bool WaveFile::OpenWrite(const char* name)
{
if (readFile || writeFile)
Close();
// open the file
writeFile = fopen(name, "wb");
if (!writeFile) {
error = "Couldn't open output file";
return false;
}
dataLength = 0;
// write the header
return WriteHeaderToFile(writeFile);
}
bool WaveFile::ResetToStart()
{
if (readFile) {
// pop out of the data chunk
if (!readFile->rewind()
|| !readFile->push("data"))
{
error = "Couldn't find data chunk on reset";
return false;
} else
return true;
} else if (writeFile) {
return fseek(writeFile, waveHeaderLength, SEEK_SET) == 0;
} else
return false;
}
bool WaveFile::Close()
{
bool retval = true;
if (readFile) {
delete readFile; // closes the file before it's destroyed
readFile = 0;
} else if (writeFile) {
// write the header information at the start of the file, if necessary
if (changed) {
long currentSpot = ftell(writeFile); // save the position
retval = WriteHeaderToFile(writeFile);
fseek(writeFile, currentSpot, SEEK_SET); // restore the old position
// this is necessary so the file gets the right length--otherwise,
// all the data we wrote would be truncated.
}
// close the file
fclose(writeFile);
writeFile = 0;
}
return retval;
}
bool WaveFile::FormatMatches(const WaveFile& other)
{
return formatType == other.formatType
&& numChannels == other.numChannels
&& sampleRate == other.sampleRate
&& bytesPerSecond == other.bytesPerSecond
&& bytesPerSample == other.bytesPerSample
&& bitsPerChannel == other.bitsPerChannel;
}
void WaveFile::CopyFormatFrom(const WaveFile& other)
{
formatType = other.formatType;
numChannels = other.numChannels;
sampleRate = other.sampleRate;
bytesPerSecond = other.bytesPerSecond;
bytesPerSample = other.bytesPerSample;
bitsPerChannel = other.bitsPerChannel;
}
void WaveFile::SetupFormat(int sampleRate, short bitsPerChannel, short channels)
{
SetFormatType(1);
SetNumChannels(channels);
SetSampleRate(sampleRate);
SetBytesPerSample((unsigned short)((bitsPerChannel >> 3) * channels));
SetBytesPerSecond(sampleRate * GetBytesPerSample());
SetBitsPerChannel(bitsPerChannel);
SetNumSamples(0);
}
bool WaveFile::GetFirstExtraItem(string& type, string& value)
{
if (readFile)
return readFile->rewind() && readFile->getNextExtraItem(type, value);
else
return false;
}
bool WaveFile::GetNextExtraItem(string& type, string& value)
{
if (readFile)
return readFile->getNextExtraItem(type, value);
else
return false;
}
bool WaveFile::CopyFrom(WaveFile& other)
{
const size_t transferBufSize = 4096;
if (!writeFile) {
error = "Copy to an unopened file";
return false;
} else if (!other.readFile) {
error = "Copy from an unopened file";
return false;
}
try {
// allocate the transfer buffer
char* transferBuffer = new char[transferBufSize];
unsigned long bytesRead = 0;
try {
if (!other.ResetToStart())
throw error = "Couldn't reset input file to start";
while (bytesRead < other.dataLength) {
// calculate the size of the next buffer
size_t bytesToRead = (size_t) min(transferBufSize,
size_t(other.dataLength - bytesRead));
// read the buffer
if (fread(transferBuffer, 1, bytesToRead, other.readFile->filep())
!= bytesToRead)
throw error = "Error reading samples from input file";
bytesRead += unsigned long(bytesToRead);
// write the buffer
if (fwrite(transferBuffer, 1, bytesToRead, writeFile) != bytesToRead)
throw error = "Error writing samples to output file";
dataLength += unsigned long(bytesToRead);
changed = true;
}
// delete the transfer buffer
delete[] transferBuffer;
} catch (...) {
delete[] transferBuffer;
throw error;
}
} catch (...) {
return false;
}
return true;
}
bool WaveFile::WriteHeaderToFile(FILE* fp)
{
// seek to the start of the file
if (fseek(fp, 0, SEEK_SET) != 0)
return false;
// write the file header
unsigned long wholeLength = waveHeaderLength + dataLength;
unsigned long chunkLength = fmtChunkLength;
if (fputs("RIFF", fp) == EOF
|| fwrite(&wholeLength, sizeof(wholeLength), 1, fp) != 1
|| fputs("WAVE", fp) == EOF
|| fputs("fmt ", fp) == EOF
|| fwrite(&chunkLength, sizeof(chunkLength), 1, fp) != 1
|| fwrite(&formatType, sizeof(formatType), 1, fp) != 1
|| fwrite(&numChannels, sizeof(numChannels), 1, fp) != 1
|| fwrite(&sampleRate, sizeof(sampleRate), 1, fp) != 1
|| fwrite(&bytesPerSecond, sizeof(bytesPerSecond), 1, fp) != 1
|| fwrite(&bytesPerSample, sizeof(bytesPerSample), 1, fp) != 1
|| fwrite(&bitsPerChannel, sizeof(bitsPerChannel), 1, fp) != 1
|| fputs("data", fp) == EOF
|| fwrite(&dataLength, sizeof(dataLength), 1, fp) != 1)
{
error = "Error writing header";
return false;
}
// if it's the same file, now we don't have to write it again unless it's
// been changed.
if (fp == writeFile)
changed = false;
return true;
}
bool WaveFile::ReadSample(float& sample)
{
double fSample;
bool retval = ReadSample(fSample);
sample = float(fSample);
return retval;
}
bool WaveFile::WriteSample(float sample)
{
return WriteSample(double(sample));
}
bool WaveFile::ReadSample(double& sample)
{
bool retval = false;
if (GetBitsPerChannel() == 8) {
unsigned char cSample;
retval = ReadSample(cSample);
sample = double(cSample) / ((1 << (8 - 1)) - 1) - 1;
} else if (GetBitsPerChannel() == 16) {
short sSample;
retval = ReadSample(sSample);
sample = double(sSample) / ((1 << (16 - 1)) - 1);
} else
error = "Floats can be written only as 8 or 16-bit samples";
return retval;
}
bool WaveFile::WriteSample(double sample)
{
if (GetBitsPerChannel() == 8)
return WriteSample((unsigned char)((sample + 1) * ((1 << (8 - 1)) - 1)));
else if (GetBitsPerChannel() == 16)
return WriteSample(short(sample * ((1 << (16 - 1)) - 1)));
else {
error = "Floats can be written only as 8 or 16-bit samples";
return false;
}
}
bool WaveFile::ReadSample(unsigned char& sample)
{
if (GetBitsPerChannel() != 8) {
error = "Sample size mismatch";
return false;
}
return ReadRaw((char*) &sample);
};
bool WaveFile::WriteSample(unsigned char sample)
{
if (GetBitsPerChannel() != 8) {
error = "Sample size mismatch";
return false;
}
return WriteRaw((char*) &sample);
};
bool WaveFile::ReadSample(short& sample)
{
if (GetBitsPerChannel() != 16) {
error = "Sample size mismatch";
return false;
}
return ReadRaw((char*) &sample, 2);
};
bool WaveFile::WriteSample(short sample)
{
if (GetBitsPerChannel() != 16) {
error = "Sample size mismatch";
return false;
}
return WriteRaw((char*) &sample, 2);
};
bool WaveFile::ReadSamples(unsigned char* samples, size_t count)
{
if (GetBitsPerChannel() != 8) {
error = "Sample size mismatch";
return false;
}
return ReadRaw((char*) samples, GetNumChannels() * count);
}
bool WaveFile::WriteSamples(unsigned char* samples, size_t count)
{
if (GetBitsPerChannel() != 8) {
error = "Sample size mismatch";
return false;
}
return WriteRaw((char*) samples, GetNumChannels() * count);
}
bool WaveFile::ReadSamples(short* samples, size_t count)
{
if (GetBitsPerChannel() != 16) {
error = "Sample size mismatch";
return false;
}
return ReadRaw((char*) samples, 2 * GetNumChannels() * count);
}
bool WaveFile::WriteSamples(short* samples, size_t count)
{
if (GetBitsPerChannel() != 16) {
error = "Sample size mismatch";
return false;
}
return WriteRaw((char*) samples, 2 * GetNumChannels() * count);
}
bool WaveFile::ReadRaw(char* buffer, size_t numBytes)
{
if (fread(buffer, 1, numBytes, GetFile()) != numBytes) {
error = "Couldn't read samples";
return false;
}
return true;
}
bool WaveFile::WriteRaw(char* buffer, size_t numBytes)
{
if (fwrite(buffer, 1, numBytes, writeFile) != numBytes) {
error = "Couldn't write samples";
return false;
}
SetDataLength(GetDataLength() + numBytes);
return true;
}
void WaveFile::ShowFormat(bool details)
{
cout
<< "Format: " << GetFormatType()
<< (IsCompressed()? " (compressed)" : " (PCM)") << endl
<< "Channels: " << GetNumChannels() << endl
<< "Sample rate: " << GetSampleRate() << endl
<< "Bytes per second: " << GetBytesPerSecond() << endl
<< "Bytes per sample: " << GetBytesPerSample() << endl
<< "Bits per channel: " << GetBitsPerChannel() << endl
<< "Bytes: " << GetDataLength() << endl
<< "Samples: " << GetNumSamples() << endl
<< "Seconds: " << GetNumSeconds() << endl;
if(GetFile())
cout << "File pointer: " << ftell(GetFile()) << endl;
else
cout << "File pointer: null" << endl;
if (details) {
string type, value;
if (GetFirstExtraItem(type, value)) {
cout << "Extra data:" << endl;
do {
cout << " " << type << ": " << value << endl;
} while (GetNextExtraItem(type, value));
}
ResetToStart();
}
}

Δεν υπάρχουν σχόλια:
Δημοσίευση σχολίου