Complete Guide to gm

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

It provides a simple and intuitive high-level language, with which you can create music easily. 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

Load gm:

library(gm)

Initialize an empty Music object:

m <- Music()

Add components:

m <- m +
  # add a 4/4 time signature
  Meter(4, 4) +
  # add a musical line of a C5 whole note
  Line(pitches = list("C5"), durations = list("whole"))

Print the Music object to see 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

Convert the Music object to musical score and audio file:

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

Continue.

Add a tempo mark:

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 gm.

Installation

Install gm from CRAN:

install.packages("gm")

Or install the development version from GitHub:

# install devtools if not
# install.packages("devtools")

devtools::install_github("flujoo/gm")

Download and install MuseScore, an open source and free notation software, which gm uses internally.

Configuration

If you don’t install MuseScore to the default path, please specify the path to the MuseScore executable.

First, open .Renviron file. You can do this with

usethis::edit_r_environ()

Next, add an environment variable into it:

MUSESCORE_PATH=<path to MuseScore executable>

Last, restart R session to activate the change.

For example, suppose the path on a Windows system is

C:/Program Files/MuseScore 3/bin/MuseScore3.exe

If gm can’t find it, you can add

MUSESCORE_PATH=C:/Program Files/MuseScore 3/bin/MuseScore3.exe

Now below is a complete guide to gm.

Music Objects

gm uses 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().

The workflow and the grammar may remind you of ggplot2.

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 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 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:

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, how to represent 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.

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, 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"))

Comparison

R Package tabr

At the time of writing, the author knows only tabr that has similar purposes and similar level of functionality to gm, among other R music packages.

However, there are some noticeable differences.

First, besides music generation, tabr also provides functions for music analysis and manipulation. gm is only for music generation.

Second, as for music generation, tabr is more about music notation. For example, tabr can generate guitar tabs and add more music notation symbols to score than gm can. gm is more about music creation at a higher level.

Third, gm embeds generated music in R Markdown documents and R Jupyter Notebooks, while tabr exports it.

Fourth, gm uses R’s basic data structures such as vector and list to represent music, while tabr uses strings.

Last, gm’s language is simpler than tabr’s, it is more straightforward to add components such as musical lines in gm. This and the above difference make gm more suitable for music programming.

Python Library music21

music21 is a mature and famous Python library which provides abundant tools for music generation and analysis. For its music generation functionality, music21 can generate more professional musical scores than gm can, and also works in Python Jupyter Notebooks.

However, the advantage of gm is still that its language is far simpler and more intuitive.

For example, in music21, it’s cumbersome to define notes and add them to musical lines:

from music21 import *

note1 = note.Note("C4")
note2 = note.Note("F#4")
note3 = note.Note("B-2")

stream1 = stream.Stream()

stream1.append(note1)
stream1.append(note2)
stream1.append(note3)

Also, creation of complex and nested tuplets is not straightforward. See Complex and Tuplets.

And sometimes users have to be concerned about musical score structures when they only want to express music structures at a higher level.

Summary

In summary, gm is a good tool for expression of high-level music structures and communication of generated music. It’s not yet for professional level music notation.

Algorithmic Composition

gm’s language is simple but powerful. One possible use 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 interesting echo sound effect.

In this example, you can see how gm uses R’s basic data structures to represent pitches and durations, and how gm’s language is simple and high-level. These two features make gm very suitable for music programming, without users considering music notation details.

Citation, Donation and Contact

Cite gm:

citation(package = "gm")

if you appreciate gm, please consider a donation:

https://ko-fi.com/flujoo

Please free to contact me if you want to say anything:

renfeimao AT gmail.com