Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Database

Inside the RIFF Specification


SEP94: Inside the RIFF Specification

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 WAVE form requires the fmt chunk to be present before the data chunk, but there are no other restrictions on the order of chunks in a WAVE file. Additional chunks such as ZSTR, DISP, and LIST (type INFO) chunks may also be present. The fact chunk is only necessary if the waveform is not in PCM format.

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


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.