/* wave.cpp Copyright (c) 1996-2002 by Timothy J. Weber. See WAVE.txt for documentation. */ #include #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 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