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.
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.
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.
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
summary(x)
#> <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
#> [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.
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.
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_'>
#> [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
distinct_pitches(x) |> pitch_semitones()
#> [1] 43 44 46 49 50 52 53
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.
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.
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.
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)
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
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.