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
Setup
Chunk-length-table
NIFF-Info, version=6b, writing program type=ENGRAVING_PROGRAM, standard units=INCHES, absolute units=20, midi ticks per quarter=16384
Parts list
Data
Page
Page-header, height=14544, width=10944
System
System-header, absolute placement=(400,1800), height=400, width=10400
Staff
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