Niffty Programmer's Guide

This is a guide for the programmer who wants to work with the Niffty code. The Java code was developed using Sun One Studio (formerly called Forte for Java). You will especially need this developement environment to work with the form layouts. This guide also assumes you have read the NIFF specification. (This is not a NIFF tutorial.)

Example NIFF file

We will walk through how Niffty loads and displays the NIFF file simple.nif. Below is how the Niffty Java applet displays the file.

If your browser properly displays the applet, you see a single staff of one measure with a treble cleff, 4/4 time signature, a whole note at middle C, and an ending barline. If you click "View Niff Source" in the floating Niffty Controls panel, you see:

NIFF Score
NIFF-Info, version=6b, writing program type=ENGRAVING_PROGRAM, standard units=INCHES, absolute units=20, midi ticks per quarter=16384
Parts list
Page-header, height=14544, width=10944
System-header, absolute placement=(400,1800), height=400, width=10400
Staff-header, absolute placement=(400,1800), anchor override=pghd, height=400, number of staff lines=5, width=10400
Time-slice, type=MEASURE_START, start time=0/1, absolute placement=(0,400)
Time-slice, type=EVENT, start time=0/1
Clef, shape=G_CLEF, staff step=2, octave=0
Time-signature, top=4, bottom=4
Stem, height=0, logical placement=(horizontal:DEFAULT, vertical:ABOVE, proximity:DEFAULT)
Notehead, shape=WHOLE, staff step=-2, duration=1/1, part ID=0
Time-slice, type=EVENT, start time=1/1, absolute placement=(10400,400)
Barline, type=THIN, extends to=BOTTOM_OF_STAFF, number of staves=1
Time-slice, type=MEASURE_START, start time=1/1, absolute placement=(10400,400)

During the following discussion of the code, you may refer to this NIFF listing as we discuss how Niffty loads the NIFF file. You will also want to refer to the javadoc documentation which was generated from the Java source code.

Compiling Niffty

You can get the source from CVS. Note that many web browsers are still using an old Java version, so when you compile Niffty you should compile for the old target. With the Sun SDK, use the following command line option:
-target 1.1

Niffty Start-Up

There are two ways to run Niffty: as a stand-alone application and as a web page applet. As a stand-alone application, you run the main method in NifftyEdit which provides a frame window to view the file provided as the first argument on the command line. This class is called NifftyEdit because a future version of Niffty will allow the user to edit and save the file. You can only do this with a stand-alone application because you can't save to the file system from an applet.

To start up as an applet, the web page applet tag refers to the Niffty class which extends Applet. We will mainly discuss this method of starting Niffty. The browser automatically calls the init() method in Niffty which opens the file given by the applet tag's "file" parameter. Then init() creates a NifftyDrawPanel which provides the area where the music is drawn. The most important line in init() is the following:
  _nifftyPanel.setScore (RiffScore.newInstance (input));
As discussed below, this uses the RiffScore to read the input NIFF information as a RIFF file, then assigns it to the NifftyDrawPanel.

Finally, init() creates a NifftyControls which is the floating window that lets you change the page which the NifftyDrawPanel displays. The music is automatically drawn when Java calls the NifftyDrawPanel paint() method, which is described in Drawing the Page below..

Loading the NIFF File

As mentioned above, init() creates an InputStream for the NIFF file and uses the RiffScore newInstance(InputStream) method to create a Score object from the input. Note that RiffScore.newInstance is a static method and that once the Score is created, it doesn't matter that it came from a RIFF file. In future versions of Niffty, there may be other ways to create a Score.  For example, there could be a class XmlScore whose newInstance would read the score from an XML file.

RiffScore.newInstance creates a RIFF object called riffInput which wraps the InputStream and is used to decode the RIFF file. It then creates the Score as follows:
  return new Score
    (RiffScoreSetup.newInstance (riffInput),
     RiffScoreData.newInstance (riffInput));
RiffScoreSetup.newInstance and RiffScoreData.newInstance are also static methods which return, respectively, a ScoreSetup and ScoreData. These are used to construct the Score. Note that this follows the NIFF listing above where a "NIFF Score" has a "Setup" and "Data" section. The entire file has now been loaded because RiffScoreData has created the ScoreData by using a RiffPage to create a Page all the way down the tree to the basic music symbols.

Class Diagram

This diagram shows the heirarchy of objects after Niffty has loaded the sample NIFF file. You may want to open the diagram in a separate window and refer to it as you read on. This is a UML diagram. It isn't exactly a class heirarchy, but rather a diagram of the objects that are loaded into memory. (You can always get the class heirarchy from javadoc.) Here is a very brief summary of how to read the diagram.

Each box has three parts. The top has the class name, the middle has the class variables, and the bottom has the class methods. Abstract methods and interfaces (which don't provide their own implementation) are shown in italics. The notation _nifftyPanel: NifftyDrawPanel means that the variable _nifftyPanel is of type NifftyDrawPanel. Likewise, getScreenHotspot(): FinalPoint means that the method getScreenHotspot() returns type FinalPoint. Only the most important variables and methods are shown.

There are two main types of connecting pointers. First, a connecting pointer that ends in an arrow head means that a class is derived from the class it points to, as in a normal class heirarchy diagram. (A dashed instead of solid line is used when the class pointed to is an abstract interface.) Second, a connecting pointer that ends in a diamond head means that an object instance of the given class is contained by the object instance which it points to. If a class of type X points to another of type Y with a diamond head, then there would be a class variable of type X in class Y which holds the reference. However, to keep the diagram uncluttered, the box for class Y may not necessarily show this class variable. Also, the number at the start of the connecting line means how many instances of the class are typically held by the class which is pointed to. "0..1" means there can be zero or one instance. "1" means there is only one instance. "1..*" means there can be one or more, etc. For example, a ScoreData can hold one or more Page objects. (Even though a ScoreData could hold zero Page objects when the ScoreData is first created, we don't show this because this is not how a ScoreData is typically used.)

From the diagram, you can see that a Score holds a tree of objects which resembles the NIFF file listing shown above.

Drawing the Page

Using the class diagram as a reference, we will discuss how Niffty draws the music on the page. Notice that the NifftyDrawPanel keeps a _pageIndex so that it knows which page to draw when Java calls its paint() method. The NifftyControls object has methods to respond to the next page and previous page buttons which tell the NifftyDrawPanel to change the page.

A Score object and all the main child objects it holds have an invalidate() method which is called when the values change to tell the object to recompute its hotspot and other drawing parameters. These drawing parameters are cached until the next call to invalidate() so that they don't have to be recomputed every time the object is told to redraw itself. If Niffty only reads in a NIFF file and displays it, then invalidate() is only called at the beginning, but if Niffty is used to edit the score in some way, invalidate() would be called after each change. An object's invalidate() also calls invalidate() for all its children.

A Page object and all the main child objects it holds have a draw(Graphics) method so that it can draw itself on the Graphics object. This is invoked by the NifftyDrawPanel's paint() method. An object's draw() also calls draw() for all its children.

A StaffSystem object and all the main child objects it holds implement the Anchored interface. (Note that objects such as Clef inherit the Anchored interface through MusicSymbol.) This is because these objects are drawn relative to another object's "hot spot" as defined in the NIFF spec. An Anchored object has a getScreenHotspot() method which computes and returns the object's hot spot in screen coordinates.

All the children of a TimeSlice extend MusicSymbol, such as Clef, Stem, Notehead and Barline. Among other reasons, this is to define the isLeftPositionedSymbol() method as discussed below.

Most of the work for drawing an object is in the call to getScreenHotspot(). Currently, Niffty ignores absolute placement tags. Most music symbols compute their screen hotspot based on the parent TimeSlice hot spot, which in turn uses getSymbolCount(), getWidth() and getSymbolPosition() in its parent MeasureStartTimeSlice to compute the X screen position.

When getSymbolCount() is called for the MeasureStartTimeSlice, it creates a SymbolPositioner object and updates it with all MusicSymbol objects from this measure as well as all the other measures in the staff system for the same time period. This is because the X position of a symbol must line up with all symbols in the staff system which occur at the same time. The MusicSymbol's isLeftPositionedSymbol() is used by the SymbolPositioner in lining up symbols with the same time stamp. For more details, see the routines in SymbolPositioner as well as getLeftPositionedSymbolCount() in TimeSlice. Once the SymbolPositioner is set up, then a TimeSlice can use its parent MeasureStartTimeSlice's getSymbolPosition(). See the source code for getScreenHotspot() in TimeSlice.

With the ability for each object to determine its screen position, its draw() method can draw its graphic symbols on the screen.

back to main page