This section covers lilypond() and tab() for writing LilyPond files and engraving to sheet music with LilyPond, respectively. Examples of the various pieces of metadata that can be passed to to these functions are provided.

Rendering

The lilypond() function creates a LilyPond file (.ly). Various rendering functions wrap lilypond() to combine the steps of creating an intermediary LilyPond file and rendering it to pdf or png. The most general render function is tab(), which for naming consistency can also be called as render_tab(). Other render_* functions exist including render_score() and render_midi(), which focus on rendering non-tablature, all-purpose sheet music and rendering only the corresponding MIDI file, respectively.

The examples in this vignette focus on tab(). You can set keep_ly = TRUE in render functions to retain the intermediary LilyPond file.

By now you have seen many calls to tab() throughout these vignettes in order to show full examples previously. With sufficient coverage of phrases, tracks and scores, and the progression through them, now it is time to go into more detail on the arguments that can be supplied as part of the rendering or engraving process that apply to the entire piece of sheet music.

Score metadata

These are the most critical components that can be supplied to lilypond(). key specifies the global key signature, e.g. key ="dm". Key signatures set for individual track override the global key, but the global key is used for any track whose key is NA. Key changes in the middle of a song are not supported, but you can always edit the LilyPond file directly to make custom changes.

time gives the time signature, defaulting to common time, "4/4". tempo provides the song tempo, defaulting to tempo = "2 = 60", which is the LilyPond default. This can be read as 60 half note beats per minute.

Note that key takes the tabr key signature notation, but time and tempo which are used mostly in a transcription context like this, take values that match LilyPond format.

Throughout this tutorial section, the guitar and bass example from the section on tracks and scores is reused. The chord chart and chord sequence are retained to provide the most complete illustration of a rendered score. For completeness, here is what you have so far.

voice1 <- rp(p("c5 d5 e5 f5 g5", "1 2 4 4 1", "1*5"), 2)

notes <- "c e g c' e' c' g e g b d' g' f a c' f' c e g e c"
strings <- "5 4 3 2 1 2 3 4 4 3 2 1 4 3 2 1 5 4 3 4 5"
voice2 <- rp(p(notes, "8*20 2", strings), 2)

bass <- rp(p("c2e2*4 g1*2 f1*2 c2e2*3", "4*10 2", "32*4 4*4 32*3"), 2)

t1 <- track(voice1, voice = 1)
t2 <- track(voice2, voice = 2)
t3 <- track(bass, clef = "bass_8", tuning = "bass")

chords <- chord_set(c(c = "x32o1o", g = "355433", f = "133211"))
chord_seq <- rep(setNames(c(1, 2, 2, 1), names(chords)[c(1:3, 1)]), 3)
chords
#>              c              g              f 
#> "x;3;2;o;1;o;" "3;5;5;4;3;3;" "1;3;3;2;1;1;"
chord_seq
#> c g f c c g f c c g f c 
#> 1 2 2 1 1 2 2 1 1 2 2 1

song <- trackbind(t1, t2, t3, id = c(1, 1, 2)) |> score(chords, chord_seq)
song
#> # A tibble: 3 × 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
#> 2 <phrase [1]> treble_8 NA    TRUE  e,a,dgbe'      2 NA         1
#> 3 <phrase [1]> bass_8   NA    TRUE  e,,a,,d,g,     1 NA         2

This time when rendering the song, use settings for the three arguments discussed above. Given what is written, it doesn’t make sense to change them all. For example, this won’t fit well as a waltz (time = "3/4"). But for illustration purposes, pretend the song is actually in the key of D minor (F major). This key has one flat, Bb, so this will affect the display of the B note in the G chord. Change the time to 2/2 just for the sake of changing it, which won’t really be any different from 4/4 except you will see a line through the common time symbol. Finally, change the tempo to 4 = 120, which is also equivalent to the default, but will show up in the output slightly differently as well.

lilypond(song, "ex32.ly", "dm", "2/2", "4 = 120")
tab(song, "ex32.pdf", "dm", "2/2", "4 = 120")

Notice how the key change alone, which added room for a single flat symbol to be squeezed into the start of the treble and bass clef staves, was enough to a line wrap compared to the previous tutorial section where it all fit on one line. This is because it was a tight fit to in the previous examples and there was not enough room for LilyPond to continue fitting everything on one line with this slight widening created by the key change. LilyPond generates sheet music with a somewhat responsive layout engraver. It won’t necessarily leave everything on one line and push only a single measure to line two.

Output file

The output file specified with file ends in pdf in every example. However, you can switch to png. Output is inferred from the file extension. file can be a relative or absolute path to the file.

Song information

The next important argument to lilypond() is header. header takes a named list of character strings that are used to fill in general song information such as the title and composer. All previous examples were blank above the tabs because none of this information was ever provided. Below an example is given that uses all available arguments. You can use any subset of these. There is no requirement to supply them all. This example just shows what is available. Several of these would likely not be used in most cases.

header <- list(
  title = "Song title",
  composer = "Words and music by the composer",
  performer = "Song performer",
  album = "Album title",
  subtitle = "Subtitle",
  arranger = "Arranged by tab arranger",
  copyright = "2018 <Record Label>",
  instrument = "guitar and bass",
  tagline = "A tagline",
  meter = "meter tag", opus = "opus tag", piece = "piece tag", poet = "poet tag"
)

tab(song, "ex33.pdf", header = header)

The copyright and tagline are cutoff in the above image, but in your pdf you will see these if you scroll to the bottom of the rendered page.

Other settings

MIDI files

You have probably noticed by now that every time a pdf if rendered, an accompanying MIDI file is also generated. This can be turned off with midi = FALSE. This specification goes into the LilyPond file itself via lilypond(). Therefore, no MIDI output will be created even if you create the LilyPond file with lilypond() but convert that to pdf with LilyPond outside of R.

One thing to note about MIDI files is that tabr will unfold any repeats that occur in a song due to calls to rp(), pct() or volta(). This allows the MIDI file to play everything the proper number of times rather than ending prematurely by not being able to read repeat notation.

Also, tabr is a package aimed at creating guitar tablature. It is not concerned with MIDI or audio signals and audio data in general. Any MIDI functionality is considered to be an extra feature and does not receive priority development or support. At this time, there is no way to make other alterations to the MIDI file internals. MIDI output can be toggled on or off as mentioned. And without you having to do anything, MIDI output will respect repeat notation in the rendered LilyPond file. MIDI output will also automatically be transposed to match a transposition that is applied to a music staff (under reasonable and simplified conditions).

String names

The string_names is only relevant for tablature staves. argument defaults to NULL. This means that standard tuning is never specified alongside the lines of a tab staff at the beginning of the tablature. However, any other tuning will be explicitly noted so the reader is aware of the alternate tuning of any applicable track. This can also be set to TRUE to force all tunings to be explicit including standard guitar tuning or FALSE to suppress them all (though it is unclear what value there is in the FALSE setting).

Paper and color settings

There is some nominal level of control over the paper layout via paper. Like header, this is a named list. The defaults are fine so you probably do not need to alter most of these values. There are six options, all numeric except for page_numbers, which is logical.

  • textheight
  • linewidth
  • indent
  • first_page_number
  • page_numbers
  • fontsize

If you pass any values for some of these in a named list to paper, any not passed will retain their built in defaults. You do not have to supply them all when using paper.

There are also global color settings. You can pass a named list to colors with any of the following. Values are hex colors or R color names.

  • color
  • background
  • staff_lines
  • time
  • clef
  • beam
  • head
  • stem
  • accidental

For more detail on these settings and others further above, see the help documentation for lilypond().

PNG output

Here are examples that make use of png-specific options and function behavior. First, when rendering to png instead of pdf, automatic cropping of the image is attempted. This removes the full page appearance for smaller snippets of sheet music. There are some limitations to this. For example, inclusion of header elements can disrupt this behavior.

x <- pitch_seq("e,", 24, key ="c") |> as_music(8) |> p() |> track() |> score()

ppr <- list(linewidth = 60, page_numbers = FALSE)
hdr <- list(title = "Notes in C on guitar", subtitle = "Standard tuning")
tab(x, "ex_png1.png", tempo = NULL, midi = FALSE, header = hdr, paper = ppr)

Above, the height is automatically cropped because file type is a png. Providing textheight to paper overrides this behavior. The width would have also been cropped on its own. However, because of the inclusion of header arguments, this fails and explicit linewidth is needed. Similarly, if elements that describe footer content (still part of the LilyPond header block) that appear at the bottom of the page such as tagline or copyright were included, automatic height cropping would have also failed. In general, automatic cropping is the default for png output, but header disrupts this, requiring manual overrides to width and height.

In the example below, remove the header content. You can make a transparent background png, though you won’t be able to see the difference here. Instead, create a dark mode example and increase the resolution.

Since this is a single voice, single track, you do not need to explicitly call each function in the transcription pipeline. Use one of the convenient render_music* functions to abstract this process, rendering sheet music directly from a music object. render_music_guitar() has good default arguments for this example.

colors <- list(color = "#e4e4e4", background = "gray10", head = "tomato", tabhead = "tomato")

x <- as_music(pitch_seq("e,", 24, key ="c"), 8) |> 
  render_music_guitar("ex_png2.png", colors = colors, res = 300)

The LilyPond file

Finally, familiarize yourself with the LilyPond file itself. You may output a file and want to make additional edits to it directly. The example below also demonstrates some differences between simplify = TRUE and simplify = FALSE. This is a simple piece of music so not all possible syntax simplifications are shown here.

phrase() objects have more robust structure that has advantages for performing object manipulations in R, but this is a more verbose version of LilyPond syntax. By default, lilypond() as well as associated render_* functions will simplify this to a more efficient syntax so that it is less cumbersome to read and work with LilyPond files directly.

Original syntax

There are other simplifications but the most notable benefits are the removal of all the <> around single notes and not repeating consecutive durations if they are unchanged from previous timesteps.

lilypond(song, "song.ly", simplify = FALSE)
cat(readLines("song.ly"), sep = "\n")
#> \version "2.23.6"
#> #(ly:set-option 'crop #t)\paper{
#>   line-width=150\mm
#>   oddFooterMarkup = ##f
#>   oddHeaderMarkup = ##f
#>   bookTitleMarkup = ##f
#>   scoreTitleMarkup = ##f
#>   indent = 0.\mm
#>   first-page-number = 1
#>   print-page-number = ##t
#>   print-first-page-number = ##t
#> }
#> 
#> #(set-global-staff-size 10)
#> \header {
#>   title = ""
#>   subtitle = ""
#>   composer = ""
#>   arranger = ""
#>   instrument = ""
#>   metre = ""
#>   opus = ""
#>   piece = ""
#>   poet = ""
#>   copyright = ""
#>   tagline = ""
#> }
#> \include "predefined-guitar-fretboards.ly"
#> 
#> #(define fb1 (make-fretboard-table))
#> \storePredefinedDiagram #fb1 \chordmode{c} #guitar-tuning "x;3;2;o;1;o;"
#> #(define fb2 (make-fretboard-table))
#> \storePredefinedDiagram #fb2 \chordmode{g} #guitar-tuning "3;5;5;4;3;3;"
#> #(define fb3 (make-fretboard-table))
#> \storePredefinedDiagram #fb3 \chordmode{f} #guitar-tuning "1;3;3;2;1;1;"
#> 
#> global = {
#>   \time 4/4
#>   \tempo 2 = 60
#>   \bar "|."
#> }
#> 
#> global_key = {
#>  \key c \major 
#> }
#> 
#> mychorddiagrams = \chordmode {
#>   \set predefinedDiagramTable = #fb1 c
#>   \set predefinedDiagramTable = #fb2 g
#>   \set predefinedDiagramTable = #fb3 f
#> }
#> 
#> chordNames = \chordmode {
#>   \override ChordName.font-size = #2
#>   \global
#>   c1 g2 f2 c1 c1 g2 f2 c1 c1 g2 f2 c1
#> }
#> 
#> \markup\vspace #3
#> \markup \fill-line {
#>   \score {
#>     <<
#>       \context ChordNames { \mychorddiagrams }
#>       \context FretBoards {
#>         \override FretBoards.FretBoard.size = #1.2
#>         \mychorddiagrams
#>       }
#>     >>
#>   \layout {}
#>   }
#> }
#> \markup\vspace #3
#> 
#> melodyAA = {
#>  \global
#>  \override StringNumber.transparent = ##t
#>  \voiceOne \repeat unfold 3 { <c''\1>1 <d''\1>2 <e''\1>4 <f''\1>4 <g''\1>1 }
#> }
#> 
#> melodyAB = {
#>  \global
#>  \override StringNumber.transparent = ##t
#>  \voiceTwo \repeat unfold 3 { <c\5>8 <e\4>8 <g\3>8 <c'\2>8 <e'\1>8 <c'\2>8 <g\3>8 <e\4>8 <g\4>8 <b\3>8 <d'\2>8 <g'\1>8 <f\4>8 <a\3>8 <c'\2>8 <f'\1>8 <c\5>8 <e\4>8 <g\3>8 <e\4>8 <c\5>2 }
#> }
#> 
#> melodyB = {
#>  \global
#>  \override StringNumber.transparent = ##t
#>  \repeat unfold 3 { <c,\3 e,\2>4 <c,\3 e,\2>4 <c,\3 e,\2>4 <c,\3 e,\2>4 <g,,\4>4 <g,,\4>4 <f,,\4>4 <f,,\4>4 <c,\3 e,\2>4 <c,\3 e,\2>4 <c,\3 e,\2>2 }
#> }
#> 
#> midimelodyAA = {
#>  \global
#>  \override StringNumber.transparent = ##t
#>  \unfoldRepeats { \voiceOne \repeat unfold 3 { <c''\1>1 <d''\1>2 <e''\1>4 <f''\1>4 <g''\1>1 }
#>  }}
#> 
#> midimelodyAB = {
#>  \global
#>  \override StringNumber.transparent = ##t
#>  \unfoldRepeats { \voiceTwo \repeat unfold 3 { <c\5>8 <e\4>8 <g\3>8 <c'\2>8 <e'\1>8 <c'\2>8 <g\3>8 <e\4>8 <g\4>8 <b\3>8 <d'\2>8 <g'\1>8 <f\4>8 <a\3>8 <c'\2>8 <f'\1>8 <c\5>8 <e\4>8 <g\3>8 <e\4>8 <c\5>2 }
#>  }}
#> 
#> midimelodyB = {
#>  \global
#>  \override StringNumber.transparent = ##t
#>  \unfoldRepeats { \repeat unfold 3 { <c,\3 e,\2>4 <c,\3 e,\2>4 <c,\3 e,\2>4 <c,\3 e,\2>4 <g,,\4>4 <g,,\4>4 <f,,\4>4 <f,,\4>4 <c,\3 e,\2>4 <c,\3 e,\2>4 <c,\3 e,\2>2 }
#>  }
#> }
#> 
#> \score {  <<
#>   \new ChordNames \chordNames
#>   \new Staff << \clef "treble_8" \global_key \context Voice = "melodyAA" \melodyAA \context Voice = "melodyAB" \melodyAB >>
#>   \new TabStaff \with { stringTunings = \stringTuning <e, a, d g b e'> } <<
#>     \override Stem.transparent = ##t
#>     \override Beam.transparent = ##t
#>     \context TabVoice = "melodyAA" \melodyAA \context TabVoice = "melodyAB" \melodyAB
#>   >>
#>   \new Staff << \clef "bass_8" \global_key \melodyB >>
#>   \new TabStaff \with { stringTunings = \stringTuning <e,, a,, d, g,> } <<
#>     \set TabStaff.instrumentName = \markup  { \hspace #7 \override #'(baseline-skip . 1.5)  \column \fontsize #-4.5 \sans { G D A E } }
#>     \override Stem.transparent = ##t
#>     \override Beam.transparent = ##t
#>     \melodyB
#>   >>
#>   >>
#>   \layout{ }
#> }
#> 
#> \score {  <<
#>   \midimelodyAA
#>   \midimelodyAB
#>   \midimelodyB
#>   >>
#>   \midi{
#>     \tempo 2 = 60
#>   }
#> }

Simplified syntax

Here you can see the changes to the notation. Since the music is so short in this example, most of the file remains the same.

lilypond(song, "song.ly")
cat(readLines("song.ly"), sep = "\n")
#> \version "2.23.6"
#> #(ly:set-option 'crop #t)\paper{
#>   line-width=150\mm
#>   oddFooterMarkup = ##f
#>   oddHeaderMarkup = ##f
#>   bookTitleMarkup = ##f
#>   scoreTitleMarkup = ##f
#>   indent = 0.\mm
#>   first-page-number = 1
#>   print-page-number = ##t
#>   print-first-page-number = ##t
#> }
#> 
#> #(set-global-staff-size 10)
#> \header {
#>   title = ""
#>   subtitle = ""
#>   composer = ""
#>   arranger = ""
#>   instrument = ""
#>   metre = ""
#>   opus = ""
#>   piece = ""
#>   poet = ""
#>   copyright = ""
#>   tagline = ""
#> }
#> \include "predefined-guitar-fretboards.ly"
#> 
#> #(define fb1 (make-fretboard-table))
#> \storePredefinedDiagram #fb1 \chordmode{c} #guitar-tuning "x;3;2;o;1;o;"
#> #(define fb2 (make-fretboard-table))
#> \storePredefinedDiagram #fb2 \chordmode{g} #guitar-tuning "3;5;5;4;3;3;"
#> #(define fb3 (make-fretboard-table))
#> \storePredefinedDiagram #fb3 \chordmode{f} #guitar-tuning "1;3;3;2;1;1;"
#> 
#> global = {
#>   \time 4/4
#>   \tempo 2 = 60
#>   \bar "|."
#> }
#> 
#> global_key = {
#>  \key c \major 
#> }
#> 
#> mychorddiagrams = \chordmode {
#>   \set predefinedDiagramTable = #fb1 c
#>   \set predefinedDiagramTable = #fb2 g
#>   \set predefinedDiagramTable = #fb3 f
#> }
#> 
#> chordNames = \chordmode {
#>   \override ChordName.font-size = #2
#>   \global
#>   c1 g2 f2 c1 c1 g2 f2 c1 c1 g2 f2 c1
#> }
#> 
#> \markup\vspace #3
#> \markup \fill-line {
#>   \score {
#>     <<
#>       \context ChordNames { \mychorddiagrams }
#>       \context FretBoards {
#>         \override FretBoards.FretBoard.size = #1.2
#>         \mychorddiagrams
#>       }
#>     >>
#>   \layout {}
#>   }
#> }
#> \markup\vspace #3
#> 
#> melodyAA = {
#>  \global
#>  \override StringNumber.transparent = ##t
#>  \voiceOne \repeat unfold 3 { c''1\1 d''2\1 e''4\1 f''\1 g''1\1 }
#> }
#> 
#> melodyAB = {
#>  \global
#>  \override StringNumber.transparent = ##t
#>  \voiceTwo \repeat unfold 3 { c8\5 e\4 g\3 c'\2 e'\1 c'\2 g\3 e\4 g\4 b\3 d'\2 g'\1 f\4 a\3 c'\2 f'\1 c\5 e\4 g\3 e\4 c2\5 }
#> }
#> 
#> melodyB = {
#>  \global
#>  \override StringNumber.transparent = ##t
#>  \repeat unfold 3 { <c,\3 e,\2>4 <c,\3 e,\2> <c,\3 e,\2> <c,\3 e,\2> g,,\4 g,,\4 f,,\4 f,,\4 <c,\3 e,\2> <c,\3 e,\2> <c,\3 e,\2>2 }
#> }
#> 
#> midimelodyAA = {
#>  \global
#>  \override StringNumber.transparent = ##t
#>  \unfoldRepeats { \voiceOne \repeat unfold 3 { c''1\1 d''2\1 e''4\1 f''\1 g''1\1 }
#>  }}
#> 
#> midimelodyAB = {
#>  \global
#>  \override StringNumber.transparent = ##t
#>  \unfoldRepeats { \voiceTwo \repeat unfold 3 { c8\5 e\4 g\3 c'\2 e'\1 c'\2 g\3 e\4 g\4 b\3 d'\2 g'\1 f\4 a\3 c'\2 f'\1 c\5 e\4 g\3 e\4 c2\5 }
#>  }}
#> 
#> midimelodyB = {
#>  \global
#>  \override StringNumber.transparent = ##t
#>  \unfoldRepeats { \repeat unfold 3 { <c,\3 e,\2>4 <c,\3 e,\2> <c,\3 e,\2> <c,\3 e,\2> g,,\4 g,,\4 f,,\4 f,,\4 <c,\3 e,\2> <c,\3 e,\2> <c,\3 e,\2>2 }
#>  }
#> }
#> 
#> \score {  <<
#>   \new ChordNames \chordNames
#>   \new Staff << \clef "treble_8" \global_key \context Voice = "melodyAA" \melodyAA \context Voice = "melodyAB" \melodyAB >>
#>   \new TabStaff \with { stringTunings = \stringTuning <e, a, d g b e'> } <<
#>     \override Stem.transparent = ##t
#>     \override Beam.transparent = ##t
#>     \context TabVoice = "melodyAA" \melodyAA \context TabVoice = "melodyAB" \melodyAB
#>   >>
#>   \new Staff << \clef "bass_8" \global_key \melodyB >>
#>   \new TabStaff \with { stringTunings = \stringTuning <e,, a,, d, g,> } <<
#>     \set TabStaff.instrumentName = \markup  { \hspace #7 \override #'(baseline-skip . 1.5)  \column \fontsize #-4.5 \sans { G D A E } }
#>     \override Stem.transparent = ##t
#>     \override Beam.transparent = ##t
#>     \melodyB
#>   >>
#>   >>
#>   \layout{ }
#> }
#> 
#> \score {  <<
#>   \midimelodyAA
#>   \midimelodyAB
#>   \midimelodyB
#>   >>
#>   \midi{
#>     \tempo 2 = 60
#>   }
#> }