Music software enables expressing musical ideas that must be both human-readable and computer-readable. Modern sheet music notation is extremely expressive, with the ability to communicate rhythm, melody, harmony, and a variety of performance instructions in a compact space. Unfortunately, as a graphical, human-readable notation, sheet music doesn't translate to computers well. A separate notation system, a Domain Specific Language (DSL), is necessary for computers to be able to process music. Also, we need tools that understand this DSL and allow us to manipulate the music.
This article examines two open source Java libraries that use two different notations which express musical information in computer-friendly ASCII formats. Both libraries can play tunes as MIDI sequences through the computer speakers but differ in their other capabilities.
JFugue is an LGPL-licensed open source library for "programming music without the complexities of MIDI." It has its own notation to represent music using only ASCII characters, provides I/O to MIDI files, and allows manipulating music programmatically.
To demonstrate the capabilities of JFugue, we will use variations on the nursery rhyme "Itsy Bitsy Spider."
The first demonstration of JFugue shows mainly the basic notation for the melody of the song:
package com.ociweb.jnb.jfugue;
import org.jfugue.Player;
/**
* This program plays a simple version of "Itsy Bitsy Spider." Though not
* specified, the song is in 6/8 time and in the key of F major. The song
* plays only the melody, with code duplication.
*/
public class ItsyBitsySimple {
public static void main(String[] args) {
Player player = new Player();
player.play(
// "Itsy, bitsy spider, climbed up the water spout."
"F5q F5i F5q G5i A5q. A5q A5i G5q F5i G5q A5i F5q. Rq. " +
// "Down came the rain and washed the spider out."
"A5q. A5q Bb5i C6q. C6q. Bb5q A5i Bb5q C6i A5q. Rq. " +
// "Out came the sun and dried up all the rain, so the"
"F5q. F5q G5i A5q. A5q. G5q F5i G5q A5i F5q. C5q C5i " +
// "itsy, bitsy spider went up the spout again."
"F5q F5i F5q G5i A5q. A5q A5i G5q F5i G5q A5i F5q. Rq."
);
}
}
The primary lesson here is in the notation used to define the song. This simple example includes note names, octaves, rests, accidentals, and durations.
Notes are specified according to the simple A-G scale with the octave number specified next. For example, middle C is C5, the C an octave higher is C6, and the note directly below that one is B5. This is a common numbering system used in some instruments like handbells.
If no octave is given, the default octave for notes is octave 5, at and above middle C.
JFugue also allows specifying the note as a number from 0 to 127, or even defining pitches in between the notes (for some non-Western musical traditions and some types of modern music), but that advanced detail is outside the scope of this document.
Rests are defined using an R instead of a note-octave combination.
To specify sharps or flats, add a #
or b
between the note and the octave. Our example version of "Itsy Bitsy
Spider" is in the key of F major, but JFugue defaults to C major (like
standard musical notation), so we need to specify that the Bs in the
second line are really B-flats. Later, we will see that we can specify
the key signature for the song, so we won't need to specify sharps and
flats that are within the key signature. Additionally, naturals can be
denoted using an n to cancel accidentals in
keys other than C major (key signatures are covered later).
Double-sharps and
double-flats are not supported.
The simplest means of expressing note duration is the one used here, based on the American system:
| Code | Duration |
| w | whole note |
| h | half note |
| q | quarter note |
| i | eighth note |
| s | sixteenth note |
| t | thirty-second note |
| x | sixty-fourth note |
| n | one-twenty-eighth note |
In the example song above, we use quarter notes (q),
eighth
notes (i), and dotted-quarter notes (q.).
As in standard
musical notation, adding a dot after a note extends its length by 50%.
Other durations, such as triplets and other tuplets can be defined by an alternate numerical notation based on the whole note. For example, to define a C5 that is a quarter note, use C5/0.25. To define a C5 note that is a third the length of a quarter note, use C5/0.08333333. As we will see later, this notation is also used when importing a MIDI file into JFugue notation.
In addition to showing the notation, this example shows a bit
of JFugue's API. Specifically, the call to player.play()
converts the defined song to a MIDI sequence and plays it through the
computer's speakers.
Now that the basics of the musical notation have been defined,
we can start to improve the song. First, from a DRY
perspective, we should get rid of the duplication between the first and
the last line, which are identical musically. Fortunately,
JFugue
provides a Pattern class that uses the Composite
design pattern and allows us to reuse musical segments. In the following version, we create a
Pattern
instance for each unique line and then add each of them in order
to another Pattern instance that represents
the
entire song.
package com.ociweb.jnb.jfugue;
import org.jfugue.Pattern;
import org.jfugue.Player;
/**
* This program plays the version of "Itsy Bitsy Spider" that is the basis of
* later examples in the article. Though not specified, the song is in 6/8
* time and in the key of F major. The song plays only the melody, with code
* duplication eliminated and with measures indicated.
*/
public class ItsyBitsy {
public static void main(String[] args) {
// "Itsy, bitsy spider, climbed up the water spout."
// and "itsy, bitsy spider went up the spout again."
Pattern pattern1 = new Pattern("F5q F5i F5q G5i | A5q. A5q A5i | G5q F5i G5q A5i | F5q. Rq. | ");
// "Down came the rain and washed the spider out."
Pattern pattern2 = new Pattern("A5q. A5q Bb5i | C6q. C6q. | Bb5q A5i Bb5q C6i | A5q. Rq. | ");
// "Out came the sun and dried up all the rain, so the"
Pattern pattern3 = new Pattern("F5q. F5q G5i | A5q. A5q. | G5q F5i G5q A5i | F5q. C5q C5i | ");
// Put the whole song together
Pattern song = new Pattern();
song.add(pattern1);
song.add(pattern2);
song.add(pattern3);
song.add(pattern1);
// Play the song
Player player = new Player();
player.play(song);
}
}
Another change here is that we have added markers for the boundaries between measures (the "|" characters). Interestingly, this has no effect on the program's interpretation of the song - it's for user convenience and clarity only. JFugue does not have the concept of a time signature, so measures may contain as many or as few beats as you like - they are there only for reading convenience.
Next, let use the Voice feature to create a round.
package com.ociweb.jnb.jfugue;
import org.jfugue.Pattern;
import org.jfugue.Player;
/**
* This program plays "Itsy Bitsy Spider" as a round. Though not specified,
* the song is in 6/8 time and in the key of F major. The song plays the
* melody in three repetitions.
*/
public class ItsyBitsyRound {
public static void main(String[] args) {
// "Itsy, bitsy spider, climbed up the water spout."
// and "itsy, bitsy spider went up the spout again."
Pattern pattern1 = new Pattern("F5q F5i F5q G5i | A5q. A5q A5i | G5q F5i G5q A5i | F5q. Rq. | ");
// "Down came the rain and washed the spider out."
Pattern pattern2 = new Pattern("A5q. A5q Bb5i | C6q. C6q. | Bb5q A5i Bb5q C6i | A5q. Rq. | ");
// "Out came the sun and dried up all the rain, so the"
Pattern pattern3 = new Pattern("F5q. F5q G5i | A5q. A5q. | G5q F5i G5q A5i | F5q. C5q C5i | ");
// Put the whole song together
Pattern song = new Pattern();
song.add(pattern1);
song.add(pattern2);
song.add(pattern3);
song.add(pattern1);
Pattern lineRest = new Pattern("Rh. | Rh. | Rh. | Rh. | ");
// Create the first voice
Pattern round1 = new Pattern("V0");
round1.add(song);
// Create the second voice
Pattern round2 = new Pattern("V1");
round2.add(lineRest);
round2.add(song);
// Create the third voice
Pattern round3 = new Pattern("V2");
round3.add(lineRest, 2);
round3.add(song);
// Put the voices together
Pattern roundSong = new Pattern();
roundSong.add(round1);
roundSong.add(round2);
roundSong.add(round3);
// Play the song
Player player = new Player();
player.play(roundSong);
}
}
In this example, we create a round
by combining three similar
Voices (similar to tracks or channels in other musical contexts).
In this case, there is a separate Pattern
instance for each Voice. Each pattern receives the same
sequence from the previous example, but some are prefixed with one or
more full
lines of rests (lineRest) so that they have
staggered starts.
The
separate voices are defined by adding V0, V1,
and V2 to the pattern. Everything
after the Voice
declaration is associated with that Voice, until another Voice is
specified. In this example, all of the information for each
Voice is grouped together, but the definitions could be interspersed as
long as the Voice is respecified each time, as shown in the following
version that uses the
Pattern instances introduced earlier.
This example is musically identical to the preceding example.
Pattern lineRest = new Pattern("Rh. | Rh. | Rh. | Rh. | ");
// Put the whole song together
Pattern song = new Pattern();
song.add("V0 " + pattern1);
song.add("V1 " + lineRest);
song.add("V2 " + lineRest);
song.add("V0 " + pattern2);
song.add("V1 " + pattern1);
song.add("V2 " + lineRest);
song.add("V0 " + pattern3);
song.add("V1 " + pattern2);
song.add("V2 " + pattern1);
song.add("V0 " + pattern1);
song.add("V1 " + pattern3);
song.add("V2 " + pattern2);
song.add("V1 " + pattern1);
song.add("V2 " + pattern3);
song.add("V2 " + pattern1);
// Play the song
Player player = new Player();
player.play(song);
Another use of Voices is to add harmonies or chord
accompaniments.
In the following example, V0 is
used as the melody introduced
earlier, and V1 is used to
provide bass chords. Note that only the short name of the chord need
be
specified (Fmaj and Bbmaj
in this example, but there are many other
options for more complicated chords), and that the default octave is
number
3 (two octaves below middle
C). Chords can also be defined by specifying each of the
notes in the chord, connected with +.
In this example, we use this approach for one of the chords
to use the first inversion of the chord, which does not have a short
name.
Another addition to this version is instrumentation.
The default instrument for all Voices is the Piano, so the
previous examples sounded like they were played on a piano.
In this example, the
melody is a trumpet (specified
as I[Trumpet] or
alternately as
I56),
and the chords are a church organ
(specified as I[CHURCH_ORGAN]
or alternately as
I19).
The instruments are defined here in the header, but they also
can be changed at any point during the song.
The ID numbers and options here are derived from the MIDI specification; the full list of 128 instruments is available in JFugue's documentation.
package com.ociweb.jnb.jfugue;
import org.jfugue.Pattern;
import org.jfugue.Player;
/**
* This program plays "Itsy Bitsy Spider" in two voices. The first voice is a
* melody is played as a trumpet, and the second (new) voice is a church organ
* playing chords. Though not specified, the song is in 6/8 time and in the
* key of F major.
*/
public class ItsyBitsyChords {
public static void main(String[] args) {
Pattern voice1 = new Pattern("V0 I[Trumpet] ");
// "Itsy, bitsy spider, climbed up the water spout."
// and "itsy, bitsy spider went up the spout again."
Pattern pattern1 = new Pattern("V0 F5q F5i F5q G5i | A5q. A5q A5i | G5q F5i G5q A5i | F5q. Rq. | ");
// "Down came the rain and washed the spider out."
Pattern pattern2 = new Pattern("V0 A5q. A5q Bb5i | C6q. C6q. | Bb5q A5i Bb5q C6i | A5q. Rq. | ");
// "Out came the sun and dried up all the rain, so the"
Pattern pattern3 = new Pattern("V0 F5q. F5q G5i | A5q. A5q. | G5q F5i G5q A5i | F5q. C5q C5i | ");
Pattern voice2 = new Pattern("V1 I[CHURCH_ORGAN] ");
//1st, 3rd, and 4th lines (third chord specified as notes)
Pattern chord1 = new Pattern("V1 Fmajh. | Fmajh. | E3h.+G3h.+C4h. | Fmajh. | ");
//2nd line
Pattern chord2 = new Pattern("V1 Fmajh. | Fmajh. | Bbmajh. | Fmajh. | ");
// Put the whole song together
Pattern song = new Pattern();
//melody
song.add(voice1);
song.add(pattern1);
song.add(pattern2);
song.add(pattern3);
song.add(pattern1);
//chords
song.add(voice2);
song.add(chord1);
song.add(chord2);
song.add(chord1, 2);
// Play the song
Player player = new Player();
player.play(song);
}
}
The last JFugue version of "Itsy Bitsy Spider" in adds two elements to the header: the key signature and the tempo. These elements can be defined anywhere in the course of a song to change keys or tempo (to express a ritardano, for example), but our example sets them only intitially. If we were to change the tempo during the course of the song, we would have to change it separately for each voice.
The key signature is specified as F major (KFmaj),
which means that the flat notations can removed from the B notes, just
as they could be
in standard musical notation. As mentioned before, the
default key signature is C
major.
The tempo is specified as 100 "Pulses Per Quarter," which is
how many "pulses" to give a quarter note. The JFugue
documentation is actually
fairly confusing on tempo, because the default value is 120, which the
documentation simultaneously defines as 120 "pulses" per
quarter note and
120
beats per minute. The two scales act in different
directions, in that more pulses per quarter would be slower, but more
beats per minute would be faster. In fact, the first
definition is the one in use here, and T100
is faster than the default tempo.
package com.ociweb.jnb.jfugue;
import org.jfugue.Pattern;
import org.jfugue.Player;
import java.io.File;
/**
* This program plays "Itsy Bitsy Spider" in two voices. The first voice is a
* melody is played as a trumpet, and the second (new) voice is a church organ
* playing chords. The key of F major and a tempo are specified. Though not
* specified, the song is in 6/8 time.
*/
public class ItsyBitsyHeader {
public static void main(String[] args) throws IOException {
Pattern header = new Pattern("KFmaj T100 V0 I[Trumpet] V1 I[CHURCH_ORGAN] ");
// "Itsy, bitsy spider, climbed up the water spout."
// and "itsy, bitsy spider went up the spout again."
Pattern pattern1 = new Pattern("V0 F5q F5i F5q G5i | A5q. A5q A5i | G5q F5i G5q A5i | F5q. Rq. | ");
// "Down came the rain and washed the spider out."
Pattern pattern2 = new Pattern("V0 A5q. A5q B5i | C6q. C6q. | B5q A5i B5q C6i | A5q. Rq. | ");
// "Out came the sun and dried up all the rain, so the"
Pattern pattern3 = new Pattern("V0 F5q. F5q G5i | A5q. A5q. | G5q F5i G5q A5i | F5q. C5q C5i | ");
//1st, 3rd, and 4th lines (third chord specified as notes)
Pattern chord1 = new Pattern("V1 Fmajh. | Fmajh. | E3h.+G3h.+C4h. | Fmajh. | ");
//2nd line
Pattern chord2 = new Pattern("V1 Fmajh. | Fmajh. | Bmajh. | Fmajh. | ");
// Put the whole song together
Pattern song = new Pattern();
song.add(header);
//melody
song.add(pattern1);
song.add(pattern2);
song.add(pattern3);
song.add(pattern1);
//chords
song.add(chord1);
song.add(chord2);
song.add(chord1, 2);
// Play the song
Player player = new Player();
player.play(song);
// save as a midi file for use in the next example
player.saveMidi(song, new File("spider.midi"));
}
}
In addition to playing a song to the speakers as a MIDI
sequence, as the
previous examples did, we can also load a MIDI file into the JFugue
library and/or export a JFugue song to a MIDI file.
Additionally,
JFugue
provides an API for musical transformations to be
applied to a Pattern, with a few
transformations implemented in the library.
In the
following example, a MIDI file created using the final version of the
song is
loaded in, and the parsed JFugue representation is printed to the
command line. Then, the entire song is transposed up a whole
note using a IntervalPatternTransformer and
reprinted. Next,
the entire song is slowed down 20% using a DurationPatternTransformer
and reprinted again. Finally, the newly modified song is
exported back to a new MIDI file.
package com.ociweb.jnb.jfugue;
import org.jfugue.Pattern;
import org.jfugue.Player;
import org.jfugue.extras.DurationPatternTransformer;
import org.jfugue.extras.IntervalPatternTransformer;
import javax.sound.midi.InvalidMidiDataException;
import java.io.File;
import java.io.IOException;
/**
* This program demonstrates MIDI I/O and musical transformations.
*/
public class IOTransformations {
public static void main(String[] args) throws InvalidMidiDataException, IOException {
Player player = new Player();
// load a midi file
Pattern pattern = player.loadMidi(new File("spider.midi"));
// print the song to the console with JFugue notation
System.out.println("Original: " + pattern.getMusicString());
// transpose up a whole note
IntervalPatternTransformer transposer = new IntervalPatternTransformer();
transposer.putParameter(IntervalPatternTransformer.INTERVAL, 2);
pattern = transposer.transform(pattern);
System.out.println("Transposed: " + pattern.getMusicString());
// slow down 20%
DurationPatternTransformer slower = new DurationPatternTransformer(1.2);
pattern = slower.transform(pattern);
System.out.println("Slowed: " + pattern.getMusicString());
// save as a midi file
player.saveMidi(pattern, new File("output.midi"));
}
}
Here is the output (trimmed for line length):
Original: V0 @0 V0 I[Trumpet] @0 V0 F5/0.20833333333333334 @100 V0 F5/0.10416666666666667...
Transposed: V0 @0 V0 I[Trumpet] @0 V0 G5/0.20833333333333334 @100 V0 G5/0.10416666666666667...
Slowed: V0 @0 V0 I[Trumpet] @0 V0 G5q @100 V0 G5i...
Note that the original version uses the numeric notation for note duration because a non-default tempo was used. In the last version, when the tempo is changed exactly to the default notation, the simplified notation can be used.
Also note that the transposition transformation moved the notes up from F5 to G5.
An interesting project is underway to create a GUI frontend for JFugue. Geertjan Wielenga has started JFugue Music NotePad, a Netbeans-based project that allows users to create simple tunes by clicking on a staff to place notes and rests.
Currently, JFugue Music NotePad supports only a subset of the overall facilities of JFugue (only a single voice, no chords, only the basic note durations, no input of MIDI or JFugue files), but it plays the tunes created in it, allows printing a view of the music, and exports the tune to a MIDI file. So, it's not a tool that could be used to create a complicated musical piece, but it is an intriguing project that might be useful for teaching musical ideas.
JFugue has a musical notation that is fairly complete, but one disadvantage is that it is not a standard; few if any other applications use the same notation, and libraries of songs that use the notation are hard to find. A competing notation that is more commonly used is abc notation. Abc notation was created in 1991 as a means of sharing folk songs and traditional tunes using only ASCII characters. It has become very popular in certain communities, and collections of tunes using the notation can be found on the Web. Additionally, the notation is a rigorously defined language, with a BNF definition.
On the downside, abc notation is not as complete musically as JFugue's notation. For example, it does not support multiple voices (at least, not in the formal language — some applications have their own extensions), and it does not support defining instruments. These limitations make sense given the original purpose of the language: to share the melodies of historical tunes.
There is a nascent GPL-licensed project to build a Java library that uses abc notation: abc4j. The API is less mature than JFugue's, and there is less functionality (no MIDI input or output, no ability to transform the tunes, etc.), but it provides an alternative for those who wish to use the large collections of music already expressed in abc notation.
Unlike JFugue, abc4j does not have a simple means of defining
the tune with a DSL within a program. The two options are
either to import the song from a file or to use an extremely verbose
API
(i.e. score.addElement(new Note(Note.C))).
Below is an example of abc notation for "Itsy Bitsy Spider"
that is loaded into a program and played.
The music file:
X:1
T:Simple Itsy Bitsy
M:6/8
L:1/8
O:nursery rhyme
Q:240
K:F
[| F2F F2G | A3 A2A | G2F G2A | F3 z3 |\
A3 A2B | c3 c3 | B2A B2c | A3 z3 |\
F3 F2G | A3 A3 | G2F G2A | F3 C2C |\
F2F F2G | A3 A2A | G2F G2A | F3 z3 |]
The header of an abc file allows specifying a lot more metadata than JFugue supports. Here, we define in order:
After the header, the actual tune is displayed. There are several aspects seen here that are different than in JFugue's notation:
z
instead of R.
[|),
the end of the tune (|]), and continuing a
line (\). There is also support for
repeats, though this example does not use it.
Below is a sample programs which loads the above file, examines the metadata, exports the file, plays the tune, and displays a representation of the score.
package com.ociweb.jnb.abc4j;
import abc.midi.TunePlayer;
import abc.notation.Tune;
import abc.parser.TuneBook;
import abc.ui.swing.JScoreComponent;
import javax.swing.JFrame;
import java.io.File;
import java.io.IOException;
/**
* This program demonstrates abc4j's capabilities. It loads a song from a
* file, displays metadata, saves the song to a different file, plays the song,
* and then displays a representation of the score.
*/
public class ItsyBitsyAbc {
public static void main(String[] args) throws IOException {
//loading from the file
TuneBook book = new TuneBook(new File("itsyBitsy.abc"));
// show details about the tunes that are loaded
System.out.println("# of tunes in itsyBitsy.abc : " + book.size());
// retrieve the specific tune by reference number
Tune tune = book.getTune(1);
// display its title
System.out.print("Title of #1 is " + tune.getTitles()[0]);
// and its key
System.out.println(" and is in the key of " + tune.getKey().toLitteralNotation());
// can export to a file (abc notation)
book.saveTo(new File("out.abc"));
// creates a simple midi player to play the melody
TunePlayer player = new TunePlayer();
player.start();
player.play(tune);
// creates a component that draws the melody on a musical staff
JScoreComponent jscore = new JScoreComponent();
jscore.setJustification(true);
jscore.setTune(tune);
JFrame j = new JFrame();
j.add(jscore);
j.pack();
j.setVisible(true);
// writes the score to a JPG file
jscore.writeScoreTo(new File("spiderScore.jpg"));
}
}
The access to metadata here (number of songs in the file and the name and key of the tune) is an advantage over what is available in JFugue. Another minor advantage is the ability to print a score of the tune without needing an external GUI application. However, we cannot transform the tune here, we hear and process only one voice, and there is no direct access to MIDI files.
Neither JFugue notation nor abc notation is as expressive as the standard staff-based graphical score, but both allow computer-based sharing and modification of songs. Each notation supports some musical ideas that the other doesn't support, but JFugue notation is closer to complete. Additionally, the JFugue library is much more powerful than the relatively new abc4j library. However, until an abc notation parser is added to JFugue, abc4j allows access to a much larger collection of music from around the world.
The best way to become familiar with these tools is to try them out yourself. Starting with the examples here, you can easily tweak them and create interesting music using either notation (both of which allow for musical expression and concepts beyond the scope of this article). Another way to start is to download a MIDI file from a Web repository and import it into JFugue's notation; seeing how a symphony by Beethoven or a song by the Beatles is encoded effectively demonstrates the power of the notation.
Lance Finney thanks Michael Easter, Tom Wheeler, and Dean Wette for reviewing this article and providing useful suggestions.