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.

  • Define a musical phrase with phrase() or the shorthand alias p().
  • Add the phrase to a track().
  • Add the track to a score().
  • Render the score to pdf with tab() or another render_* function.

Constructing a musical phrase

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.

Music syntax

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)
#> # 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)
#> # 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

Calling LilyPond from R

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.

Score metadata

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 `./'
#> 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 `./'...
#> 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.