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:.
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
The NIFF SDK won't
There are three major components to the NIFF SDK.
niffdump utility to decode and print the structure of a NIFF file.
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.
The NIFF SDK has been compiled and partially tested on the following platforms:
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.
Unix users can use the configuration script provided and then run make.
cd niffsdk-1.03
./configure --prefix=/home/tim
If you don't know what options configure takes then run
./configure --help.
make all
make check
make install
The following make targets also exist.
clean
mostlyclean
clean but leave libraries and executables.
distclean
configure before you can make again.
maintainer-clean
maintainer-clean removes files that you can't
regenerate without the tools used to develop the NIFF SDK itself.
There are some things to watch out for with the current build system.
make supporting an "include" mechanism.
make will gag on these.
AR macro in
`top.in' may be wrong for your system.
configure. Put any permanent changes in their corresponding
`.in' files.
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:
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.
Each component of the NIFF SDK (riffio, niffio, and niff) has its own directory under the top of the distribution. Each directory may contain:
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
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.
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 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.
A chunk consists of:
Chunk
+---------------+
| id |
| (4 bytes) |
+---------------+
| size: n |
| (4 bytes) |
+---------------+
| data |
| ( n bytes) |
+---------------+
| pad |
| ( n mod 2) |
+---------------+
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:
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) |
+---------------+ +---------------+
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.
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
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.
Starting with the RIFF file positioned before a chunk, read a chunk's id, size, and list type (if it exists).
Advance the file past a chunk, including any pad byte.
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.
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.
The RIFF standard only specifies file structure down to the primitive chunk level. NIFF extends RIFF in the following ways:
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.
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.
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.
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.
The NIFF SDK classifies chunks according to following rules:
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.
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) |
+---------------+
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.
Tags have operations similar to those defined for chunks
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 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).
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.
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
n
rf
RIFFIOFile
nf
NIFFIOFile
offset
RIFFIOOffset
fcc
chunk
RIFFIOChunk
size
RIFFIOSize
tag
NIFFIOTag
chunkctx
NIFFIOChunkContext
tagctx
NIFFIOTagContext
userctx
NIFFIOUserContext
clt
NIFFIOChunkLengthTable
stbl
NIFFIOStbl (String Table)
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. */
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).
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.
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);
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!
There are some rules that apply to four-character codes. You can use
RIFFIOFOURCCIsValid() to check for compliance
(5).
`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};
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.
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.
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;
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,
...
}
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.
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.
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.
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
#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);
}
#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'.
unsigned char Version[8] = "6b";
You need a string to record the NIFF version in the Niff Info chunk. (6)
/*
* 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.
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:
NIFFIOFile object to perform I/O on
Standard C library files. You have access to this object but don't
need it in this example.
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.
NIFFIOStartNiff();
This writes the header for the NIFF form.
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.
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.
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.
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.
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.
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().
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.
NIFFIOParser
Using a NIFFIOParser involves these steps:
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.
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.
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)
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)
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.
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:
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);
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."
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);
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);
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);
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.
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.
NIFFIORegisterFormNIFF().
NIFFIORegisterDefaultList().
NIFFIORegisterDefaultTaggedChunk() or
NIFFIORegisterDefaultAtomicChunk(), whichever is appropriate.
NIFFIORegisterDefaultTag().
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.
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.
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.
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.
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.
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.
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.
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);
}
You got it, it's a composite pattern.
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.
Although the FOURCC type
can be stored in a 32-bit word, it is not an integral type
I'm not being snooty; RIFFIO needs its own version independent from `niff.h'.
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.
This string should really be a constant from `niff.h'.
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.