Inside the RIFF Specification
Designed with multimedia in mind
Hamish Hubbard
Hamish is a computer-science student at Canterbury University in New Zealand. He can be reached at [email protected].
The Resource Interchange File Format (RIFF) is a tagged-file specification designed for the storage of multimedia data. Data types ranging from C++ objects to full-motion video can be stored based on the RIFF specification, and new data types can be added; see Table 1.
RIFF provides a standard storage method for different types of multimedia data. Applications can ignore types of data in a RIFF file that they can't process, preventing software from becoming obsolete because of the introduction of a new variation of a data type. The specification's only major limitation is that, in its current version, the data area of a RIFF file may not exceed four gigabytes. Given the current state of the art of most PCs, this limitation isn't serious, but it may become so in a future that promises giant files such as those of full-length digital HDTV movies. Already, four gigabytes will only hold a few hours of uncompressed CD-quality audio.
Digitized waveforms recorded or synthesized on PCs are easily handled by the RIFF specification. Waveform data is one of the most readily manageable multimedia data types used on PCs. Digitized audio waveforms require far less bandwidth and CPU power to process than full-motion video, for example. Consequently, audio is the most widespread type of multimedia data in use. Wave Viewer, the application I present in this article, reads and writes RIFF files containing waveform data. Wave Viewer, which uses the Borland ObjectWindows Library (OWL 2.0) and compiles under Borland C++ 4.0, is a 32-bit application compatible with Win32s, Windows NT, and other 32-bit versions of Windows.
RIFF Internals
The basis of the RIFF specification is a data structure known as the "chunk," which contains a unique chunk-type identifier, an integer value that holds the size of the chunk's data area and the data itself. Example 1(a) is Microsoft's example of the layout of a chunk, using C syntax. RIFF and LIST chunks have an extra field at the beginning of their data areas; see Example 1(b). These examples assume that there is no padding between fields in either structure. Therefore, chunk data in a non-RIFF or LIST chunk starts at an offset of 8 bytes into the chunk (12 bytes in the case of RIFF or LIST chunks). Chunks are padded at the end to WORD (16-bit) boundaries, however.
Chunks contain data such as ASCII text, waveform data, or bitmaps; certain chunks (currently only RIFF or LIST chunks) may contain nested subchunks. The data-area size includes the size of these subchunks (if any). By splitting a file into several variable-length chunks, RIFF allows for greater flexibility than file formats defined around fixed-length and position fields.
The first chunk in a RIFF file must be a RIFF chunk with a chunk ID consisting of the four-character code RIFF. The first chunk may alternatively be a RIFX chunk--the X indicates that all integers in the file are in Motorola format. In the present version of the RIFF specification, only one RIFF or RIFX chunk is allowed per file.
The RIFF chunk contains at least one other chunk, with the number of chunks varying depending on the form type (file format) of the file and on the number of optional chunks that are present in the file. These chunks are known as "subchunks" of the RIFF chunk.
RIFF chunks have a special code at the start of the data area that specifies the form type--the type of data in the file and its format. A RIFF form is also defined by:
- A list of mandatory chunks (which must be present to make up a valid file of the aforementioned form type).
- A list of optional chunks, some or all of which may be present.
- Optionally, an order in which to store some or all of the chunks.
LIST Chunks
LIST chunks are the only chunks apart from RIFF chunks that may contain their own subchunks (although this may change). LIST chunks are usually subchunks of RIFF chunks themselves. Like RIFF chunks, LIST chunks have a four-character code in the first four bytes of their data area. This code specifies the list type (analogous to a RIFF chunk's form type) of the LIST chunk.
For example, a LIST chunk of list type INFO may contain subchunks such as INAM (the name of the data stored in the file) and ICRD (creation date). LIST chunks of type INFO are optional in current RIFF forms, but their use is recommended. The LIST chunks' subchunks can store much more information about the file than is available from the filename and date stamp. These LIST subchunks share a common format: Each contains one ASCIIZ (NULL terminated) string. The file ckinf.h (available electronically, see "Availability," page 3) describes Microsoft's recommendations about data storage for some of these chunks.
As long as LIST chunks are stored in the correct place (according to the applicable RIFF form), correctly written applications that cannot process LIST chunks will ignore their presence. Table 2 describes the layout of a typical RIFF file containing a LIST chunk.
The Wave Viewer source code contains several chunks that may be stored in a LIST chunk of list type INFO (see ckinf.h), as well as short and long descriptions of these chunk types. Some chunk types are commented out because they are inappropriate for RIFF files of form type WAVE, the only RIFF form that Wave Viewer processes. The chunk types that may be stored in a LIST chunk of list type INFO are listed separately from the other chunks. Table 3 describes an example WAVE form file.
Multiple LIST chunks may be stored in a RIFF file if they have different list types (and are therefore used to store different types of data). If an application does not allow editing of LIST (list type INFO) subchunks, it may treat the LIST chunk as though it contains nothing other than one piece of data when reading it from disk. If no errors occur during file saving, all of the subchunks will be preserved. Also, the code required to read a RIFF file remains relatively uncomplicated if the LIST chunk is treated like any other unknown chunk type.
The RIFF API
Beginning with Windows 3.0 with Multimedia Extensions, all versions of Windows have included an API known as the "multimedia file I/O services," which includes functions, data types, and messages that ease the task of navigating through, reading, and writing RIFF files of all forms. The functions, according to Microsoft's documentation, are superior to Windows and ANSI C file I/O routines in several respects. Specifically, the chunk-navigation functions decrease the complexity of the code needed to navigate the structure of a RIFF file. They have minimal CPU overhead compared with going directly to the Windows/DOS file I/O routines, and their use reduces the size of an applications's executable because the API is part of Windows instead of a statically linked library. However, Windows' multimedia file handles are incompatible with ANSI C/Windows file handles.
The basic functions of the API, including mmioOpen(), mmioClose(), mmioRead(), mmioWrite(), mmioSeek(), and mmioRename(), are fairly self-explanatory and can perform file operations on any file, although they are geared toward use with RIFF files. The Win16 versions of these functions can use huge pointers; there is no 64K limit on the amount of data that may be read or written at one time.
The functions that have no analogue in general-purpose file I/O APIs are mmioDescend(), mmioAscend(), and mmioCreateChunk(). These functions navigate through the nested structure of a RIFF file and, in the case of mmioCreateChunk(), build chunks in a file that is being written. Because these functions take care of the calculation of addresses, offsets, and chunk sizes, they simplify application code and help ensure that files are read and written correctly.
Working with RIFF files
Example 2 demonstrates the basics of reading a RIFF file. First, mmioOpen() attempts to open the file for reading; then mmioDescend(), used with the MMIO_FINDRIFF flag, finds and descends into the RIFF chunk. The MMIO_FINDRIFF flag is necessary because of the form-type fcode that occupies the beginning of RIFF chunk's data area. If mmioDescend() is successful, the current file position is set to the first byte after the form type four-character code in the RIFF chunk.
If the code is successful, the current file position is set to an offset of 12 bytes from the beginning of the chunk. This location is the first byte of the first subchunk of the RIFF chunk. mmioDescend() has filled the RIFFCkInfo structure with information about the RIFF chunk. If the code fails, then the file represented by the variable fileName is not a RIFF file of form type WAVE.
mmioDescend() may also be used to search for a chunk of a particular type by specifying the MMIO_FINDCHUNK flag. This is useful for finding chunks in a RIFF file that an application can process. If the chunk is a subchunk (that is, if it has a parent chunk), then a pointer to MMCKINFO structure previously filled by mmioDescend() with information about the parent chunk must be supplied as mmioDescend()'s third parameter. Note that mmioDescend() searches from the current position in the file, and it may be necessary to use mmioSeek() to seek back to the beginning of the parent chunk before searching. If mmioDescend() fails to find a chunk, the current file position becomes undefined.
However, Wave Viewer takes the approach of reading all of the chunks in a WAVE form file, whether the chunk types are known or not. By specifying no flags when calling mmioDescend(), the next chunk in the RIFF file (if any) is descended into and its information stored in the MMCKINFO structure passed to it as its second parameter.
mmioAscend() is the counterpart of mmioDescend(); it ascends out of a chunk that has been descended into. It moves the current file position to the first byte following the chunk that was ascended from (unless there is no more data in the file). A call to mmioAscend() should be made to leave a chunk after data has been read from it.
Writing a RIFF file
The function mmioCreateChunk() builds new chunks in an open RIFF file. A MMCKINFO structure specifies the chunk's attributes and a flag must be set if a RIFF or LIST chunk is being created (MMIO_CREATERIFF or MMIO_CREATELIST, respectively). Depending on the circumstances of the call to mmioCreateChunk(), only certain values in the MMCKINFO structure need to be set by an application; mmioCreateChunk() fills in several of the values.
If successful, mmioCreateChunk() moves the current file position to the beginning of the data area of the new chunk (and after the chunk type for RIFF or LIST chunks). The contents of the chunk may then be written using mmioWrite(), or a subchunk may be created with another call to mmioCreateChunk(). Note that mmioCreateChunk() cannot insert a chunk partway into an already existing file. If this is attempted, existing data in the file will be overwritten.
Once the contents of a chunk have been written, mmioAscend() ensures that the correct chunk-size value is written to the header of the chunk that is currently being written.
The WAVE Form
The WAVE form, which stores digitized sound in files with an extension of .WAV, is defined as:
- A RIFF chunk of form type WAVE.
- An fmt chunk containing the waveform's format.
- An optional fact chunk with format-dependent information.
- An optional cue-points chunk (indentifying various locations within the waveform data).
- An optional associated-data list (a LIST chunk of list type adtl).
- A data chunk containing waveform data.
The fmt chunk contains (at least) a WAVEFORMAT structure (defined in mmsystem.h) that contains waveform format information. Listing One is a code segment from chunkvw.cpp that shows how to decode this information. Depending on the wFormatTag member of WAVEFORMAT, there may be additional information in the fmt chunk following the WAVEFORMAT structure. For example, if the data is in PCM format (WAVE_FORMAT_PCM), there is a WORD value at the end of the chunk containing the number of bits per sample. The data chunk contains the waveform data.
Other Chunk Types
Strings can be stored in any of several chunk types defined in the RIFF specification. These strings may contain annotations or text not appropriate for any of the LIST (list type INFO) chunks. The most useful chunk is ZSTR, which may be used to store an ASCIIZ string. Two other string chunks are BSTR and WSTR, which contain size prefixes of types BYTE and WORD, respectively. All these chunk types should be stored as subchunks of a RIFF chunk.
A simple representation of a waveform may be stored in a DISP chunk, the contents of which may be in any format that the Windows clipboard can display. For example, a DISP chunk could contain an icon to be displayed if the file is embedded using OLE. A DISP chunk's data area consists of a DWORD containing a clipboard-format constant (such as CF_DIB), followed by the data used for the representation. Usage of the CF_TEXT format is discouraged; it's better to use a string chunk such as ZSTR instead.
Wave Viewer
Wave Viewer is an application written in C++ that reads and writes WAVE form files, returns information on some chunk types, and enables editing of certain chunks that contain text. It is a 32-bit Windows application compatible with Win32s, and it uses C++ features such as templates, exception handling, the ANSI string class, and run-time type identification (RTTI). At the heart of the Wave Viewer program are wvfrmdc.h (Listing Two) and wvfrmdc.cpp (Listing Three). The complete Wave Viewer program (.H, .CPP, .RC, and the like) is available electronically; see "Availability," page 3.
The only direct calls to the Windows API made by Wave Viewer are to the multimedia file I/O services. The rest of the application uses only the ObjectWindows 2.0 class library and Borland container classes. I used Borland's AppExpert to generate the outline of the application. Code demonstrating the use of the multimedia file I/O services is in the TWaveformDoc class (Listing Three), while code demonstrating the use of WAVE chunk contents is contained in the TChunkView class (chunkvw.cpp, Listing One). These two classes make up a document/view pair that is consistent with the doc/view model in ObjectWindows 2.0. These two classes may be readily transplanted into other ObjectWindows applications or modified to support RIFF forms other than WAVE.
Table 1: Form types. These are several file formats based on the RIFF specification. The form-type codes may be stored at the beginning of a RIFF chunk's data area.
Form Description CPPO APPS Software International C++ Object Format PAL Palette File Format RDIB Device Independent Bitmap Format RMID MIDI Audio Format RMMP Multimedia Movie File Format WAVE Waveform Audio Format
Table 2: Example RIFF file. This shows the layout of a simple WAVE form file as stored on disk. The "fmt" and "data" chunks are subchunks of the RIFF chunk.
Data type Description FOURCC Chunk type (for example, "RIFF") DWORD Chunk size FOURCC Form type (for example, "WAVE") FOURCC Chunk type (for example, "fmt") DWORD Chunk size BYTE[Chunk size] Chunk contents (for example, waveform format) FOURCC Chunk type (for example, "data") DWORD Chunk size BYTE[Chunk size] Chunk contents (for example, waveform data)
Table 3: An example WAVE form file. A RIFF file containing these chunks would hold a digitized waveform, and copyright and filename information. Any application unable to process the LIST chunk could safely ignore it.
Chunk type Contents Optional RIFF (WAVE) All other chunks in the file, No according to the WAVE form fmt Waveform-format information No data Waveform data No LIST (INFO) All descriptive chunks (ICOP, INAM in this example) Yes ICOP Copyright information (ASCIIZ string) Yes INAM The name of the waveform (ASCIIZ string) Yes
Example 1: (a) Microsoft's example of the chunk layout using C syntax; (b) RIFF and LIST chunks have an extra field at the beginning of their data area.
(a) typedef unsigned long DWORD; typedef unsigned char BYTE; typedef DWORD FOURCC; // Four-character code typedef struct { FOURCC ckID; // The unique chunk identifier DWORD ckSize; // The size of field <ckData> BYTE ckData[ckSize]; // The actual data of the chunk } CK; (b) typedef struct { FOURCC ckID; DWORD ckSize; union { FOURCC fccType; // RIFF form type BYTE ckData[ckSize]; } ckData; } RIFFCK;
Example 2: Reading a RIFFfile.
HMMIO HWaveFile; MMCKINFO RIFFCkInfo; HWaveFile = mmioOpen(fileName, 0, MMIO_READ); RIFFCkInfo.fccType = mmioFOURCC('W','A','V','E'); mmioDescend(HWaveFile, &RIFFCkInfo, 0, MMIO_FINDRIFF);
Listing One
/* Project WaveView Copyright 1994. All Rights Reserved. SUBSYSTEM: waveview.exe FILE: excerpted from chunkvw.cpp AUTHOR: Hamish Hubbard OVERVIEW: Source file for implementation of TChunkView (TListView). */ #include <owl\owlpch.h> #include <owl\inputdia.h> #include "riffsup.h" #pragma hdrstop #include "wvfrmdc.h" #include "chunkvw.h" #include "txtcked.h" #include "ckinf.h" . . . void TChunkView::CmEditItem () { RIFFCkArray& RIFFChunks = dynamic_cast<TWaveformDoc *>(Doc)->GetRIFFCkArray(); int index = GetSelIndex(); int arrayIndex = 0; CkInfo ckInfo; GETCKINFO(RIFFChunks[index], &ckInfo); // Determine whether the chunk selected by the user is s text-chunk // and if so, allow user to edit it via the Text-chunk Editor dialog. // Otherwise, display information about fmt , data, etc chunks. if (ckInfo.ckID == mmioFOURCC('f', 'm', 't', ' ')) { // Display format-information. string fmtStr; WAVEFORMAT *waveFormat = (WAVEFORMAT *)(RIFFChunks[index] + sizeof(CkInfo)); char numStr[100]; // WAVE_FORMAT_PCM (0x0001) is only format defined in regular // mmsystem.h, but there are several others defined by MS. fmtStr += "Format type # : "; fmtStr += itoa(waveFormat->wFormatTag, numStr, 10); fmtStr += "\nNumber of channels (1 = mono, 2 = stereo) : "; fmtStr += itoa(waveFormat->nChannels, numStr, 10); fmtStr += "\nSamples per second : "; fmtStr += ultoa(waveFormat->nSamplesPerSec, numStr, 10); fmtStr += "\nAverage # bytes per second : "; fmtStr += ultoa(waveFormat->nAvgBytesPerSec, numStr, 10); fmtStr += "\nBlock alignment (minimum unit of data) : "; fmtStr += ultoa(waveFormat->nBlockAlign, numStr, 10); // If the data is in PCM format then there is extra information // to be extracted from the FMT structure. if (waveFormat->wFormatTag == WAVE_FORMAT_PCM) { fmtStr += "\nBits per sample : "; fmtStr += itoa( ((PCMWAVEFORMAT *)waveFormat)-> wBitsPerSample, numStr, 10); } MessageBox(fmtStr.c_str(), "'fmt ' Chunk Information"); return; } // Display basic information about a 'data' chunk. if (ckInfo.ckID == mmioFOURCC('d', 'a', 't', 'a')) { char sizeStr[100]; string dataStr; dataStr = "Size of data chunk : "; dataStr += itoa(ckInfo.ckSize, sizeStr, 10); dataStr += " bytes"; MessageBox(dataStr.c_str(), "'data' Chunk Information"); return; } while (CkDesc[arrayIndex].FOURCCStr != 0) { if (ckInfo.ckID==mmioStringToFOURCC(CkDesc[arrayIndex].FOURCCStr,0)) { string ckStr = (RIFFChunks[index] + sizeof (CkInfo)); string desc = CkDesc[arrayIndex].longDesc; TextCkEditDlg(this, ckStr, desc).Execute(); // Set chunk-size information for edited chunk // by getting length of text string and adding 1 to // account for necessary null terminator. ckInfo.ckSize = ckStr.length() + 1; char *buffer; try { buffer = new char[ckInfo.ckSize + sizeof (CkInfo)]; } catch (xalloc) { MessageBox("Error: Out of memory."); return; } memcpy(buffer + sizeof(CkInfo), ckStr.c_str(), ckInfo.ckSize); SETCKINFO(buffer, &ckInfo); RIFFChunks.Destroy(index); RIFFChunks.AddAt(buffer, index); // Assume that text of this chunk has been modified. dynamic_cast<TWaveformDoc *>(Doc)->SetDirtyFlag(TRUE); return; } arrayIndex++; } } . . .
Listing Two
#if !defined(__wvfrmdc_h) // Sentry, use only if it's not already included. #define __wvfrmdc_h /* Project WaveView -- Copyright 1994. All Rights Reserved. SUBSYSTEM: waveview.exe FILE: wvfrmdc.h -- AUTHOR: Hamish Hubbard OVERVIEW: Class definition for TWaveformDoc (TFileDocument). */ #include <owl\owlpch.h> #include <owl\docview.h> #include <owl\filedoc.h> #include <cstring.h> #include "chunkds.h" // RIFFCkArray (TArrayAsVector) type, etc. #pragma hdrstop #include "wvapp.rh" // Definition of all resources. class TWaveformDoc : public TFileDocument { public: TWaveformDoc (TDocument* parent = 0); ~TWaveformDoc (); virtual BOOL Open (int mode, const char far *path = 0); virtual BOOL Commit (BOOL force = FALSE); void SetDirtyFlag(BOOL flag = TRUE) { DirtyFlag = flag; } public: // TXFile is a class to be used to hold state information when throwing // exceptions during execution of code that reads or writes files. class TXFile { private: string errorMsg; BOOL closeFile; public: TXFile (const string& msg = "", BOOL close = TRUE) { errorMsg = msg; closeFile = close; } const string& GetMessage () { return errorMsg; } const BOOL GetClose () { return closeFile; } }; RIFFCkArray& GetRIFFCkArray() { return *RIFFChunks; } protected: virtual BOOL ReadWaveFile (int omode, const char *name); virtual BOOL WriteWaveFile (); private: void ReadSubchunks (const HMMIO HWaveFile, MMCKINFO& parentCkInfo) throw (TXFile, xalloc); void WriteSubchunks (const HMMIO HWaveFile) throw (TXFile); private: RIFFCkArray *RIFFChunks; UINT chunkDepth; // Specifies number of chunks that have // been descended into. int saveIndex; // Index of the next chunk to be saved in the array. }; #endif // __wvfrmdc_h sentry.
Listing Three
/* Project WaveView Copyright 1994. All Rights Reserved. SUBSYSTEM: WaveView.exe Application FILE: wvfrmdc.cpp -- AUTHOR: Hamish Hubbard OVERVIEW: Source file for implementation of TWaveformDoc (TFileDocument). */ #include <owl\owlpch.h> #pragma hdrstop #include "wvfrmdc.h" TWaveformDoc::TWaveformDoc (TDocument* parent) : TFileDocument(parent) { RIFFChunks = new RIFFCkArray(1, 0, 4); chunkDepth = 0; saveIndex = 0; } TWaveformDoc::~TWaveformDoc () { // Empty RIFFChunks array, deleting contents of each chunk (thereby freeing // memory occupied by each chunk). RIFFChunk's object is then destroyed. RIFFChunks->Flush(); delete RIFFChunks; } BOOL TWaveformDoc::Open (int mode, LPCSTR path) { if (path) SetDocPath(path); if (mode != 0) SetOpenMode(mode); return ReadWaveFile(GetOpenMode(), GetDocPath()); } BOOL TWaveformDoc::ReadWaveFile (int, const char *name) { // Attempt to open the file named by name as a RIFF File. If successful, // read information from the file relating to digitized waveform data // and enable the client dialog window. HWaveFile is a RIFF API file // handle. It is not compatible with regular Windows file handles. HMMIO HWaveFile; MMCKINFO parentCkInfo; chunkDepth = 0; try { // Open file specified in fileName for reading, using buffered I/O. HWaveFile = mmioOpen((const_cast<char *>(name), 0, MMIO_READ); if (!HWaveFile) throw (TXFile("Unable to open the file.", FALSE)); parentCkInfo.fccType = mmioFOURCC('W','A','V','E'); if (mmioDescend(HWaveFile, &parentCkInfo, 0, MMIO_FINDRIFF)) throw TXFile("The file is not a RIFF file containing waveform data."); CkInfo *waveCkInfo = new CkInfo; waveCkInfo->ckSize = 0; waveCkInfo->ckID = parentCkInfo.ckid; waveCkInfo->ckType = parentCkInfo.fccType; waveCkInfo->ckDepth = chunkDepth; RIFFChunks->Add((char *)waveCkInfo); chunkDepth++; // Read the subchunks contained in the WAVE chunk. ReadSubchunks(HWaveFile, parentCkInfo); } // Handle TXFile-type exceptions (errors during file reading/writing). catch (TXFile xFile) { // If something fails, clean up and bail out. if(xFile.GetClose() == TRUE) mmioClose(HWaveFile, 0); return FALSE; } // Handle xalloc exceptions (thrown by operator new). catch (xalloc) { mmioClose(HWaveFile, 0); return FALSE; } mmioClose(HWaveFile, 0); return TRUE; } void TWaveformDoc::ReadSubchunks (const HMMIO HWaveFile,MMCKINFO& parentCkInfo) throw(TWaveformDoc::TXFile, xalloc) { MMCKINFO subCkInfo; CkInfo ckInfo; char *ckContents; while (mmioDescend(HWaveFile, &subCkInfo, 0, 0) == 0) { // Prevent ascending past the end of the file. if ((subCkInfo.dwDataOffset + subCkInfo.cksize) > (parentCkInfo.dwDataOffset + parentCkInfo.cksize)) throw(TXFile("The file contains corrupt or damaged information.")); // Fill in some of the details about the chunk. ckInfo.ckID = subCkInfo.ckid; ckInfo.ckType = subCkInfo.fccType; ckInfo.ckSize = subCkInfo.cksize; ckInfo.ckDepth = chunkDepth; chunkDepth++; switch (subCkInfo.ckid) { case mmioFOURCC('L', 'I', 'S', 'T'): ckInfo.ckSize = 0; ckContents = new char[sizeof(CkInfo)]; SETCKINFO(ckContents, &ckInfo); RIFFChunks->Add(ckContents); ReadSubchunks(HWaveFile, parentCkInfo); break; default: ckInfo.ckID = subCkInfo.ckid; ckInfo.ckType = subCkInfo.fccType; ckContents=new char[ckInfo.ckSize+sizeof(CkInfo)]; mmioRead(HWaveFile,ckContents+sizeof(CkInfo), ckInfo.ckSize); SETCKINFO(ckContents, &ckInfo); RIFFChunks->Add(ckContents); break; } // Ascend out of the current subchunk. mmioAscend(HWaveFile, &subCkInfo, 0); chunkDepth--; } } // TWaveformDoc -- Save the document to permanent storage. BOOL TWaveformDoc::Commit (BOOL force) { if (TDocument::Commit(force) == FALSE) return FALSE; return WriteWaveFile(); } BOOL TWaveformDoc::WriteWaveFile () { // HWaveFile is a RIFF API file handle. HMMIO HWaveFile; MMCKINFO MMCkInfo; try { saveIndex = 0; // Attempt to open the file for saving to permanent storage. // The cast (char *) in the function below is a bit ugly... HWaveFile = mmioOpen(const_cast<char *>GetDocPath()), 0, MMIO_WRITE | MMIO_CREATE); if (HWaveFile == 0) throw TXFile("Error: Unable to open the file for saving.", FALSE); WriteSubchunks(HWaveFile); if (mmioAscend(HWaveFile, &MMCkInfo, 0) != 0) throw TXFile("Error: Unable to save a chunk to the file."); if (mmioClose(HWaveFile, 0) != 0) throw TXFile("Error: Unable to close the file being saved."); } catch (TXFile xFile) { if (xFile.GetClose() == TRUE) mmioClose(HWaveFile, 0); return FALSE; } return TRUE; } void TWaveformDoc::WriteSubchunks (const HMMIO HWaveFile) throw (TWaveformDoc::TXFile) { MMCKINFO MMCkInfo; CkInfo ckInfo; DWORD flags; while (saveIndex < RIFFChunks->GetItemsInContainer()) { GETCKINFO((*RIFFChunks)[saveIndex], &ckInfo); MMCkInfo.fccType = ckInfo.ckType; MMCkInfo.ckid = ckInfo.ckID; MMCkInfo.dwFlags = 0; switch (MMCkInfo.ckid) { case mmioFOURCC('R', 'I', 'F', 'F'): flags = MMIO_CREATERIFF; break; case mmioFOURCC('L', 'I', 'S', 'T'): flags = MMIO_CREATELIST; break; default: flags = 0; break; } if (mmioCreateChunk(HWaveFile, &MMCkInfo, flags) != 0) throw TXFile("Error: A chunk could not be created during saving."); if (ckInfo.ckSize > 0) if (mmioWrite(HWaveFile, (*RIFFChunks)[saveIndex] + sizeof (CkInfo), ckInfo.ckSize) == -1) throw TXFile("Error: A chunk could not be saved correctly."); saveIndex++; if (saveIndex + 1 < RIFFChunks->GetItemsInContainer()) { CkInfo nextCkInfo; GETCKINFO((*RIFFChunks)[saveIndex + 1], &nextCkInfo); switch (MMCkInfo.ckid) { case mmioFOURCC('L', 'I', 'S', 'T'): case mmioFOURCC('R', 'I', 'F', 'F'): WriteSubchunks(HWaveFile); break; default: break; } if (mmioAscend(HWaveFile, &MMCkInfo, 0) != 0) throw TXFile("Error: A chunk could not be saved correctly."); if (nextCkInfo.ckDepth < ckInfo.ckDepth) return; } else // Ascend out of the last chunk. if (mmioAscend(HWaveFile, &MMCkInfo, 0) != 0) throw TXFile("Error: A chunk could not be saved correctly."); } }
Copyright © 1994, Dr. Dobb's Journal