NIFF SDK User's Guide

For Version 1.03

17 March 1997

Timothy Butler


Table of Contents


Introduction

This document is incomplete. Most noticably absent is a reference section describing each function and type in detail. However, detailed reference documentation does appear in the source code and should not be difficult to find.

The NIFF SDK is a software development kit that supports the Notation Interchange File Format (NIFF). It is a collection of software libraries and tools, written in ANSI C, that read, write, and navigate NIFF files.

NIFF allows the interchange of music notation data among music notation editing, publishing, and scanning programs. Because music notation is a complex and evolving language the NIFF designers limited its scope to serve as simply a container for standard musical elements. As a result, a NIFF application must perform two tasks:.

  1. Read and write the standard NIFF elements.
  2. Interpret the elements according to the application.

The NIFF SDK handles the first task of reading, writing, and navigating a NIFF file. There is great flexiblity in how to interpret a NIFF file but that is beyond the scope of the NIFF SDK. Indeed, the NIFF designers expect no two applications to make use of NIFF in exactly the same way.

The NIFF SDK aspires to

Non-features

The NIFF SDK won't

Features

There are three major components to the NIFF SDK.

RIFFIO
Libraries and tools for manipulating RIFF files.
NIFFIO
Libraries and tools for manipulating NIFF files. Uses RIFFIO.
NIFF
"Official" files from the NIFF Project (really only `niff.h' for now).

RIFFIO

NIFFIO

Copying

This document and the accompanying NIFF SDK are public domain. This is done in the spirit of promoting the NIFF standard and the free exchange of information.

Platforms

The NIFF SDK has been compiled and partially tested on the following platforms:

Obtaining

You may anonymous FTP the NIFF SDK from URL

ftp://blackbox.cartah.washington.edu/pub/NIFF

It is available in zip, compressed tar, and GNU compressed tar formats.

Installing

Unix Platforms

Unix users can use the configuration script provided and then run make.

  1. Change the current directory to the top of the distribution.
            cd niffsdk-1.03
    
  2. Run configure.
            ./configure --prefix=/home/tim
    
    If you don't know what options configure takes then run ./configure --help.
  3. Run make
            make all
    
  4. (optional) Test the build.
            make check
    
  5. (optional) Install the build.
            make install
    

The following make targets also exist.

clean
Remove everything that was built.
mostlyclean
Like clean but leave libraries and executables.
distclean
Remove everything, leaving only what was in the distribution. You will be forced to run configure before you can make again.
maintainer-clean
Remove everything that can be remade from scratch. Caution: maintainer-clean removes files that you can't regenerate without the tools used to develop the NIFF SDK itself.

Unix Build Notes

There are some things to watch out for with the current build system.

Non-Unix Platforms

If you aren't runing Unix then you are on your own (for now). You must track down all of the sources tucked away in the nooks and crannies of the distribution.

The most important parts that you need to find are:

`riffio/src/lib/riffio'
The RIFFIO library sources.
`riffio/src/lib/stdcriff'
The STDCRIFF library sources.
`riffio/include/{riffio,stdcriff}.h'
RIFFIO header files.
`niff/include/niff.h'
`niffio/src/lib/niffio'
The NIFFIO library sources.
`niffio/src/include/niffio.h'
NIFFIO header file.

Once you build the libraries you should try building the "hello world" application located in `riffio/src/example/hello'. Link in RIFFIO and STDCRIFF. Also try writing a simple NIFF file using `niffio/src/example/nif001' and linking in all of the libraries.

Layout

Each component of the NIFF SDK (riffio, niffio, and niff) has its own directory under the top of the distribution. Each directory may contain:

`include'
source include files (.h)
`doc'
documentation
`mk'
Makefile includes and templates for Unix builds
`src/lib'
source code for libraries
`src/cmd'
source code for executable tools
`src/example'
example source code (required for several tests)
`src/test'
source code for tests
`test'
test suite

Related Documents

This guide assumes the reader is familiar with NIFF, RIFF, and the C programming language. For detailed information on the NIFF format and a summary of RIFF, please see the current NIFF specification at URL

        ftp://blackbox.cartah.washington.edu/pub/NIFF

For detailed information on the RIFF standard please see

Microsoft Press, Microsoft Windows Multimedia Programmer's Reference,
Redmond WA, 1991

Telephone: 1-800-MSPRESS, ISBN#1-55615389-9

Acknowledgements

Both Cindy Grande and Mark Walsen contributed to the original design and specification of the NIFF SDK. Besides keeping the NIFF Project on track, Cindy Grande also assembled the NIFF SDK Version 1.00 when I was unavailable. Thanks to Steve Mounce for test driving the earliest versions.

Background

This chapter is an overview of the RIFF and NIFF file formats from the perspective of the NIFF SDK. Even if you are familiar with these file formats this chapter introduces some terms that are used throughout this document.

RIFF

RIFF itself, is not a file format. Rather, RIFF provides a framework for defining a family of file formats that share a common structure at the highest levels. The basic component of a RIFF file is a chunk. File formats that are based on RIFF, such as WAVE and NIFF, define different types of chunks that may meaningfully appear in their respective files. A RIFF-based file format will also define the contents of its different chunk types.

Some chunks, called lists, contain other chunks. Subchunks of lists may be primitive chunks, which contain data defined by a specific file-format or subchunks may themselves be nested lists. (1) @footnote {I've seen some documentation that (IMHO) incorrectly refers to primitive chunks as "subchunks". I use "subchunks" to refer to elements of lists. I use "primitive chunks" to mean "chunks that aren't lists." Usually I will omit the word "primitive" when referring to "chunks and lists."} .


                    chunk <------------------+
                      |                      |
                      |                      | (contains zero or more)
                     / \                     |
           +---------------------+           |
           |                     |           |
   primitive chunk              list  -------+

The first and top-most chunk in a RIFF file must be a list that identifies the file type and contains all of the other chunks in the file. This special list is called a form. There can be only one form per file and it constitutes the entire file.

Chunks

A chunk consists of:

id
Four ASCII characters that identify the type of the chunk. These make up an object called a four-character code.
size
An unsigned 32-bit integer recording the size, in bytes, of the chunk's data. This value does not account for the bytes used by the id, size, or possible pad byte. The byte order is determined by the type of the RIFF file (see section Forms).
data
A variable number of bytes that make up the information stored in the chunk. The number of data bytes is the chunk's size.
pad byte
Chunks must always occupy an even number of bytes. If the chunk size is odd, then a byte with value 0 (NUL) is appended after the chunk data. The value of size does not include this value; rather its presence is implied if size is odd.
             Chunk
        +---------------+
        | id            |
        |  (4 bytes)    |
        +---------------+
        | size: n       |  
        |  (4 bytes)    |
        +---------------+
        | data          |
        |  ( n bytes)   |
        +---------------+
        | pad           |
        |  ( n mod 2)   |
        +---------------+

Lists

Chunks whose data consists of other chunks are called lists. Lists have a couple of constriants on their structure. A list's id must be "LIST". Furthermore a list's data consists of:

type
A four-character code identifying the type of the list. Because all lists have an id of "LIST", this serves the purpose that id does for ordinary chunks.
subchunks
A sequence of zero or more chunks making up the list's elements. Each subchunk may (or may not) be a list.

A list's data can only hold one type value and the subchunks; no other data is allowed. A list's size is the sum of the sizes of its type (4 bytes) and subchunks. A list will never have a pad byte because all of its components will have even sizes.

Here is how a list fits into the definition of a chunk.

             Chunk               List
        +---------------+  +---------------+
        | id            |  | id: "LIST"    |
        |  (4 bytes)    |  |  (4 bytes)    |
        +---------------+  +---------------+
        | size: n       |  | size: n       |
        |  (4 bytes)    |  |  (4 bytes)    |
        +---------------+  +---------------+
        | data          |  | type          |
        |  ( n bytes)   |  |  (4 bytes))   |
        |               |  |---------------|
        |               |  | subchunks     |
        |               |  |  (n-4 bytes)  |
        +---------------+  +---------------+

Forms

A RIFF file consists of one top-level list called a form that identifies the file type.

A form is exactly like a list except for the following:

A form is a specialized list and when this document refers to "lists" you may assume this includes forms. All NIFF SDK operations that operate on a list also operate on a form.

Composing Chunks

A RIFF file can be viewed as a tree of chunks and lists, rooted at its form chunk. In addition, there is some order imposed on the chunks simply by their position in the RIFF file. Thus a RIFF file looks like an outline.


Form RIFF
|
+--List A
|  |
|  +--Chunk B
|  |
|  +--Chunk C
|
+--List D
|  |
|  +--Chunk E
|
+--Chunk F

Chunk Operations

There are a few operations on chunks that are useful for reading and writing RIFF files. These operations position the RIFF file at specific places as they navigate through the branches of the tree. Their behaviour depends on whether they are operating on a primitive chunk or a list.

Descend

Starting with the RIFF file positioned before a chunk, read a chunk's id, size, and list type (if it exists).

Ascend

Advance the file past a chunk, including any pad byte.

Create

A create operation starts writing a new chunk.

Write the id, size, and (possibly) type of a chunk at the current file position. Leave the file positioned to write a primitive chunk's data or a list's subchunks.

Finalize

Usually it is too difficult to predict the size of a chunk before its contents are written. The finalize operation establishes the end of a chunk's data at the current file position by updating the chunk's size and writing a pad byte if necessary.

A finalize operation usually involves seeking backwards in the file to a chunk's size if its existing value is incorrect.

NIFF

The RIFF standard only specifies file structure down to the primitive chunk level. NIFF extends RIFF in the following ways:

Inside Chunks

The contents of a chunk's data is defined and limited by each chunk type. Chunks may contain specific combinations of the following:

Some NIFF chunks start with a fixed size structure followed by a variable number of tags (see section Tags).

The NIFF SDK must read, write, and navigate through tags within chunks.

Chunk Property Structures

Most NIFF chunks begin their data with a fixed-length property structure that holds values specific to the chunk type.

For example, every Notehead chunk's data begins with the following property structure.

        (BYTE)       shape
        (SIGNEDBYTE) staff step
        (RATIONAL)   duration

The size of a chunk's property structure is stored in the NIFFIO file's Chunk Length Table. This is so a NIFF reader can skip past the structure without having the structure size hard-coded (2).

The NIFF SDK defines chunks that have property structures as cooked chunks. Chunks without property structures are called raw chunks. This distinction is made because some function signatures in the NIFF SDK differ depending on if the functions operate on a chunk with a property structure.

Chunk Tags

Following the property structure (if it is present) may be zero or more tags. Tags are like mini-chunks that modify the meaning or augment their containing chunks. See section Tags. No additional chunk data is allowed after the tags.

The NIFF SDK defines a taggable chunk as one that may have a property structure followed by tags.

Atomic Chunks

Some chunks contain a variable amount of data that is not contained in a property structure or tags. One example, a EPS Graphic chunk, contains a variable sized Postscript description of a graphic. This atomic chunk does not have a property structure and cannot have any tags. Other examples of atomic chunks include the Chunk Length Table and the String Table. Even though a Chunk Length Table does contain structured entries, the NIFF SDK doesn not consider these to be property structures and there is a variable number of them.

Chunk Classification Summary

The NIFF SDK classifies chunks according to following rules:

Can a chunk have tags?
If a chunk can have tags then it is taggable. If no tags are allowed, then the chunk is atomic.
Does the chunk have a property structure?
An chunk with a property structure is a cooked chunk. That means the NIFF SDK has routines that associate the structure with the chunk. A raw chunk has no property structure. All atomic chunks are raw. There are a few raw, taggable chunks such as the Tag Activate chunk. This chunk has no property structure, but tags are allowed.

These rules may seem half-baked at first glance, but the NIFF SDK is very picky about defining functions so that they match the classification of chunks (and tags). This forces NIFF SDK clients to be aware of the property structures associated with chunks and tags.

Tags

Each tag modifies or augments the meaning of its associated chunk. Much like leaves on a branch, tags are the outermost elements of a NIFF file. NIFF permits tags on some taggable chunks but not on other atomic chunks.

Following a chunk's property structure are zero or more tags that modify or augment the meaning of the chunk.

Tags are like miniature chunks. They have single-byte id and size fields. Thus tag data can range in size from 0 to 255 bytes. Like chunks, tags must be padded to an even number of bytes.

             Tag
        +---------------+
        | id            |
        |  (1 byte)     |
        +---------------+
        | size: n       | 
        |  (1 byte)     |
        +---------------+
        | data          |
        |  ( n bytes)   |
        +---------------+
        | pad           |
        |  ( n mod 2)   |
        +---------------+

Inside Tags

Depending on the kind of tag, a tag will contain either its own fixed-length property structure or a variable number of bytes. Tags are primitive elements; they cannot contain other tags.

The NIFF SDK refers to a tag that contains a property structure as a cooked tag. A tag that contains a variable amount of data is a raw tag.

Tag Operations

Tags have operations similar to those defined for chunks

descend
With the NIFF file postioned before a tag, read a tag's id and size. Leave the NIFF file positioned at the start of the tag's data.
ascend
Given a tag, position the NIFF file after the tag's data and (possible) pad byte.
create
Write a tag's id and size to a NIFF file at the current location. Leave the file positioned to write the tag's data.
finalize
Establish the current file position as the end of a tag. Update that tag's size and write a pad byte as required. Leave the file positioned immediately after the tag.

Chunk Length Table

The Chunk Length Table is a special chunk that records the sizes of each kind of chunk's property structure. This ensures that a NIFF reader can locate a chunk's tags even if the property structure size changes with a new version of the NIFF standard (this assumes that the property structure will never shrink). Atomic chunks appear in the Chunk Length Table with a property structure size of -1. Raw, taggable chunks have a property structure size of 0. Lists do not appear in the Chunk Length Table.

NIFF is binary

NIFF is a binary file format. Integral types are stored in a NIFF file in a specific (big-endian, Motorola) format. The NIFF SDK portably translates between local machine byte-order and NIFF byte-order when reading and writing integral types(3).

NIFF SDK Basics

Naming conventions

Function and Type Names

One of my goals in developing the NIFF SDK was to create a resusable library that could handle any RIFF (or RIFX) file. That library is called RIFFIO, and NIFFIO uses it extensively.

Another goal was to keep the NIFF project's `niff.h' file independent of NIFFIO or RIFFIO. All of the NIFFIO functions and data types begin with "NIFFIO". RIFFIO names are prefixed with "RIFFIO". As a result, you will end up using names and calling functions from (at least) three different sources.

For example, many of the NIFFIO functions return a success code type of RIFFIOSuccess. The four-character code data type is called RIFFIOFOURCC -- even when it is used by NIFFIO.

Sometimes the names get long. I justify this by keeping the names predictable. You will often see a family of functions that differ only by the name of the NIFF chunk or tag that they manipulate. The NIFF name is taken from that found in `niff.h'.

I am certain to break somebody's linker.

Hungarian Notation

Many variable names begin with a lower case prefix that denotes their type. A capitalized descriptive name often follows when the meaning of the variable is not perfectly clear.

A "p" is prepended for pointers to specific types. Occasionally you might see an "a" prepened to arrays of types. For example, fccId is a four-character code that identifies something. pnf is a pointer to a NIFFIOFile.

Functions are not prefixed with their return type. When the naming convention interferes with clarity, I break the rules.

Here are some of the prefixes used:

str
NUL-terminated string
n
integral number of things
rf
RIFFIOFile
nf
NIFFIOFile
offset
RIFFIOOffset
fcc
Four-character code
chunk
RIFFIOChunk
size
RIFFIOSize
tag
NIFFIOTag
chunkctx
NIFFIOChunkContext
tagctx
NIFFIOTagContext
userctx
NIFFIOUserContext
clt
NIFFIOChunkLengthTable
stbl
NIFFIOStbl (String Table)

Fundamental Types

Basic NIFF Types

The NIFF SDK uses the following types from `niff.h'.


typedef unsigned char   BYTE;           /* a one byte unsigned integer */
typedef signed char     SIGNEDBYTE;     /* a one byte signed integer */
typedef unsigned char   CHAR;           /* a one byte ascii character */
typedef signed short    SHORT;          /* a two byte signed integer */
typedef signed long     LONG;           /* a four byte signed integer */
typedef unsigned long   DWORD;          /* used in MAKEFOURCC macro     */

typedef struct {
    SHORT   numerator;
    SHORT   denominator;
}                       RATIONAL;       /* two 2-byte signed
                                           integers, used for timing
                                           representation */

typedef long            STROFFSET;       /* a four byte signed integer
                                           pointing to a RIFF ZSTR in
                                           the String Table chunk in
                                           the Setup Section. */

typedef unsigned short  FONTIDX;         /* a 2-byte index to a Font
                                            Description chunk in the
                                            Font Descriptions list. */

Unused NIFF types

The NIFF SDK does not use the NIFF_TAG, NIFF_CHUNK, NIFF_LIST, or NIFF_RIFF_FORM types from `niff.h'. Instead, the NIFF SDK provides its own data structures for tags, chunks, lists, and forms.

You may notice that FOURCC is also missing from the list types used from `niff.h'. The NIFF SDK uses the equivalent type RIFFIOFOURCC instead; with its accompanying RIFFIOMAKEFOURCC macro (4).

Four-character Codes

The first thing to know about four-character codes is that there is probably already a macro in `niff.h', `niffio.h', or `riffio.h' for the code that you need.

RIFFIOFOURCC's store four ASCII characters in an unsigned long. RIFFIO provides utilities to build four-character codes from character chars and to decompose them into strings. Use them!

RIFFIOMAKEFOURCC() is a macro that assembles four characters into a RIFFIOFOURCC. RIFFIOFOURCCToString() formats the characters of a RIFFIOFOURCC into a NUL-terminated string. The user must provide a string that is already allocated to hold at least RIFFIO_FOURCC_LIM characters.

RIFFIOFOURCC Example

Create and print a four-character code.


RIFFIOFOURCC fcc;

char         str[RIFFIO_FOURCC_LIM];

fcc = RIFFIOMAKEFOURCC('N','I','F','F');

RIFFIOFOURCCToString(fcc, str);

printf("fcc == <%s>\n", str);

FOURCC Hack

You may be tempted to cast the address of a four-character code to a string (of known length but no NUL termination). Don't do it!

Four-character Code Rules

There are some rules that apply to four-character codes. You can use RIFFIOFOURCCIsValid() to check for compliance (5).

Chunk and Tag Identifiers

`niff.h' provides names for all of the four-character code chunk identifiers and the single-byte tag ID's.

#define niffckidLyric  MAKEFOURCC('l','y','r','c')

enum {  nifftagPartID = 0x20};

Success Codes

Many NIFF SDK functions return a boolean value of true if they complete without errors.These functions are declared as returning the type RIFFIOSuccess to emphasize the meaning of the return value. The enumerated values RIFFIO_OK and RIFFIO_FAIL are provided for functions returning a RIFFIOSuccess.

Provisions exist for functions to set an error code that distinguishes between error types. Presently, this value is unused.

Property Structure Definitions

The NIFF SDK uses the structures in niff.h that define the properites of tags and chunks. Each cooked chunk and tag ObjectName has an associated structure called "niffObjectName" that records the properties of the object.

Property Example

For example here is the definition of a Notehead structure.

typedef struct  niffNotehead
{
        BYTE        shape;
        SIGNEDBYTE  staffStep;
        RATIONAL    duration;
}       niffNotehead;

A Height tag is just a short, so it is not stored in a structure. But you don't need to know that. All you have to refer to is a niffHeight.

typedef SHORT       niffHeight;

Enumerated Property Values

For properties that encode values within integers `niff.h' provides enumerated types. For example, a Notehead's shape is encoded with these values:

enum
{       noteshapeBreve  = 1,
        noteshapeWhole  = 2,
        noteshapeHalf   = 3,
        noteshapeFilled = 4,
        ...
}

Chunk Length Constants

Each chunk has an associated constant that describes the size of the property structure in the NIFF file. This is the same value that gets stored in the Chunk Length Table.

Here are some example chunk length constants:

#define niffcklenPart 14
#define niffcklenTagInactivate  0
#define niffcklenPsType1Font -1

The Tag Inactivate chunk has no property structure but it allows tags. The Ps Type1 Font chunk cannot have tags.

Writing NIFF Files

This chapter introduces the use of NIFFIOStorage routines to write a NIFF file. A NIFFIOStorage object and it associated routines provide a high level interface for building NIFF files.

NIFFIOStorage objects also provide additional features that are not demonstrated in this chapter.

Writing Example

Here is an example program that writes a NIFF file that contains only a Setup Section describing two Parts. This example does not involve writing any tags, but tags are created in much the same way as chunks. Return values indicating possible error conditions are ignored in these examples for brevity.

Writing Example File

This is the structure of the file that will be written.

NIFF
|
+--Setup Section
   |
   +--Chunk Length Table
   |
   +--Niff Info
   |
   +--Parts
   |  |
   |  +--Part
   |  |
   |  +--Part
   |
   +--String Length Table

Writing Example Program

#include <stdio.h>
#include <niff.h>
#include <niffio.h>

unsigned char Version[8] = "6b";

/*
 * Values for the String table
 * Offsets will be filled in by NIFFIOStoreStbl().
 */
#define NUM_STRINGS 4
NIFFIOStblEntry MyStbl[NUM_STRINGS] =
{
    /* Offset, String */
    0, "piano",
    0, "p",
    0, "violin",
    0, "vl"
};

int 
main()
{
    FILE           fp;
    NIFFIOStorage *pstore;

    fp     = fopen("test.nif", "wb");
    pstore = NIFFIOStorageNewSTDC(fp);

    NIFFIOStartNiff();
      NIFFIOStartSetupSection();

        NIFFIOStoreDefaultCLT();

        NIFFIOchunkNiffInfo(Version, progtypeNone, unitsNoUnits, -1 ,-1);

        NIFFIOStartParts();
          NIFFIOchunkPart(0,0,1,2,-1,-1,-1);
          NIFFIOchunkPart(0,2,3,1,-1,-1,-1);
        NIFFIOEndParts();

        NIFFIOchunkStringTable();
          NIFFIOStoreStbl(MyStbl, NUM_STRINGS);

      NIFFIOEndSetupSection();
    NIFFIOEndNiff();

    NIFFIOStorageDelete(pstore);
    fclose(outfp);
}

Writing Example Details

Includes


#include <stdio.h>
#include <niff.h>
#include <niffio.h>

The NIFFIOStorage routines are declared in `niffio.h'. Some enumerated symbols, such as progtypeNone come from `niff.h'.

Version

unsigned char Version[8] = "6b";

You need a string to record the NIFF version in the Niff Info chunk. (6)

Strings

/*
 * Values for the String table
 * Offsets will be filled in by NIFFIOStoreStbl().
 */
#define NUM_STRINGS 4
NIFFIOStblEntry MyStbl[NUM_STRINGS] =
{
    /* Offset, String */
    0, "piano",
    0, "p",
    0, "violin",
    0, "vl"
};

The NIFF SDK provides a NIFFIOStbl structure to represent each entry in the string table (one structure per entry). Each NIFFIOStblEntry stores the offset to the string and a pointer to the string in memory. These offsets are zero because they have not yet been determined. NIFFIOStoreStbl will fill in the proper values later.

Creating a new NIFFIOStorage


FILE           fp;
NIFFIOStorage *pstore;

fp     = fopen("test.nif", "wb");
pstore = NIFFIOStorageNewSTDC(fp);

A NIFFIOStorage object records the state of a NIFF file in the process of writing (or storing) its form, lists, chunks, and tags. You can construct a NIFFIOStorage from a Standard C Library FILE pointer. That file must be opened in binary mode.

Admittedly, NIFFIOStorageNewSTDC() is an ugly, long name, but here is what it does:

Most NIFF SDK objects, such as NIFFIOStorage are allocated and freed using function names of the form NIFFIOXXXNew() and NIFFIOXXXDelete(). Then they may be initialized, for example, with a a FILE pointer. In this case, you call a special allocator that takes a FILE pointer as an argument. The function NIFFIOStorageNewSTDC() is provided for convenience.

Now you can start writing the NIFF form.

Starting a Form

NIFFIOStartNiff();

This writes the header for the NIFF form.

Starting a List


NIFFIOStartSetupSection();

The first item in the form is the Setup Section list. All lists begin with a call to NIFFIOStartXXX(), where XXX is the name of the list. Eventually these must be matched by calls to NIFFIOEndXXX(). Lists need explicit "start" and "end" calls because they can be nested.

Chunk Length Table


NIFFIOStoreDefaultCLT();

The first chunk is a Chunk Length Table. You only really need two entries for the Niff Info and Part chunks. Even so, the NIFF SDK provides a default chunk length table with all the entries you would ever need; and it is easy to create.

The Chunk Length Table is treated specially by the NIFF SDK because it is a raw chunk with its own special structure. Almost all the other chunks are "stored" in a more uniform way.

Typical Chunk


NIFFIOchunkNiffInfo(Version, progtypeNone, unitsNoUnits, -1 ,-1);

Each type of chunk, XXX, has a corresponding function called NIFFIOchunkXXX() that writes the chunk header and the chunk's properties. The properties are passed as function arguments.

SubChunks


NIFFIOStartParts();
  NIFFIOchunkPart(0,0,1,2,-1,-1,-1);
  NIFFIOchunkPart(0,2,3,1,-1,-1,-1);
NIFFIOEndParts();

The next item is the Parts list. The "start" function automatically finalizes the preceeding NiffInfo chunk. Each Part chunk can be written in the same way as the NiffInfo chunk.

You must explicitly end the Parts list so that any following chunks are not considered part of the list.

String Table


NIFFIOchunkStringTable();
  NIFFIOStoreStbl(MyStbl, NUM_STRINGS);

Like the Chunk Length Table, the String Table is another special, raw chunk that has a unique structure. In this case you start the String Table like any other chunk but write its contents using NIFFIOStoreStbl().

You could call NIFFIOStoreStbl() several times to add more sets of strings.

Cleaning Up


NIFFIOEndSetupSection();
NIFFIOEndNiff();

NIFFIOStorageDelete(pstore);
fclose(outfp);

All that is left is to mark the end of the lists that are left open and then clean up. The debug flavor of the NIFF SDK is very picky about ending every list with its own end function.

The memory allocated to *pstore is freed by NIFFIOStorageDelete().

Parsing NIFF Files

A NIFFIOParser is the highest level facility in the NIFF SDK for reading a NIFF file. A NIFFIOParser scans an entire NIFF file and makes callbacks to functions registered by a user for certain NIFF elements. A user needs to supply callbacks only for the elements in which they are interested.

These are the NIFF elements that are recognized by the parser and the types of callbacks associated with them:

If a callback for a specific element is unknown to the parser, then the parser will attempt to call an existing non-specific version.

Using a NIFFIOParser

Using a NIFFIOParser involves these steps:

  1. Write and compile the callbacks.
  2. Create a new parser at runtime.
  3. Register the callbacks with the parser at runtime.
  4. Start the parser on a specific file; it will parse the entire file.
  5. Delete the parser and perform other cleanup.

Writing Callbacks

Callback Signatures

As you have seen, there are many different types of callbacks and circumstances in which they can be called! But callbacks really take only four forms: raw and cooked flavors for both chunks and tags.

All callbacks return a RIFFIOSuccess to indicate if they encounter any errors.

Raw callbacks

A callback for a raw chunk (no property structure) looks like this:

RIFFIOSuccess
cbChunk(NIFFIOChunkContext *pctxChunk);

Each callback gets a pointer to a structure that describes the environment in which it was called, also known as its context. A chunk's context includes the file it came from, its RIFFIOChunk structure, and other information. See section Chunk contexts.

Similarly a callback for a raw tag takes a single NIFFIOtagContext.

RIFFIOSuccess
cbTag(NIFFIOTagContext *pctxTag)

A tag's context includes a reference to its parent chunk among other things.

Cooked callbacks

A callback for a cooked chunk always accepts a pointer to that chunk's property structure. This means that cooked chunk callbacks can only be used for a single type of chunk.

RIFFIOSuccess
cbInfoStart(NIFFIOChunkContext *pctxChunk, niffNiffInfo *ni)

Likewise, a cooked tag callback always includes a pointer to its property structure.

RIFFIOSuccess
cbHeight(NIFFIOTagContext *pctxTag, niffHeight *p)

Callback pairs

Atomic elements, such as some chunks and all tags, use a single callback.

Callbacks for composite elements come in pairs; a start callback for when the parser encounters the element, and an end callback for after the parser has processed all of the element's components. A user can register both, or just one, of the begin and end callbacks.

RIFFIOSuccess
cbNoteheadStart(NIFFIOChunkContext *pctxChunk, niffNotehead *p)

An end callback has the same signature as the start callback.

RIFFIOSuccess
cbNoteheadEnd(NIFFIOChunkContext *pctxChunk, niffNotehead *p)

Creating a NIFFIOParser.

The function NIFFIOParserNew() will return a pointer to a newly created parser. This parser must eventually be freed by calling NIFFIOParserDelete().

Once a new parser is created, you can use it to parse a file, but very little will happen if you haven't registered any callbacks. However, even a parser with no callbacks can still be useful to debug a bad NIFF file if its tracing feature is enabled.

Registration Functions

A user will write a callback and then register it with a NIFFIOParser at runtime. Registering a callback with a NIFFIOParser makes the callback known to the parser and determines the circumstances under which the parser will make the callback. For example, you can register an Invisible tag callback to be called only when the tag appears within a Notehead chunk.

Every NIFF element type has its own, dedicated, registration function. Each registration function is aware of, and enforces the following aspects of NIFF elements:

Is the element composite (form, list, taggable chunk)?

Registration functions for composite elements take a start and an end callback. Either of these may be null, but both are required in the argument list.

NIFFIORegisterChunkNotehead(pparserNew, cbNoteheadStart, cbNoteheadEnd);
NIFFIORegisterChunkStringTable(pparserNew, cbStringTable);

Does the element have a property structure (cooked or raw)?

The callbacks that are registered must take a property structure argument for cooked chunks and tags. The NIFF form and lists are considered to be "raw."

Is the element a tag?

Tag registration functions take a chunk id argument that must match the tag's parent chunk for the callback to be called. There is a wildcard four-character code, NIFFIO_FOURCC_WILDCARD, that indictates the callback should be made for the tag found in any chunk.

NIFFIORegisterTagInvisible(pparserNew, niffckidNotehead, cbInvisible);

NIFFIORegisterTagLogicalPlacement(pparserNew, 
                                  NIFFIO_FOURCC_WILDCARD, 
                                  cbLogicalPlacement);

Registering Default Callbacks

The parser can be configured to make callbacks for non-specific NIFF elements such as any tag, regardless of its id. If the parser cannot find a callback that matches a NIFF element exactly, then it will use one of these registered callbacks.

These registration functions are

RIFFIOSuccess
NIFFIORegisterDefaultList(NIFFIOParser *pparser,
                          NIFFIOChunkCallback cbStart,
                          NIFFIOChunkCallback cbEnd);
RIFFIOSuccess
NIFFIORegisterDefaultTaggedChunk(NIFFIOParser *pparser,
                                 NIFFIOChunkCallback cbStart,
                                 NIFFIOChunkCallback cbEnd);
RIFFIOSuccess
NIFFIORegisterDefaultAtomicChunk(NIFFIOParser *pparser,
                                 NIFFIOChunkCallback cb);
RIFFIOSuccess
NIFFIORegisterDefaultTag(NIFFIOParser *pparser,
                         NIFFIOTagCallback cbTag);

Registering User Elements

Registration functions also exist for user-defined chunks and tags.

THESE ARE NOT IMPLEMENTED YET

RIFFIOSuccess
NIFFIORegisterUserChunk(NIFFIOParser *pparser,
                        DWORD userid, 
                        NIFFIOChunkCallback cb);
RIFFIOSuccess
NIFFIORegisterUserTag(NIFFIOParser *pparser,
                      DWORD userid, 
                      NIFFIOTagCallback cb);

Running the Parser

Once you have registered your callbacks you can run the parser, pparser, on a NIFFIOFile, pnf, with the following command.

NIFFIOParseFile(pparser, pnf,  0, 0 );

Those zeroes are optional user context pointers that can communicate information into and out of the NIFF form callbacks. See section User contexts

The parser will parse the entire file, calling only those callbacks which you have registered.

Callback Precedence

The parser tries to find a callback that best matches each NIFF element that it encounters. Once the parser finds a matching callback, no other callbacks are made for that element.

Form
A form will only match the callbacks registered with NIFFIORegisterFormNIFF().
List
A list will first match callbacks registered by each list's named registration function and then it will match callbacks registered by NIFFIORegisterDefaultList().
Chunk
A chunk will first match callbacks registered by each chunk's named registration function. Then it will match callbacks registered by NIFFIORegisterDefaultTaggedChunk() or NIFFIORegisterDefaultAtomicChunk(), whichever is appropriate.
Tag
Named tag callbacks are registered according to the type of chunk that contains the tag. The parser will first try an exact match for a tag and chunk combination. Then the parser will try a match for a tag and wildcard chunk combination. Then the parser will match a tag callback registered with NIFFIORegisterDefaultTag().

Parser Errors

Umm...its not really well specified how the parser behaves when it encounters an error or a callback reports an error. Sorry.

Even if the parser encounters an error, it will continue to parse as much of the file as it can recognize. For example, if a subchunk callback returns an error, the parser will continue processing other subchunks. If the parser encounters at least one error it will return an error status from NIFFIOParseFile().

If a start callback returns an error, then its corresponding end callback will not be called.

I think there needs to be a way to abort the parser. This could be done from a callback, but I would need to add a reference to the parser in the callback's context.

Parser Memory Allocation

On creation, the parser allocates memory for its callback tables and two buffers for chunk and tag property structures. Registering callbacks also allocates memory. While it parses a file, the parser allocates no memory from the heap.

If callback functions allocate memory, then it is up to their authors to track it and ensure this memory gets freed. One way to track memory in a callback could be to associate it with a user context that is traceable from the top-level down. That is, all memory allocated by callbacks could be found through a structure rooted at the top-level user context.

Another way could be to use special functions in the callbacks that keep track of the memory that they allocate and deallocate. Any deallocated memory could be cleaned up once the parse is complete.

Parser Tracing

If you want to watch the parser in action, you can enable its tracing output with NIFFIOParserSetTracing().

I might provide hooks to replace the default tracing function with your own. I don't know how useful this would be.

Callback Contexts

Each callback gets a "context" for its element and possibly a pointer to the element's property structure. A context describes the environment in which a NIFF element was found and allows callbacks to communicate with each other.

Chunk contexts

A chunk context has the following members:

typedef struct
{
  unsigned          nLevel;        /* chunk depth */
  NIFFIOFile        *pnf;          /* NIFF file that contains the chunk   */
  RIFFIOChunk       *pchunk;       /* Chunk information from file         */
  NIFFIOUserContext ctxParent;     /* Parent chunk user context           */
  NIFFIOUserContext ctxMe;         /* Child user context, to be filled in
                                    * by chunk start callback
                                    */
} NIFFIOChunkContext;

nLevel is the nesting depth of the current chunk or tag. It is the number of chunks above the current object. For example, the the NIFF form chunk has an nLevel equal to zero.

A callback may need access to the NIFFIOFile if it must read the contents of a raw chunk such as the String Table. To do so, it will also need to know the size of the chunk from the RIFFIOChunk pointer provided.

User contexts are explained later. ctxParent is a copy of this chunk's parent's user context. Even if this callback changes the value of ctxParent, the parent will never know. On the other hand, this callback may be expected to change ctxMe, which is the user context that this callback may return.

Tag contexts

Here is a tag context.

typedef struct
{
  unsigned          nLevel;        /* tag depth */
  NIFFIOFile        *pnf;          /* NIFF file that contains the chunk  */
  NIFFIOTag         *ptag;         /* Tag information from file          */
  RIFFIOChunk       *pchunkParent; /* Parent chunk information from file */
  NIFFIOUserContext ctxParent;     /* Parent chunk user context          */
} NIFFIOTagContext;

Most of the members of a tag's context have similar meanings as those in a chunk's context. A tag callback is provided the RIFFIOChunk of its parent so that it may detect the type of its parent chunk. A tag callback can only read its parent's user context. It makes no sense for a tag to have its own user context, because this value would not be passed to any other callback.

User contexts

While parsing a NIFF file, users may keep track of their own parsing information by passing around pointers to custom, user-defined data structures. For example, a set of callbacks might build a parse tree as a result of parsing a NIFF file.

Each chunk context structure provides a pointer called ctxMe that a chunk (or list's) start callback may set. In turn, the NIFFIO parser will pass chunk and tag callbacks the "context" of their enclosing (parent) chunk in ctxParent.

A NIFFIOUserContext is simply a void pointer that callbacks must cast to their desired type.

typedef void *NIFFIOUserContext;

A NIFFIOUserContext is solely for the user's use. NIFFIO will never dereference any NIFFIOUserContext. User contexts may be safely ignored by any user who does not wish to use them. It is safe to assign a null pointer to a NIFFIOUserContext.

Parser Example

Here is a simple example program that parses an NIFF file performing various actions.

#include <stdio.h>
#include "niff.h"
#include "niffio.h"

unsigned long nInvisibleNoteheads = 0; /* counter */

RIFFIOSuccess
cbListStart(NIFFIOChunkContext *pctxChunk)
{
    char strType[RIFFIO_FOURCC_LIM];

    RIFFIOFOURCCToString(pctxChunk->pchunk->fccType, strType);
    printf("LIST START: '%s'\n", strType);
    return RIFFIO_OK;
}

RIFFIOSuccess
cbListEnd(NIFFIOChunkContext *pctxChunk)
{
    char strType[RIFFIO_FOURCC_LIM];

    RIFFIOFOURCCToString(pctxChunk->pchunk->fccType, strType);
    printf("LIST END: '%s'\n", strType);
    return RIFFIO_OK;
}

RIFFIOSuccess
cbNoteheadStart(NIFFIOChunkContext *pctxChunk, niffNotehead *p)
{
    printf("NOTEHEAD: staff step == %d\n", p->staffStep);
    return RIFFIO_OK;
}

RIFFIOSuccess
cbInvisible(NIFFIOTagContext *pctxTag)
{
    nInvisibleNoteheads++;
    return RIFFIO_OK;
}

int
main(int argc, char **argv)
{

    FILE         *pfile;  
    NIFFIOFile   *pnf;
    NIFFIOParser *pparser;

    pfile   = fopen(argv[1], "rb");
    pnf     = NIFFIOFileNewSTDC(pfile);

    pparser = NIFFIOParserNew();

    NIFFIORegisterDefaultList  (pparser, cbListStart, cbListEnd);
    NIFFIORegisterChunkNotehead(pparser, cbNoteheadStart, 0);
    NIFFIORegisterTagInvisible (pparser, niffckidNotehead, cbInvisible);

    NIFFIOParserSetTracing(pparser, 0);
    NIFFIOParseFile(pparser, pnf,  0, 0 );

    printf("Invisible Noteheads == %ld\n", nInvisibleNoteheads);

    NIFFIOParserDelete(pparser);
    NIFFIOFileDelete(pnf);
    fclose(pfile);

}

Coming soon...

Glossary

atomic chunk
A NIFF chunk that cannot contain any tags, as opposed to a taggable chunk.
chunk
The fundamental component type of a RIFF file. All of the components in a RIFF file are chunks although they may be a form, list, or primitive chunk.
cooked chunk
A NIFF chunk that has a property structure; as opposed to a raw chunk.
cooked tag
A NIFF tag that has a property structure; as opposed to a raw tag.
form
The first and top-most chunk in a RIFF file. A RIFF file is essentially single form whose list type defines the type of RIFF file (e.g. NIFF or WAVE). A form is a kind of list chunk.
list
A composite chunk in a RIFF file that contains subchunks. Subchunks may be other list chunks or primitive chunks.
primitive chunk
A chunk that is not a list chunk; it does not contain other chunks.
property structure
A fixed-length structure at the start of a NIFF chunk or tag's data that holds values associated with with the chunk or tag. For example, A Notehead chunk has a duration property. Chunk property structures may be followed by zero or more tags.
raw chunk
A NIFF chunk that has no property structure; as opposed to a cooked chunk.
raw tag
A NIFF tag that has no property structure; as opposed to a cooked tag.
subchunk
A chunk directly contained in a list. A subchunk may be a nested list or a primitive chunk.
tag
A component of some NIFF chunks. Tags are like mini-chunks that modify the meaning or augment their containing chunks.
taggable chunk
A chunk that is allowed to contain tags. Usually it will have a property structure followed by zero or more tags. Compare with an atomic chunk.


Footnotes

(1)

You got it, it's a composite pattern.

(2)

Properties have no special alignment requirements. If they are odd sized, they may force the even-sized tags to start on an odd boundary. This seems odd.

(3)

Although the FOURCC type can be stored in a 32-bit word, it is not an integral type

(4)

I'm not being snooty; RIFFIO needs its own version independent from `niff.h'.

(5)

For awhile I was thinking that four-character codes could violate the preceeding rules. Therefore I made RIFFIO_FOURCC_LIM bigger than necessary so I could format backslash-escaped strings. This won't be necessary.

(6)

This string should really be a constant from `niff.h'.

(7)

Actually, this program only counts noteheads made invisible with an direct Invisible tag, not using a Tag Activate mechanism.


This document was generated on 17 March 1997 using the texi2html translator version 1.51.