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.
Load gm:
library(gm)
Initialize an empty Music
object:
<- Music() m
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 + Tempo(120)
m show(m)
Add another musical line as a part:
<- m + Line(
m pitches = list("C3", "G3"),
durations = list("half", "half")
)
show(m)
Add another musical line as a voice:
<- m + Line(
m 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.
Install gm from CRAN:
install.packages("gm")
Or install the development version from GitHub:
# install devtools if not
# install.packages("devtools")
::install_github("flujoo/gm") devtools
Download and install MuseScore, an open source and free notation software, which gm uses internally.
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
::edit_r_environ() usethis
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
Objectsgm uses Music
objects to represent music.
The workflow is usually like this:
Initialize an empty Music
object with Music()
.
Add components to it with +
, for example, Music() + Meter(4, 4)
.
Print it, or convert it to musical score or audio file with show()
to check its structure.
Keep adding components and checking it until you get what you want.
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
ObjectsWe use Line
objects to represent musical lines.
Create a Line
object with Line()
:
<- Line(
l 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:
<- Music() + Meter(4, 4) + l
m 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.
The pitches
argument in Line()
represents a musical line’s pitch contents.
pitches
must be a list, whose members can be
single pitch notations, like "C4"
, to represent the pitch contents of notes,
single MIDI note numbers, like 60
or "60"
, also to represent the pitch contents of notes,
single NA
s to represent the pitch contents of rests, and
vectors of pitch notations and MIDI note numbers, like c("C4", "61")
, to represent the pitch contents of chords.
A pitch notation consists of
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:
<- c(60, 62, 64, 65) pitches
We can transpose them up by a fifth easily:
+ 7
pitches #> [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.
<- Line(list(73, 73), list(2, 2))
l <- Music() + Meter(2, 4) + Key(7, bar = 1) + Key(-7, bar = 2) + l
m show(m)
gm is not perfect in interpreting MIDI note numbers, so if you want it to be precise, use pitch notations.
The durations
argument in Line()
represents a musical line’s durational contents.
durations
must be a list, whose members can be
single duration notations or their abbreviations, like "quarter"
or just "q"
,
single duration values, like 1
, which is equivalent to "quarter"
, and
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"
or "m"
"long"
or "l"
"breve"
or "b"
"whole"
or "w"
"half"
or "h"
"quarter"
or "q"
"eighth"
or "8"
"16th"
or "16"
"32nd"
or "32"
"64th"
or "64"
"128th"
or "128"
"256th"
or "256"
"512th"
or "512"
"1024th"
or "1024"
For example, the following musical line consists of a whole rest, a quarter rest, and a 32nd rest:
<- Music() + Meter(6, 4) + Line(list(NA, NA, NA), list("w", "q", "32"))
m 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:
<- Line(rep(list(NA), 5), list("q", "q.", "q..", "q...", "q...."))
l <- Music() + Meter(9, 4) + l
m show(m)
You can also append what we call “tupler” notations in gm to a duration notation to represent a tuplet:
<- Line(rep(list(NA), 3), list("q/3", "q/3", "q/3"))
l <- Music() + Meter(1, 4) + l
m 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
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.
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:
<- tuplet("quarter", Tupler(3, unit = "eighth", take = "eighth")) t
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:
<- tuplet("quarter", Tupler(3, unit = "quarter", take = "quarter"))
t
t#> quarter / 3 * (quarter / quarter)
The score looks like this:
<- Music() + Meter(1, 4) + Line(list(NA, NA, NA), list(t, t, t))
m 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:
<- tuplet(
t "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:
<- Line(rep(list(NA), 4), list(t, t, t, tuplet("half/3")))
l <- Music() + Meter(2, 4) + l
m 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,
<- Line(list(NA, NA, NA), list("h/3", "h/3", "h/3"))
l <- Music() + Meter(1, 4) + l
m 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.
Sometimes you may want to tie together some notes, you can do this by specifying the argument tie
in Line()
.
<- Line(
l 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)
)
<- Music() + Meter(4, 4) + l
m show(m)
In tie = list(c(1, 1), c(2, 2), 3)
in the above example,
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.
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
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:
<- Music() + Meter(1, 4) + Line(list("C5"), list(4))
m 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:
<- Line(list("C5", "D5", "E5"), list(1, 1, 1))
l <- Music() + Meter(4, 4) + l
m show(m)
We can insert it into the third measure after the second beat:
<- Line(list("C5", "D5", "E5"), list(1, 1, 1), bar = 3, offset = 2)
l <- Music() + Meter(4, 4) + l
m show(m)
Line
ObjectsWe 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:
<- Line(rep(list("E5"), 8), rep(list(0.5), 8), name = "a")
l1 <- Music() + Meter(4, 4) + l1
m 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:
<- Line(list("C4", "G4"), list(2, 2), name = "b")
l2 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
:
<- Line(list("C4", "G4"), list(2, 2), name = "b", to = "a", after = FALSE)
l2 <- m + l2
m 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:
<- Line(
l3 pitches = list("A4", "B4", "C5"),
durations = rep(list("w/3"), 3),
name = "c",
to = "a",
as = "voice"
)
<- m + l3
m show(m)
Finally, below is a fourth Line
, let’s add it to Line
“b” as another staff:
<- Line(list("E3"), list(4), to = "b", as = "staff", name = "d")
l4 <- m + l4
m 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 Line
s 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
ObjectsWe 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,
<- Music() + Meter(3, 4) + Meter(1, 8, bar = 2)
m
m#> Music
#>
#> Meters
#>
#> * 3/4 at bar 1
#> * 1/8 at bar 2
Add a Line
, we can convert this Music
to score:
<- m + Line(list("G4", "A4"), list(3, 0.5))
m show(m)
By specifying arguments actual_number
and actual_unit
, we can create pickup measures. For example,
<- Music() +
m 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
<- Line(list("A4", "B4", "C5", "D5", "E5"), rep(list(1), 5))
l 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
ObjectsWe 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,
<- Music() + Meter(1, 4) + Key(-7) + Key(0, bar = 2) + Key(7, bar = 3)
m
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
<- Line(list(NA, NA, NA), list(1, 1, 1))
l 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:
<- Music() + Meter(4, 4) +
m 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 + Key(-2, to = 1)
m 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 + Key(2, to = 4, scope = "staff")
m show(m)
scope = "staff"
means the key signature is added only to a specific staff.
Clef
ObjectsWe 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:
<- Line(list("B3", "C4", "D4"), list(3, 3, 3))
l <- Music() + Meter(3, 4) + l
m show(m)
If not satisfied, you can change it:
<- m + Clef("G", to = 1)
m show(m)
You can also add a clef into other measures:
<- m + Clef("F", to = 1, bar = 2)
m show(m)
Or even at other beat:
<- m + Clef("G", octave = -1, to = 1, bar = 3, offset = 2)
m show(m)
Tempo
ObjectsWe 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,
<- Music() +
m 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"))
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.
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 *
= note.Note("C4")
note1 = note.Note("F#4")
note2 = note.Note("B-2")
note3
= stream.Stream()
stream1
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.
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.
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:
<- as.list(c(64, 65, 69, 71, 72, 76))
pitches <- rep(list(1), length(pitches))
durations
<- Music() + Meter(4, 4) + Tempo(120)
m
for (i in 0:8) {
<- m + Line(pitches, durations, offset = 0.5 * i)
m
}
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.
Cite gm:
citation(package = "gm")
if you appreciate gm, please consider a donation:
Please free to contact me if you want to say anything:
renfeimao AT gmail.com