Introduction to the gm package

Create music easily, and show musical scores and audio files in R Markdown documents, R Jupyter Notebooks and RStudio

Renfei Mao Renfei Mao , Bruna Wundervald https://brunaw.com (Maynooth University)https://www.maynoothuniversity.ie/
04-03-2021

The package name “gm” means “grammar of music” or “generate music”.

It provides an elegant and intuitive language, with which you can easily create music in R. It generates and embeds musical scores and audio files in R Markdown documents, R Jupyter Notebooks and RStudio seamlessly.

Let’s go through a quick example to get a feeling of it.

Quick Example


# Loading the package and creating the music object
library(gm)
m <- Music()

We have initialized an empty Music object, to which we can add components using the + operator:


m <- m +
  Meter(4, 4) +
  Line(pitches = list("C5"), durations = list("whole"))

We have added two components:

  1. a 4/4 time signature and
  2. a musical line which consists of a C5 whole note.

We can print the Music object to get a brief description of it:


m
Music

Line 1

* as part 1 staff 1 voice 1
* of length 1
* of pitch C5
* of duration 4

Meter 4/4 

We can also convert the Music object to a musical score and a playable audio file:


show(m, to = c("score", "audio"))

We can add many more components. A tempo mark, for example:


m <- m + Tempo(120)
show(m)

Add another musical line as a part:


m <- m + Line(
  pitches = list("C3", "G3"),
  durations = list("half", "half")
)

show(m)

Add another musical line as a voice:


m <- m + Line(
  pitches = list(c("E4", "G4"), c("D4", "F4"), c("F4", "A4"), c("E4", "G4")),
  durations = list("quarter", "quarter", "quarter", "quarter"),
  as = "voice",
  to = 1
)

show(m)

This process can keep going, but let’s stop here. If you want to go deeper, let’s first learn how to install and configure the gm package.

Installation and Configuration

Installing gm can be done from CRAN:


install.packages("gm")

Or using the development version from GitHub:


# install.packages("devtools")
devtools::install_github("flujoo/gm")

To use the gm package, MuseScore, an open source and free notation software, is required. You can install it from https://musescore.org/.

If you don’t install MuseScore to the default path (see https://musescore.org/en/handbook/3/revert-factory-settings), you need to specify the path to the MuseScore executable:

  1. Open “.Renviron” file. You can do this with usethis::edit_r_environ().

  2. Add MUSESCORE_PATH=<path to MuseScore executable> into it. For example, suppose on a computer using Windows the path is “C:/Program Files/MuseScore 3/bin/MuseScore3.exe”, you can add MUSESCORE_PATH=C:/Program Files/MuseScore 3/bin/MuseScore3.exe, if gm can’t figure it out.

  3. Restart R session to activate the change.

Now below is a complete guide to understanding the gm package.

Music Objects

In gm, we use Music objects to represent music.The workflow is usually like this:

  1. Initialize an empty Music object with Music().

  2. Add components to it with +, for example, Music() + Meter(4, 4).

  3. Print it, or convert it to musical score or audio file with show() to check its structure.

  4. Keep adding components and checking it until you get what you want.

  5. Sometimes you may want to export the final Music object with export().

Let’s have a look at each component that you can add to a Music object.

Line Objects

We use Line objects to represent musical lines.

Create a Line object with Line():


l <- Line(
  pitches = list("C4", c("E4", "G4"), NA),
  durations = list("quarter", "quarter", "quarter")
)

This Line object consists of a note, a chord and a rest.

Print it to check its structure:


l
Line

* of length 3
* of pitches C4, (E4, G4), _
* of durations 1, 1, 1 

Add it to a Music object to convert it to musical score:


m <- Music() + Meter(4, 4) + l
show(m)

As you may have noticed, to represent a musical line, we don’t directly supply notes, chords, or rests to Line() as input. Instead, we separate the line’s pitch and durational contents. This is more convenient, since you may always want to deal with one aspect and in the meantime ignore the other.

Pitches

The pitches argument in Line() represents a musical line’s pitch contents.

pitches must be a list, whose members can be

  1. single pitch notations, like "C4", to represent the pitch contents of notes,

  2. single MIDI note numbers, like 60 or "60", also to represent the pitch contents of notes,

  3. single NAs to represent the pitch contents of rests, and

  4. vectors of pitch notations and MIDI note numbers, like c("C4", "61"), to represent the pitch contents of chords.

A pitch notation consists of

  1. a tone name (from A to G),
  2. an optional accidental (-, --, # or ##), and
  3. a number identifying the pitch’s octave (from 0 to 9).

A MIDI note number is a number between 12 and 127.

See https://en.wikipedia.org/wiki/Scientific_pitch_notation#Table_of_note_frequencies for a conversion table of pitch notations and MIDI note numbers.

An advantage of MIDI note numbers is that they are easy to operate. For example, we have the following four MIDI note numbers:


pitches <- c(60, 62, 64, 65)

We can transpose them up by a fifth easily:


pitches + 7
[1] 67 69 71 72

However, this advantage comes at a cost: MIDI note numbers are always ambiguous. For example, the following three pitches are all equivalent to MIDI note number 73:

gm interprets MIDI note numbers based on piano keys and neighbor pitches. In the following example, MIDI note number 73 is interpreted as C5 sharp in the first measure, but as D5 flat in the second measure, for these two measures have different keys.


l <- Line(list(73, 73), list(2, 2))
m <- Music() + Meter(2, 4) + Key(7, bar = 1) + Key(-7, bar = 2) + l
show(m)

gm is not perfect in interpreting MIDI note numbers, so if you want it to be precise, use pitch notations.

Durations

The durations argument in Line() represents a musical line’s durational contents.

durations must be a list, whose members can be

  1. single duration notations or their abbreviations, like "quarter" or just "q",

  2. single duration values, like 1, which is equivalent to "quarter", and

  3. Duration objects returned by tuplet(), which is used to create complex tuplets.

The most basic duration notations consist of only duration types. The duration types (and their abbreviations) defined in gm are:


"maxima" ("m")
"long" ("l")
"breve" ("b")
"whole" ("w")
"half" ("h")
"quarter" ("q")
"eighth" ("8")
"16th" ("16")
"32nd" ("32")
"64th" ("64")
"128th" ("128")
"256th" ("256")
"512th" ("512")
"1024th" ("1024")

For example, the following musical line consists of a whole rest, a quarter rest, and a 32nd rest:


m <- Music() + Meter(6, 4) + Line(list(NA, NA, NA), list("w", "q", "32"))
show(m)

The duration type or its abbreviation can be followed by zero to four dots, in a duration notation, as in the following example:


l <- Line(rep(list(NA), 5), list("q", "q.", "q..", "q...", "q...."))
m <- Music() + Meter(9, 4) + l
show(m)

You can also append what we call “tupler” notations in gm to a duration notation to represent a tuplet:


l <- Line(rep(list(NA), 3), list("q/3", "q/3", "q/3"))
m <- Music() + Meter(1, 4) + l
show(m)

In the above example, we use "q/3" to represent a triplet. You can read "q/3" as “divide a quarter note into three parts, and take one of them”. We will talk more about tuplets in the next section.

To sum up, a duration notation consists of

  1. a duration type or its abbreviation,
  2. zero to four dots, and
  3. zero or more tupler notations.

You get a duration value when convert a duration notation to value. For example, 1 is equivalent to "quarter", and 1.5 to "quarter.".

However, the duration notations that can be converted to duration values must NOT contain tupler notation. For example, if you convert "q/3" to value, you get 1/3, but this in gm is not a valid duration value. We put this restriction to avoid ambiguity. For example, value 1/3 can be "q/3", "half/6", or two tied "eighth/3"s.

Also note that you can NOT use characters to represent duration values. For example, you can use duration value 1 to represent quarter note, but not "1", which is invalid in gm. This is also to avoid ambiguity. For example, we can use "eighth" to represent eighth note, whose abbreviation is "8". if "8" was a duration value, it would represent breve note, there would be a conflict. So keep "8" to only eighth note, and 8 to breve note.

Tuplets

Duration notations can’t represent some complex tuplets. For example, representing the first tuplet in the following score:

This tuplet can read as “divide a quarter note into three parts, take two of them”. We can’t express “take two” in any duration notation. This is where we need tuplet() and Tupler(). Let’s look at an example.

We can express duration notation "quarter/3" with tuplet() and Tupler() as:


t <- tuplet("quarter", Tupler(3, unit = "eighth", take = "eighth"))

This code can read as: divide a quarter note (tuplet("quarter", ...)) into there parts (Tupler(3, ...)), each part is an eighth note (unit = "eighth"), and we take one part (take = "eighth").

Print it and we can see it is just equivalent to "quarter/3":


t
quarter / 3 

Modify the code and we can represent the aforementioned tuplet:


tuplet("quarter", Tupler(3, unit = "eighth", take = "quarter"))
quarter / 3 * (quarter / eighth) 

The difference is that this time we take two parts or units.

To sum up, we use tuplet() to create complex tuplets, and Tupler() to specify how to divide a duration into parts and what to take from these parts.

We can go further with these two functions. For example, we can change unit in the above example:


t <- tuplet("quarter", Tupler(3, unit = "quarter", take = "quarter"))
t
quarter / 3 * (quarter / quarter) 

The score looks like this:


m <- Music() + Meter(1, 4) + Line(list(NA, NA, NA), list(t, t, t))
show(m)

This may seem strange to you, but it’s syntactically valid.

We can even create complex nested tuplets like in the following score:

The first three tuplets can be represented by:


t <- tuplet(
  "half",
  Tupler(3, unit = "quarter", take = "half"),
  Tupler(3, unit = "quarter", take = "quarter")
)

t
half / 3 * (half / quarter) / 3 

Note that there are two Tupler objects in the above code.

Now let’s create this score:


l <- Line(rep(list(NA), 4), list(t, t, t, tuplet("half/3")))
m <- Music() + Meter(2, 4) + l
show(m)

Yes, there is something wrong with this score. MuseScore (which gm uses internally) has some bugs, so it can’t convert MusicXML (which gm uses to represent musical scores internally) containing nested tuplets to score correctly. So if there are nested tuplets in your Music object, you can export it to MusicXML file with export(), and open the file with other notation software like Finale. Below is a correct score generated in Finale:

Note that in Line() objects, tuplets must form tuplet groups, or you will get an error. For example,


# try to create a `Line` object which consists of only one tuplet
Line(list(NA), list("q/3"))
Error: Tuplets in `durations` must form complete groups.

* The tuplet group containing `durations[[1]]` is incomplete.

Also note that tuplet groups must NOT cross barline. For example,


l <- Line(list(NA, NA, NA), list("h/3", "h/3", "h/3"))
m <- Music() + Meter(1, 4) + l
show(m)
Error: Any tuplet group in any Line of the Music must not cross barline.

* In Line 1, the tuplet group starting at position 1 crosses barline.

Tie

Sometimes you may want to tie together some notes, you can do this by specifying the argument tie in Line().


l <- Line(
  pitches = list(c("C5", "E5"), c("C5", "E5"), c("C5", "E5"), c("C5", "E5")),
  durations = list("quarter", "quarter", "quarter", "quarter"),
  tie = list(c(1, 1), c(2, 2), 3)
)

m <- Music() + Meter(4, 4) + l
show(m)

In tie = list(c(1, 1), c(2, 2), 3) in the above example,

  1. c(1, 1) means we add a tie to the first note of the first chord to connect it with the first note of the second chord. Note that to tie two adjacent notes, we only specify the position of the first note.

  2. Similarly, c(2, 2) means we add a tie to the second note of the second chord to connect it with the second note of the third chord.

  3. 3 means we add ties to the whole third chord to connect it with the fourth chord.

However, don’t bother to split a cross-bar note and tie resulted notes together, gm takes care of this kind of situations, like in the following example:


m <- Music() + Meter(1, 4) + Line(list("C5"), list(4))
show(m)

The C5 whole note is automatically split into four tied quarter notes, because the time signature is 1/4.

bar and offset

With these two arguments in Line(), you can easily insert a Line object at other positions rather than the first beat of the first measure.

For example, below is an ordinary Line object:


l <- Line(list("C5", "D5", "E5"), list(1, 1, 1))
m <- Music() + Meter(4, 4) + l
show(m)

We can insert it into the third measure after the second beat:


l <- Line(list("C5", "D5", "E5"), list(1, 1, 1), bar = 3, offset = 2)
m <- Music() + Meter(4, 4) + l
show(m)

Multiple Line Objects

We can add multiple Line objects to a Music object in different ways.

For example, below is a Music object that consists of only one Line object:


l1 <- Line(rep(list("E5"), 8), rep(list(0.5), 8), name = "a")
m <- Music() + Meter(4, 4) + l1
show(m)

The Line object is named “a” with argument name.

Below is another Line object named “b”:


Line(list("C4", "G4"), list(2, 2), name = "b")

We can add Line “b” as another part:


l2 <- Line(list("C4", "G4"), list(2, 2), name = "b")
show(m + l2)

Line “b” is appended at the end by default. We can make Line “b” come before Line “a”, by specifying argument to and after:


l2 <- Line(list("C4", "G4"), list(2, 2), name = "b", to = "a", after = FALSE)
m <- m + l2
show(m)

to = "a" means we add Line “b” with Line “a” as a reference.

The Music object contains two Line objects now. Let’s introduce a third Line object named “c”:


Line(list("A4", "B4", "C5"), rep(list("w/3"), 3), name = "c")

We can add it to Line “a” as another voice:


l3 <- Line(
  pitches = list("A4", "B4", "C5"),
  durations = rep(list("w/3"), 3),
  name = "c",
  to = "a",
  as = "voice"
)

m <- m + l3
show(m)

Finally, below is a fourth Line, let’s add it to Line “b” as another staff:


l4 <- Line(list("E3"), list(4), to = "b", as = "staff", name = "d")
m <- m + l4
show(m)

We can print this Music to check its structure:


m
Music

Line 1

* as part 1 staff 1 voice 1
* of length 2
* of pitches C4, G4
* of durations 2, 2
* of name "b"

Line 2

* as part 1 staff 2 voice 1
* of length 1
* of pitch E3
* of duration 4
* of name "d"

Line 3

* as part 2 staff 1 voice 1
* of length 8
* of pitches E5, E5, E5, E5, E5, E5, E5, E5
* of durations 1/2, 1/2, 1/2, 1/2, 1/2, 1/2, 1/2, 1/2
* of name "a"

Line 4

* as part 2 staff 1 voice 2
* of length 3
* of pitches A4, B4, C5
* of durations 4/3, 4/3, 4/3
* of name "c"

Meter 4/4 

The Music contains four Lines now, which have different states. In summary, there are two parts, the first part contains two staffs, which are Line “b” and Line “d”, and the second part contains one staff which has two voices, which are Line “a” and Line “c”.

With these arguments in Line() we have so far introduced, you can create quite complex musical scores easily.

Meter Objects

We use Meter objects to represent time signatures. For example, a 3/4 time signature can be represented by


Meter(number = 3, unit = 4)
Meter 3/4 

We can add it to a Music object:


Music() + Meter(3, 4)
Music

Meter 3/4 

To change time signature in the middle, we can add another Meter object with specific bar. For example,


m <- Music() + Meter(3, 4) + Meter(1, 8, bar = 2)
m
Music

Meters

* 3/4 at bar 1
* 1/8 at bar 2 

Add a Line, we can convert this Music to score:


m <- m + Line(list("G4", "A4"), list(3, 0.5))
show(m)

By specifying arguments actual_number and actual_unit, we can create pickup measures. For example,


m <- Music() +
  Meter(4, 4, actual_number = 1, actual_unit = 4) +
  Meter(4, 4, bar = 2, invisible = TRUE)

m
Music

Meters

* 4/4 (1/4) at bar 1
* 4/4 at bar 2 

l <- Line(list("A4", "B4", "C5", "D5", "E5"), rep(list(1), 5))
show(m + l)

The time signature which appears on the first measure is 4/4, but the actual one is 1/4. invisible = TRUE makes the second time signature invisible.

Key Objects

We use Key objects to represent key signatures.

By specifying argument key in Key(), we can create key signatures from C flat major to C sharp major.


for (key in -7:7) {
  print(Key(key))
}
Key C- major (A- minor) 
Key G- major (E- minor) 
Key D- major (B- minor) 
Key A- major (F minor) 
Key E- major (C minor) 
Key B- major (G minor) 
Key F major (D minor) 
Key C major (A minor) 
Key G major (E minor) 
Key D major (B minor) 
Key A major (F# minor) 
Key E major (C# minor) 
Key B major (G# minor) 
Key F# major (D# minor) 
Key C# major (A# minor) 

By specifying argument bar in Key(), we can change the key signature of a musical score in the middle. For example,


m <- Music() + Meter(1, 4) + Key(-7) + Key(0, bar = 2) + Key(7, bar = 3)
m
Music

Meter 1/4

Keys

* C- major (A- minor) at bar 1
* C major (A minor) at bar 2
* C# major (A# minor) at bar 3 

l <- Line(list(NA, NA, NA), list(1, 1, 1))
show(m + l)

We can add a key signature to a specific part or staff, rather than to the whole score. For example, below is a musical score of two parts:


m <- Music() + Meter(4, 4) +
  Line(list("E5"), list(4)) +
  Line(list("G4", "A4"), list(2, 2), to = 1, as = "staff") +
  Line(list("C4"), list(4)) +
  Line(list("C3", "D3"), list(2, 2), to = 3, as = "staff")

show(m)

We can change the key signature of only the first part:


m <- m + Key(-2, to = 1)
show(m)

to = 1 means the Key object is added only to the part containing the first Line in the Music object.

We can change the key signature of only the second staff of the second part:


m <- m + Key(2, to = 4, scope = "staff")
show(m)

scope = "staff" means the key signature is added only to a specific staff.

Clef Objects

We use Clef objects to represent clefs.

Clef objects you can create in gm are:


Clef("G")
treble Clef 

Clef("G", line = 1)
French Clef 

Clef("G", octave = 1)
octave up treble Clef 

Clef("G", octave = -1)
octave down treble Clef 

Clef("F")
bass Clef 

Clef("F", line = 3)
baritone F-Clef 

Clef("F", line = 5)
subbass Clef 

Clef("F", octave = 1)
octave up bass Clef 

Clef("F", octave = -1)
octave down bass Clef 

Clef("C")
alto Clef 

Clef("C", line = 1)
soprano Clef 

Clef("C", line = 2)
mezzo-soprano Clef 

Clef("C", line = 4)
tenor Clef 

Clef("C", line = 5)
baritone C-Clef 

gm automatically adds a clef to a staff based on its pitch contents, if the staff has no clef at the beginning. For example, in the following score, gm adds an alto clef to the staff:


l <- Line(list("B3", "C4", "D4"), list(3, 3, 3))
m <- Music() + Meter(3, 4) + l
show(m)

If not satisfied, you can change it:


m <- m + Clef("G", to = 1)
show(m)

You can also add a clef into other measures:


m <- m + Clef("F", to = 1, bar = 2)
show(m)

Or even at other beat:


m <- m + Clef("G", octave = -1, to = 1, bar = 3, offset = 2)
show(m)

Tempo Objects

We use Tempo objects to represent tempo marks.

We can create a Tempo object with Tempo(). For example,


Tempo(tempo = 240)
Tempo quarter = 240 

tempo = 240 means the speed is 240 quarter notes per minute. The default unit is quarter note, you can change it with argument unit:


Tempo(tempo = 240, unit = "half")
Tempo half = 120 

These two speeds, 240 quarter notes per minute and 120 half notes per minute, are nonetheless equivalent.

With arguments bar and offset, you can add a Tempo object at any position. For example,


m <- Music() +
  Meter(4, 4) +
  Line(as.list(70:77), rep(list(1), 8)) +
  Tempo(60) +
  Tempo(240, unit = "half.", bar = 1, offset = 2.5)

show(m, to = c("score", "audio"))

Algorithmic Composition

gm provides a simple and intuitive language, with which you can express complex music easily. One possible usage of gm is algorithmic composition, which means using algorithms to create music.

Below is an example:


pitches <- as.list(c(64, 65, 69, 71, 72, 76))
durations <- rep(list(1), length(pitches))

m <- Music() + Meter(4, 4) + Tempo(120)

for (i in 0:8) {
  m <- m + Line(pitches, durations, offset = 0.5 * i)
}

show(m, to = c("score", "audio"))

The code generates nine parts which all contain the same pitch and durational contents. The only difference is that their delays are gradually increased, to create the echo sound effect.

Citing the gm package

You can simply do:


citation(package = "gm")

Citation

For attribution, please cite this work as

Mao & Wundervald (2021, April 3). R-Music: Introduction to the gm package. Retrieved from https://r-music.rbind.io/posts/2021-04-03-gm/

BibTeX citation

@misc{mao2021introduction,
  author = {Mao, Renfei and Wundervald, Bruna},
  title = {R-Music: Introduction to the gm package},
  url = {https://r-music.rbind.io/posts/2021-04-03-gm/},
  year = {2021}
}