Documentation for module WAVE. Version 1.3. Copyright (c) 1996, 1998, 2000, 2001 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. ------------------------------------------------------------------------------- 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. 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