External data and alternate notation

The tabr package uses a particular syntax for music notation. It can convert this representation of music to the syntax used by LilyPond in order to render basic sheet music. However, music notation can be entered with an alternative syntax. It might be entered in some other form in R, or more likely may come from outside of R. It could be music notation from other software. In general, the source is some music dataset that contains music notation of some kind that you want to analyze.

While the music notation syntax used by tabr integrates functionality cohesively across the entire package and also supports data export to LilyPond, syntax conversion functions help with importing external data sources. Syntax converters map alternative music notation formats from various music data sources to tabr style. This allows you to leverage the functionality of tabr for music analysis and possibly transcription without having to convert the data yourself. Ideally these endeavors should be limited only by the quality and completeness of the imported data rather than the original notation format.

However, this requires that an alternative music notation syntax is known, and likely popular, and that a syntax converter has been written and added to the package for converting that particular format, to the extent possible. The alternative syntax can be arbitrary, but a converter must exist for it. This feature of the package has been slated for incorporation for a while, but is new in version 0.4. It is under active development. Currently two syntax converters are available and is used in the examples below.

Converting to tabr

The to_tabr() function is used for converting alternative representations of music notation to tabr syntax, allowing data in other formats to be piped seamlessly into music analysis workflows and even sheet music transcription. This function is a general wrapper around specific syntax converters, which are more likely to be called directly. Examples of both approaches are shown below.

Sources with partial information

Some sources do not offer as complete or explicit information for tabr to leverage, for example in order to make sufficiently informed sheet music. In such cases, what is available in those formats is converted to the extent possible. Available function arguments for a given syntax converter allow you to add some additional specification. Different arguments may be available and/or required for different syntax converters.

A good example of music data that is less complete than would be ideal for certain use cases is a list of chords obtained from the chorrrds. This package scrapes chord information from the Cifraclub website, which only provides song chords, not note for note transcription data for any particular instrument. This means the result of syntax conversion must also yield only chords, which is fine for data analysis but does not lend itself well to sheet music transcription.

music21 tiny notation

The tiny notation format for music21 is similar in several respects to tabr notation. The to_tabr() function, and specifically, from_music21(), will convert from music21 to tabr syntax. The functionality is still a work in progress, but works for simple notation.

m21 <- "4/4 CC#FF4.. trip{c#8eg# d'- e-' f g a'} D4~# D E F r B16 trip{c4 d e}"
x <- from_music21(m21)
class(x)
#> [1] "music"     "character"
x
#> <Music string>
#>   Format: space-delimited time
#>   Values: <d_,,f,,>4.. <d_ea_>t8 d_'t8 e_'t8 ft8 gt8 a't8 e_,~4 d,4 e,4 f,4 r4 b,16 ct4 dt4 et4
#> <Music string>
#>   Timesteps: 16 (14 notes, 2 chords)
#>   Octaves: tick
#>   Accidentals: flat
#>   Key signature: c
#>   Time signature: 4/4
#>   Tempo: 2 = 60
#>   Lyrics: NA
#>   Format: space-delimited time
#>   Values: <d_,,f,,>4.. <d_ea_>t8 d_'t8 e_'t8 ft8 gt8 a't8 e_,~4 d,4 e,4 f,4 r4 b,16 ct4 dt4 et4

y <- to_tabr(id = "music21", x = m21)
identical(x, y)
#> [1] TRUE

from_music21(m21, output = "list") # same as music_split(x)
#> $notes
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: <d_,,f,,> <d_ea_> d_' e_' f g a' e_,~ d, e, f, r b, c d e
#> 
#> $info
#> <Note info string>
#>   Format: space-delimited time
#>   Values: 4.. t8 t8 t8 t8 t8 t8 4 4 4 4 4 16 t4 t4 t4
#> 
#> $lyrics
#> [1] NA
#> 
#> $key
#> [1] "c"
#> 
#> $time
#> [1] "4/4"
#> 
#> $tempo
#> [1] "2 = 60"

Different syntax converters not only handle different input structures, but due to the different nature of the information they contain, their outputs are different as well. See the to_tabr() documentation for more details on converters and their available arguments.

chorrrds package output (Cifraclub)

Turn to output from the chorrrds package. This is much simpler than music21 notation, but this does not mean that little can be done with it after conversion. As mentioned, chord symbols are far from complete information for some purposes, but all is not lost. from_chorrrds() will convert the available data to tabr syntax and can even enhance the information with your guidance.

Consider some additional limitations beyond not being as detailed as music21 notation. The input in this case also does not specify distinct pitches by assigning octaves numbers to the notes in a chord, not even the root note. By default, every chord is positioned to start with its root note in octave three. This may not correctly represent how the chord is played in the song in question, but assumptions must be made.

Since all that is provided is a generic chord symbol, it is also ambiguous how the chord is constructed in general, not just how high or low it sounds. By default a standard chord (i.e., piano chord) is constructed if it can be determined from the symbol.

Default behavior

In this example, a series of chords starting with a B flat are converted. They appear in notation format provided by chorrrds. The last two chords are intentionally given alternate bass notes, one being the same as highest note in the chord, and one not. You can see that to_tabr() takes an id that is the suffix of the from_* function. The result is the same.

chords <- c("Bb", "Bbm", "Bbm7", "Bbm7(b5)", "Bb7(#5)/G", "Bb7(#5)/Ab")
x <- from_chorrrds(chords)
x
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: <b_d'f'> <b_d_'f'> <b_d_'f'a_'> <b_d_'e'a_'> <gb_d'g_'a_'> <a_b_d'g_'>

y <- to_tabr("chorrrds", x = chords)
identical(x, y)
#> [1] TRUE

The result is a noteworthy string containing explicit pitches, useful throughout tabr.

Notice that a particular rule for handling the alternate bass is that if it does not match the top of the chord, it is prepended as a new bass note. If it does match, the top note is dropped, meaning the chord is simply inverted. The less information given, the more that must be assumed.

Prefer those B flats to start on octave two? Want only simple triads? Change to sharps? Summarize the data? No problem. The functions of tabr of ready to operate.

x <- transpose(x, -12) |> chord_slice(1:3) |> sharpen_flat()
tally_pitches(x)
#> # A tibble: 7 × 2
#>   pitch     n
#>   <chr> <int>
#> 1 g,        1
#> 2 g#,       1
#> 3 a#,       6
#> 4 c#        3
#> 5 d         3
#> 6 e         1
#> 7 f         3
#> [1] 43 44 46 49 50 52 53

Guitar chords

Since chorrrds deals specifically in chords, from_chorrrds() offers an argument for specifically selecting common guitar chords shapes instead of a standard chord. Setting guitar = TRUE switches to using the guitarChords dataset to find matching guitar chords using the gc_info() function. This argument would not necessarily apply or be available for some other syntax converter.

This is a nice feature because gc_info() takes several arguments for filtering the numerous ways a single chord can be played on a guitar. This helps you constrain and specify, at least as part of an initial pass with a broad brush, how these chord symbols from chorrrds should be interpreted. Arguments to gc_chords() for setting filter conditions on the guitarChords dataset can be passed as a named list to gc_args() so that you have more control over the conversion of a chord symbol to specific pitches.

from_chorrrds(chords, guitar = TRUE)
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: <b_,fb_d'f'> <b_,fb_d_'f'> <b_,fa_d_'f'> <b_,a_d_'e'> <b_,g_a_d'> <b_,g_a_d'>

One limitation currently is that this method is mapping to a database of guitar chords, which currently does not handle inclusion of arbitrary alternate bass notes. These notes are dropped.

It is helpful to be familiar with the guitarChords dataset and how the filter arguments for the various gc_* functions work so that you have a sense for how to better control the chord specification. If filters do not yield a single unique chord, the first in the list of possible matches is returned. This is typically the lowest on the guitar neck. If filters are too specific or a chord is simply not available, NA is returned.

Example usage

Below is a more typical example. The chord symbols are converted to a tibble data frame for tidy music analysis.

Additionally, the chord symbols alone do not provide anything worth making sheet music from, but you can make a chord chart.

Ready for analysis

chords <- c("Am", "C", "D", "F", "Am", "E", "Am", "E")
x <- from_chorrrds(chords, guitar = TRUE)

as_music_df(x)
#> # A tibble: 8 × 8
#>   duration pitch      note    semitone octave  freq pitch_int scale_int
#>   <chr>    <chr>      <chr>      <int>  <int> <dbl>     <int> <chr>    
#> 1 NA       a,eac'e'   aeace         57      2 110.         NA NA       
#> 2 NA       cgc'e'g'   cgceg         48      3 131.          3 m3       
#> 3 NA       dad'g_'    dadg_         50      3 147.          2 M2       
#> 4 NA       f,cfac'f'  fcfacf        53      2  87.3        -9 M6       
#> 5 NA       a,eac'e'   aeace         57      2 110.          4 M3       
#> 6 NA       e,b,ea_be' ebea_be       52      2  82.4        -5 P4       
#> 7 NA       a,eac'e'   aeace         57      2 110.          5 P4       
#> 8 NA       e,b,ea_be' ebea_be       52      2  82.4        -5 P4

Say also that you knew that these chords were all strummed four times each, four quarter notes per measure. You can expand the series and add the time information as well.

x <- rep(x, each = 4)
time <- rep(4, length(x))

mdf <- as_music_df(x, time, key = "am")
mdf
#> # A tibble: 32 × 17
#>    duration pitch    note  semitone octave  freq key   scale scale_deg pitch_int
#>    <chr>    <chr>    <chr>    <int>  <int> <dbl> <chr> <chr>     <int>     <int>
#>  1 4        a,eac'e' aeace       57      2  110. am    diat…         3        NA
#>  2 4        a,eac'e' aeace       57      2  110. am    diat…         3         0
#>  3 4        a,eac'e' aeace       57      2  110. am    diat…         3         0
#>  4 4        a,eac'e' aeace       57      2  110. am    diat…         3         0
#>  5 4        cgc'e'g' cgceg       48      3  131. am    diat…         3         3
#>  6 4        cgc'e'g' cgceg       48      3  131. am    diat…         3         0
#>  7 4        cgc'e'g' cgceg       48      3  131. am    diat…         3         0
#>  8 4        cgc'e'g' cgceg       48      3  131. am    diat…         3         0
#>  9 4        dad'g_'  dadg_       50      3  147. am    diat…         4         2
#> 10 4        dad'g_'  dadg_       50      3  147. am    diat…         4         0
#> # ℹ 22 more rows
#> # ℹ 7 more variables: scale_int <chr>, slur <chr>, slide <lgl>, bend <lgl>,
#> #   dotted <int>, articulation <chr>, annotation <chr>

The functions available in tabr for manipulating noteworthy strings can be applied similarly to data frame columns for general music data analysis.

Also, notice the generic primitives length() and rep() have been implemented for tabr classes. See help("tabr-methods") for more information on the available methods.

Visual representation

This vignette is part of the music programming and music data analysis vignette collection, but for completeness, the example below shows how you can connect this to LilyPond. Note that for purposes of this example, this last step requires a LilyPond installation, though you could also do this with ggplot (see this vignette for examples).

The fretboard diagram syntax and chord names for creating the chord chart with LilyPond can be obtained directly from the pitches in a chord, as long as those pitches match a known chord in guitarChords. Since the goal here is to make a chord chart of playable chords, consider chord shape and position on the guitar neck. The pitches themselves obviously do not contain any instrument-specific information.

You can use gc_args to specify common playable chords such as open position and other nearby low-fret chord shapes. In this example this is almost not necessary at all. However, say you want to ensure that the C chord is the open position chord rather than the barred shape that occurred above (both chord shapes beginning on string 5, fret 3 in standard tuning). You can stipulate that the min_fret for all chords must range from 0:1.

chords <- unique(chords)
x <- from_chorrrds(chords, guitar = TRUE, gc_args = list(min_fret = 0:1))
x
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: <a,eac'e'> <cegc'e'> <dad'g_'> <f,cfac'f'> <e,b,ea_be'>

gc_is_known(x) # Are chords available with these exact pitch sequences?
#> [1] TRUE TRUE TRUE TRUE TRUE
y <- gc_notes_to_fb(x)
y
#>           a,:m            c:5            d:5           f,:5           e,:5 
#> "x;o;2;2;1;o;" "x;3;2;o;1;o;" "x;x;o;2;3;2;" "1;3;3;2;1;1;" "o;2;2;1;o;o;"

When the chords are ready, all that is needed for rendering is LilyPond integration and the following command. Since there are only a few chords, increase the font size to fill out the page.

out <- "House of the rising sun - chords.pdf"
render_chordchart(y, out, fontsize = 80)

Closing example

You can merge instrument data with the music data. The mdf data frame above contains the barred C chord, so min_fret is not used here to derive fret data.

For fun, define a new function to parse the fret data and calculate a new variable giving the physical fret span needed for your fretting hand, excluding the open fret of course.

library(dplyr)

fret_span <- function(x){
  f <- function(x) strsplit(x, ";") |> unlist() |> as.integer() |> 
    range(na.rm = TRUE) |> diff()
  suppressWarnings(sapply(x, f) + 1L) # coercing string to NA integer
}

mutate(mdf, frets = gc_notes_to_fb(pitch), fret_span = fret_span(frets)) |>
  select(duration, pitch, frets, fret_span)
#> # A tibble: 32 × 4
#>    duration pitch    frets        fret_span
#>    <chr>    <chr>    <chr>            <int>
#>  1 4        a,eac'e' x;o;2;2;1;o;         2
#>  2 4        a,eac'e' x;o;2;2;1;o;         2
#>  3 4        a,eac'e' x;o;2;2;1;o;         2
#>  4 4        a,eac'e' x;o;2;2;1;o;         2
#>  5 4        cgc'e'g' x;3;5;5;5;3;         3
#>  6 4        cgc'e'g' x;3;5;5;5;3;         3
#>  7 4        cgc'e'g' x;3;5;5;5;3;         3
#>  8 4        cgc'e'g' x;3;5;5;5;3;         3
#>  9 4        dad'g_'  x;x;o;2;3;2;         2
#> 10 4        dad'g_'  x;x;o;2;3;2;         2
#> # ℹ 22 more rows

Wrapping up

The examples above for output from the chorrrds package demonstrate that there is plenty that can still be achieved with no more information than basic chord symbols.

Eventually tabr will include more syntax converters. Depending on the quality and completeness of the music data and format in question, different conversion options and analysis possibilities will be available.