mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-16 14:16:09 -06:00
575 lines
14 KiB
C++
575 lines
14 KiB
C++
/* wave.cpp
|
|
|
|
Copyright (c) 1996-2002 by Timothy J. Weber.
|
|
|
|
See WAVE.txt for documentation.
|
|
*/
|
|
|
|
#ifndef _MSC_VER
|
|
#include <stdlib.h>
|
|
#else
|
|
// Microsoft doesn't include min, though it's part of the standard library!
|
|
template<class T>
|
|
T min(T a, T b) { return a < b? a: b; }
|
|
#endif
|
|
|
|
#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
|
|
|
|
/***************************************************************************
|
|
typedefs and class definitions
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
prototypes for static functions
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
static variables
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
public member functions for WaveFile
|
|
***************************************************************************/
|
|
|
|
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 += bytesToRead;
|
|
|
|
// write the buffer
|
|
if (fwrite(transferBuffer, 1, bytesToRead, writeFile) != bytesToRead)
|
|
throw error = "Error writing samples to output file";
|
|
dataLength += 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 = 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;
|
|
}
|
|
|
|
/***************************************************************************
|
|
private member functions for WaveFile
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
main()
|
|
***************************************************************************/
|
|
|
|
#ifdef TEST_WAVE
|
|
|
|
#include <iostream>
|
|
|
|
static void reportProblem()
|
|
{
|
|
cout << " *** ERROR: Result incorrect." << endl;
|
|
}
|
|
|
|
static void checkResult(bool got, bool expected)
|
|
{
|
|
if (got)
|
|
cout << "success." << endl;
|
|
else
|
|
cout << "fail." << endl;
|
|
|
|
if (got != expected)
|
|
reportProblem();
|
|
}
|
|
|
|
static void pause()
|
|
{
|
|
cout << "Press Enter to continue." << endl;
|
|
cin.get();
|
|
}
|
|
|
|
static void ShowErrors(WaveFile& from, WaveFile& to)
|
|
{
|
|
bool any = from.GetError() || to.GetError();
|
|
|
|
if (from.GetError())
|
|
cout << "Error on input: " << from.GetError() << "." << endl;
|
|
|
|
if (to.GetError())
|
|
cout << "Error on output: " << to.GetError() << "." << endl;
|
|
|
|
if (!any)
|
|
cout << "Success." << endl;
|
|
}
|
|
|
|
static void ShowFormat(WaveFile& wave, bool details = true)
|
|
{
|
|
cout
|
|
<< "Format: " << wave.GetFormatType()
|
|
<< (wave.IsCompressed()? " (compressed)" : " (PCM)") << endl
|
|
<< "Channels: " << wave.GetNumChannels() << endl
|
|
<< "Sample rate: " << wave.GetSampleRate() << endl
|
|
<< "Bytes per second: " << wave.GetBytesPerSecond() << endl
|
|
<< "Bytes per sample: " << wave.GetBytesPerSample() << endl
|
|
<< "Bits per channel: " << wave.GetBitsPerChannel() << endl
|
|
<< "Bytes: " << wave.GetDataLength() << endl
|
|
<< "Samples: " << wave.GetNumSamples() << endl
|
|
<< "Seconds: " << wave.GetNumSeconds() << endl;
|
|
|
|
if(wave.GetFile())
|
|
cout << "File pointer: " << ftell(wave.GetFile()) << endl;
|
|
else
|
|
cout << "File pointer: null" << endl;
|
|
|
|
if (details) {
|
|
string type, value;
|
|
if (wave.GetFirstExtraItem(type, value)) {
|
|
cout << "Extra data:" << endl;
|
|
do {
|
|
cout << " " << type << ": " << value << endl;
|
|
} while (wave.GetNextExtraItem(type, value));
|
|
}
|
|
wave.ResetToStart();
|
|
}
|
|
pause();
|
|
}
|
|
|
|
int main(int argc, const char* argv[])
|
|
{
|
|
if (argc < 3)
|
|
cout << "Copies one WAVE file to another, in canonical form." << endl;
|
|
else {
|
|
WaveFile From, To;
|
|
|
|
cout << "Opening input..." << endl;
|
|
From.OpenRead(argv[1]);
|
|
ShowErrors(From, To);
|
|
ShowFormat(From);
|
|
|
|
cout << "Setting formats..." << endl;
|
|
To.CopyFormatFrom(From);
|
|
ShowFormat(To);
|
|
|
|
cout << "Opening output..." << endl;
|
|
To.OpenWrite(argv[2]);
|
|
ShowErrors(From, To);
|
|
|
|
cout << "Copying..." << endl;
|
|
To.CopyFrom(From);
|
|
ShowErrors(From, To);
|
|
cout << "Resulting format: " << endl;
|
|
ShowFormat(To);
|
|
cout << "Source format: " << endl;
|
|
ShowFormat(From);
|
|
|
|
cout << "Closing..." << endl;
|
|
To.Close();
|
|
From.Close();
|
|
ShowErrors(From, To);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|