Rendering sheet music is based on building up pieces of musical information culminating in a score. The fundamental object to consider in the transcription context is a phrase. A phrase is created from a noteworthy string and incorporates additional information, most importantly time and rhythm. It can also include positional information such as the instrument string on which a note is played. Outside of rendering tabs, there is no reason to construct phrase objects. Everything from the phrase object on up is about using the R to LilyPond pipeline to render some kind of sheet music document.
A good place to start is with a simple, reproducible example that
outlines the basic flow from small, succinct musical phrases, which
combine to form tracks, in turn combined into a score, and ending with
engraving the score to familiar sheet music. The brief example below
recreates the single measure of guitar tablature shown in the
tabr
logo. Here are the steps as a list.
phrase()
or the shorthand
alias p()
.track()
.score()
.tab()
or another
render_*
function.The term phrase here simply means any arbitrary piece of musical
structure you string together. phrase()
takes three main
arguments when building a phrase from its component parts. The first
gives pitches (or rests) separated in time by spaces. For chords, remove
spaces to indicate simultaneous notes. For example, a rest followed by a
sequence of pitches might be notes = "r a, c f d a f"
.
info
is note metadata such as duration. Whole notes are
given by 1, half notes by 2, quarter notes 4, and so on, e.g.,
info = "4 8 8 8 8 8 8"
(or shorten to
info = "4 8*6"
). This example does not require additional
information such as dotted notes, staccato notes, ties/slurs, slides,
bends, hammer ons and pull offs, etc.
The third argument, string
only applies to fretted
string instruments and is always optional. In this example it specifies
the strings of a guitar. Providing this information in conjunction with
the pitch fixes the frets so that LilyPond does not have to guess them.
This only applies for tablature output. Note that the x
shown below is just a placeholder indicating no need to specify a string
for the quarter note rest. You can put a string number there but it is
ignored.
Explicit string numbers are not needed for this example since lowest fret numbers (LilyPond default) are intended. They are provided for a more complete example.
This is the general approach, but there are multiple ways to create
equivalent phrase objects in tabr
.
phrase(notes = "r a, c f d a f", info = "4 8*6", string = "x 5 5 4 4 3 4")
#> <Musical phrase>
#> r4 <a,\5>8 <c\5>8 <f\4>8 <d\4>8 <a\3>8 <f\4>8
Building a phrase from component parts may be necessary in some programmatic contexts. However, when doing manual data entry for simple, interactive examples, the music class offers a higher level of abstraction, sparing you some typing as well as cognitive load.
As an aside, if you are working with the music
class,
you can enter notes, note info, and optionally string numbers if
applicable, all in one string. This is more efficient for data entry. It
can also be easier to follow because it binds the otherwise separate
arguments by timestep. See the vignettes and help documentation on music
objects for more details.
If you define the music object
as_music("r4 a,8 c f d a f")
#> <Music string>
#> Format: space-delimited time
#> Values: r4 a,8 c8 f8 d8 a8 f8
it can be passed directly to phrase()
, which understands
this syntax and interprets the notes
argument as music
syntax if the info
argument is not provided
(info = NULL
). In fact, the music object does not even need
to be previously defined. The string format can be directly provided to
phrase()
.
(p1 <- p("r4 a,8 c f d a f"))
#> <Musical phrase>
#> r4 <a,>8 <c>8 <f>8 <d>8 <a>8 <f>8
Notice how each timestep is complete within the single character string above. Also, durations (and string numbers) can repeat implicitly until an explicit change occurs.
Track construction is as simple as wrapping a phrase object in
track()
. This example uses a single phrase. Typically a
track would consist of many phrases concatenated together. Tracks are
just tibble data frames with an additional track class.
track1 <- track(p1)
track1
#> # A tibble: 1 × 7
#> phrase clef key tab tuning voice lyrics
#> <list> <chr> <chr> <lgl> <chr> <int> <chr>
#> 1 <phrase [1]> treble_8 NA TRUE e,a,dgbe' 1 NA
The complete score is composed of one or more tracks. This example has only a single track. Just as the track constructor takes phrases, the score constructor takes tracks as inputs. Score objects are tibble data frames with an additional score class.
song <- score(track1)
song
#> # A tibble: 1 × 8
#> phrase clef key tab tuning voice lyrics id
#> <list> <chr> <chr> <lgl> <chr> <int> <chr> <int>
#> 1 <phrase [1]> treble_8 NA TRUE e,a,dgbe' 1 NA 1
Once a score object is created, it is ready to be sent to LilyPond.
If LilyPond is installed on your system (and added to your system PATH
variable on Windows systems), tab()
or any of the
render_*
functions should call it successfully.
Alternatively, on Windows, it can be added explicitly by calling
tabr_options()
. This option to specify the LilyPond path is
still available on other systems. An example of this is commented out
below. However, tabr
will do its best on package load to
set these paths in tabr_options()
for you if it can
successfully detect a LilyPond installation in a standard file system
location, so you may not have to do anything. Just check
tabr_options()
after you load the package. If any of the
paths are equal to the empty string ""
, then you need to
set the paths. Otherwise you should be ready to run LilyPond from R.
# For example if path to `/bin` not set (edit depending on your installation)
# tabr_options(lilypond = "C:/lilypond-2.24.2/bin/lilypond.exe")
Technically, when loading tabr
on Windows it will
attempt to check the specific path above to see if
lilypond.exe
exists there. If it does, the path in
tabr_options()
will be filled in for you. Therefore, if you
need to specify the LilyPond path because it is not in your PATH
environment variable and nothing shows in tabr_options()
,
then the path shown above is probably not where you installed
LilyPond.
Other than ensuring LilyPond is accessible, there is only one
function left to call in this series of steps to produce the sheet
music. tab()
takes several arguments, many of which are
optional and have default values. However, since music is so variable it
makes sense to specify these arguments every time. These arguments
include metadata about the piece such as key signature, time signature,
tempo, title, author, and so on. Most of that is not needed here, but
for good practice at least be unambiguous about the music itself and
specify a key signature, time signature and tempo.
tab(song, "phrase.pdf", key = "dm", time = "4/4", tempo = "4 = 120")
#> #### Engraving score to phrase.pdf ####
#> GNU LilyPond 2.18.2
#> Processing `./phrase.ly'
#> Parsing...
#> Interpreting music...
#> Preprocessing graphical objects...
#> Interpreting music...
#> MIDI output to `./phrase.mid'...
#> Finding the ideal number of pages...
#> Fitting music on 1 page...
#> Drawing systems...
#> Layout output to `./phrase.ps'...
#> Converting to `./phrase.pdf'...
#> Success: compilation successfully completed
This log output will be printed at the R console. Load the new pdf file to see the result. It should look like this.
tabr
functions are designed to support piping. Even
given the series of steps from a phrase to a rendered pdf, a short
example like this does not require a single assignment. The same steps
involved in building up the structure and rendering the output could be
performed as follows.
p("r a2 c f d a f", "4 8*6") |> track() |> score() |>
tab("phrase.pdf", key = "dm", time = "4/4", tempo = "4 = 120")
While music can be quite complex and a full score will be much
longer, tabr
strives to minimize the work while still
forcing some sense of organized structure. For long and complex music,
it can require some effort and practice to ensure your approach to
transcription in your R code is not opaque.
To recap, the phrase()
is defined, added to a
track()
, the track is added to a score()
, and
the score is rendered to tablature in a pdf file with tab()
or other render_*
function. The next tutorial section
discusses phrases in more detail and provides an overview of the
different notes and note metadata that can be specified when
constructing a phrase.