Chord functions

Chord identity and comparison

You have already seen is_chord(), which is similar to is_note(). Another check you have seen is is_diatonic(). You can also check whether a chords are major or minor, but this is imperfect due to the inability to know if the user is interpreting their notation of a chord as an inversion. Instances where it is too difficult to tell, or inapplicable such as with single notes, return NA.

x <- "b c ce_g cd#g"
is_diatonic(x, key = "b_")
#> [1] FALSE  TRUE  TRUE FALSE
chord_is_major(x)
#> [1]    NA    NA FALSE FALSE
chord_is_minor(x)
#> [1]   NA   NA TRUE TRUE

A few functions that compare chords are chord_rank(), chord_order() and chord_sort(). Ranking chords, and the ordering and sorting based on that, requires a definition or set of definitions to work from.

The first argument is a noteworthy string. The second, pitch, can be "min" (the default), "mean", or "max". Each of these refers to the functions that operate on the three available definitions of ranking chords. When ranking individual notes, the result is fixed because there are only two pitches being compared. For chords, however, pitch = "min" compares only the lowest pitch or root note of a chord. For pitch = "max", the highest pitch note in each chord is used for establishing rank. For pitch = "mean", the average of all notes in the chord are used for ranking chords.

Rank is from lowest to highest pitch. These options define how chords are ranked, but each function below also passes on additional arguments via ... to the base functions rank() and order() for the additional control over the more general aspects of how ranking and ordering are done in R. chord_order() works analogously to chord_rank(). chord_sort() wraps around chord_order().

x <- "a2 c a2 ceg ce_g cea"
chord_rank(x, "min")
#> [1] 1.5 4.5 1.5 4.5 4.5 4.5
chord_rank(x, "max")
#> [1] 1.5 3.0 1.5 4.5 4.5 6.0
chord_rank(x, "mean")
#> [1] 1.5 3.0 1.5 5.0 4.0 6.0

chord_order(x)
#> [1] 1 3 2 4 5 6
chord_order(x, "mean")
#> [1] 1 3 2 5 4 6

chord_sort(x, "mean")
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: a2 a2 c <ce_g> <ceg> <cea>

Slice and filter chords

Chords can be sliced or indexed using the functions chord_root(), chord_top() and chord_slice(). The first two are special cases of chord_slice. The first two functions return a noteworthy string containing only the root or top notes of each chord. If the string contains a single note, by definition the note is returned.

For chord_slice(), however, an integer index range is provided and it is possible to reduce a note or chord to nothing by passing indices that are completely out of bounds. Any note or chord that is completely sliced away is dropped.

The example below also shows that what matters is pitch order, not the order in which notes in a chord are entered in the string.

x <- "a2 ceg e_gc egc,cc'"
chord_root(x)
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: a, c c c,
chord_top(x)
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: a, g g c'
identical(chord_slice(x, 1), chord_root(x))
#> [1] TRUE

chord_slice(x, 2)
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: e e_ c
chord_slice(x, 4)
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: g
chord_slice(x, 3:5)
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: g g <egc'>

The slicing functions deal with position within a chord; they are not a simple reproduction of vector indexing of time steps, which is trivial and can already be done with note_slice() (clearly not slicing a single note, but a noteworthy string). Filtering the sequence rather than the elements within it is best done by taking the results of a function that returns a logical vector and passing them to note_slice(). This tends to fall under the topic of general noteworthy string functions and does not apply strictly to chords, but an example is shown here.

x <- "a2 ceg e_gc egc,cc'"
note_slice(x, 3:4)
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: <e_gc> <egc,cc'>
note_slice(x, is_chord(x))
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: <ceg> <e_gc> <egc,cc'>

Chord transformations

A broken chord can be created with chord_break(), which separates a chord into its component notes, separating in time. It accepts a single chord.

x <- "ce_g"
chord_break(x)
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: c e_ g

chord_invert() creates chord inversions. It also takes a single chord as input. It treats any chord as being in root position as provided. The example below applies the function over a series of inversion values to show how the output changes.

pc(sapply((-3):3, function(i) chord_invert(x, i)))
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: <c2e_2g2> <e_2g2c> <g2ce_> <ce_g> <e_gc4> <gc4e_4> <c4e_4g4>

While a chord with n notes has n - 1 inversions, chord_invert() allows inversions to continue, moving a chord further up or down in octaves. If you want to restrict the function to only allowing the defined number of inversions (excluding root position), set limit = TRUE. This enforces the rule that, for example, a chord with three notes has two inversions and n can only take values between -2 and 2 or it will throw and error.

Building up on chord_invert(), chord_arpeggiate() grows a chord up or down the scale in pitch by creating an arpeggio. n describes how many steps to add onto the original chord. Setting by = "chord" will replicate the entire chord as is, up or down the scale. In this case n indicates whole octave transposition steps. By default, n refers to the number of steps that individual chord notes are arpeggiated, like in chord_invert(). This means for example that in a chord with three notes, setting n = 3 and by = "note" is equivalent to setting n = 1 and by = "chord".

The argument broken = TRUE will also convert to a broken chord, resulting in an arpeggio of individual notes.

chord_arpeggiate("ce_gb_", 2)
#> <Noteworthy string>
#>   Format: vectorized time
#>   Values: <ce_gb_> <e_gb_c4> <gb_c4e_4>
chord_arpeggiate("ce_gb_", -2)
#> <Noteworthy string>
#>   Format: vectorized time
#>   Values: <ce_gb_> <b_2ce_g> <g2b_2ce_>
chord_arpeggiate("ce_gb_", 2, by = "chord")
#> <Noteworthy string>
#>   Format: vectorized time
#>   Values: <ce_gb_> <c4e_4g4b_4> <c5e_5g5b_5>
chord_arpeggiate("ce_gb_", 1, broken = TRUE, collapse = TRUE)
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: c e_ g b_ e_ g b_ c4

Dyads

Before introducing the chord constructors, here is a brief mention and example of the dyad() function for constructing dyads from a root note and and interval. Dyads are not always considered chords, but this is as good a place as any to mention dyad() since the key distinction made in tabr in this context is whether there is a single note or multiple notes. The interval passed to dyad() can be in semitones, or a named interval from mainIntervals that corresponds to the number of semitones.

dyad("a", 3)
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: <ac'>
x <- c("minor third", "m3", "augmented second", "A2")
dyad("a", x)
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: <ac'> <ac'> <ac'> <ac'>
dyad("c'", x, reverse = TRUE)
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: <ac'> <ac'> <ac'> <ac'>

Predefined chord constructors

Now to the topic of chord construction, there are two general forms of chord construction currently available in tabr. The first is for typical chords based on their defining intervals; i.e., “piano chords”. These are not particularly useful for guitar-specific chord shapes and fingerings, which generally span a greater pitch range. See further below for guitar chords.

In tabr chords are often constructed from scratch by explicitly typing the chord pitches in a noteworthy string, but many chords can also be constructed using helper functions. Currently, helpers exist for common chords up through thirteenths. tabr offers two options for each chord constructor function name: the longer chord_*-named function and its x* alias. The table below shows all available constructors.

#>        full_name abbreviation
#> 1      chord_min           xm
#> 2      chord_maj           xM
#> 3     chord_min7          xm7
#> 4     chord_dom7           x7
#> 5      chord_7s5         x7s5
#> 6     chord_maj7          xM7
#> 7     chord_min6          xm6
#> 8     chord_maj6          xM6
#> 9      chord_dim         xdim
#> 10    chord_dim7        xdim7
#> 11    chord_m7b5        xm7b5
#> 12     chord_aug         xaug
#> 13       chord_5           x5
#> 14    chord_sus2          xs2
#> 15    chord_sus4          xs4
#> 16    chord_dom9           x9
#> 17     chord_7s9         x7s9
#> 18    chord_maj9          xM9
#> 19    chord_add9        xadd9
#> 20    chord_min9          xm9
#> 21   chord_madd9         xma9
#> 22   chord_min11         xm11
#> 23    chord_7s11        x7s11
#> 24 chord_maj7s11       xM7s11
#> 25      chord_11         x_11
#> 26   chord_maj11         xM11
#> 27      chord_13         x_13
#> 28   chord_min13         xm13
#> 29   chord_maj13         xM13

These functions take root notes and a key signature as input. The given function determines the intervals of the chord. This in combination with a root note is all that is needed to create the chord. However, the key signature can enforce whether the result uses flats or sharps when accidentals are present.

chord_min7("a c e")
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: <ac'e'g'> <ce_gb_> <egbd'>
chord_min7("a c e", key = "f")
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: <ac'e'g'> <ce_gb_> <egbd'>
xm7("a c e", key = "f")
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: <ac'e'g'> <ce_gb_> <egbd'>

Predefined guitar chords

The dataset guitarChords is a tibble containing 3,967 rows of predefined guitar chords. It is highly redundant, but convenient to use. It is generated from a much smaller chords basis set, that is then transposed over all notes, yielding chord types and shapes for all twelve notes. Chords begin from open position and range up one octave to chords whose lowest fret is eleven. There are also multiple chord voicings for many chord types. Finally, chords containing accidentals are included in the table with both the flat and sharp versions.

There are twelve columns. Again, some of the column-wise information is also redundant, but it is not a big deal to include and removes the need to do a variety of computations to map from one representation of chord information to another. Here are the first ten rows:

guitarChords
#> # A tibble: 3,967 × 12
#>    id    lp_name root  octave root_fret min_fret bass_string notes      frets 
#>    <fct> <chr>   <chr>  <dbl>     <dbl>    <dbl>       <int> <chr>      <chr> 
#>  1 M     a,:5    a          2         0        0           5 a,eac#'e'  xo222o
#>  2 M     a,:5    a          2         0        0           5 a,ead_'e'  xo222o
#>  3 m     a,:m    a          2         0        0           5 a,eac'e'   xo221o
#>  4 7     a,:7    a          2         0        0           5 a,egc#'e'  xo2o2o
#>  5 7     a,:7    a          2         0        0           5 a,egd_'e'  xo2o2o
#>  6 M7    a,:maj7 a          2         0        0           5 a,eg#c#'e' xo212o
#>  7 M7    a,:maj7 a          2         0        0           5 a,ea_d_'e' xo212o
#>  8 m7    a,:m7   a          2         0        0           5 a,egc'e'   xo2o1o
#>  9 sus2  a,:sus2 a          2         0        0           5 a,eabe'    xo22oo
#> 10 sus4  a,:sus4 a          2         0        0           5 a,ead'e'   xo223o
#> # ℹ 3,957 more rows
#> # ℹ 3 more variables: semitones <list>, fretboard <chr>, open <lgl>

Defining new guitar chord collections

You can also define your own chords using chord_def(). All you need are the fret numbers for the fretted chord. Currently it is assumed to be a six-string instrument. The default tuning is standard, but this can be changed arbitrarily. NA indicates a muted string. Order is from lowest pitch string to highest. In the example below, create a set of minor chords based on the open Am shape.

frets <- c(NA, 0, 2, 2, 1, 0)
chord_def(frets, "m", 6) # sixth entry (highest string: string #1) is optional
#> # A tibble: 1 × 13
#>   id    lp_name root  octave root_fret min_fret bass_string notes    frets 
#>   <chr> <chr>   <chr>  <int>     <dbl>    <dbl>       <int> <chr>    <chr> 
#> 1 m     a,:m    a          2         0        0           5 a,eac'e' xo221o
#> # ℹ 4 more variables: semitones <list>, optional <chr>, fretboard <chr>,
#> #   open <lgl>

guitarChords does not currently contain the optional column, but this is a column where you can indicate optional chord notes, as shown above.

chord_def() is scalar and defines a single chord, always returning a table with one row, but you can map over it however you need in order to define a collection of chords. Below, a set of chords is generated with sharps and again with flats.

purrr::map_dfr(1:12, ~chord_def(frets + .x, "m"))
#> # A tibble: 12 × 13
#>    id    lp_name root  octave root_fret min_fret bass_string notes         frets
#>    <chr> <chr>   <chr>  <int>     <dbl>    <dbl>       <int> <chr>         <chr>
#>  1 m     b_,:m   b_         2         1        1           5 b_,fb_d_'f'   x133…
#>  2 m     b,:m    b          2         2        2           5 b,g_bd'g_'    x244…
#>  3 m     c:m     c          3         3        3           5 cgc'e_'g'     x355…
#>  4 m     d_:m    d_         3         4        4           5 d_a_d_'e'a_'  x466…
#>  5 m     d:m     d          3         5        5           5 dad'f'a'      x577…
#>  6 m     e_:m    e_         3         6        6           5 e_b_e_'g_'b_' x688…
#>  7 m     e:m     e          3         7        7           5 ebe'g'b'      x799…
#>  8 m     f:m     f          3         8        8           5 fc'f'a_'c''   x8(1…
#>  9 m     g_:m    g_         3         9        9           5 g_d_'g_'a'd_… x9(1…
#> 10 m     g:m     g          3        10       10           5 gd'g'b_'d''   x(10…
#> 11 m     a_:m    a_         3        11       11           5 a_e_'a_'b'e_… x(11…
#> 12 m     a:m     a          3        12       12           5 ae'a'c''e''   x(12…
#> # ℹ 4 more variables: semitones <list>, optional <lgl>, fretboard <chr>,
#> #   open <lgl>
purrr::map_dfr(1:12, ~chord_def(frets + .x, "m", key = "f")) # flats
#> # A tibble: 12 × 13
#>    id    lp_name root  octave root_fret min_fret bass_string notes         frets
#>    <chr> <chr>   <chr>  <int>     <dbl>    <dbl>       <int> <chr>         <chr>
#>  1 m     b_,:m   b_         2         1        1           5 b_,fb_d_'f'   x133…
#>  2 m     b,:m    b          2         2        2           5 b,g_bd'g_'    x244…
#>  3 m     c:m     c          3         3        3           5 cgc'e_'g'     x355…
#>  4 m     d_:m    d_         3         4        4           5 d_a_d_'e'a_'  x466…
#>  5 m     d:m     d          3         5        5           5 dad'f'a'      x577…
#>  6 m     e_:m    e_         3         6        6           5 e_b_e_'g_'b_' x688…
#>  7 m     e:m     e          3         7        7           5 ebe'g'b'      x799…
#>  8 m     f:m     f          3         8        8           5 fc'f'a_'c''   x8(1…
#>  9 m     g_:m    g_         3         9        9           5 g_d_'g_'a'd_… x9(1…
#> 10 m     g:m     g          3        10       10           5 gd'g'b_'d''   x(10…
#> 11 m     a_:m    a_         3        11       11           5 a_e_'a_'b'e_… x(11…
#> 12 m     a:m     a          3        12       12           5 ae'a'c''e''   x(12…
#> # ℹ 4 more variables: semitones <list>, optional <lgl>, fretboard <chr>,
#> #   open <lgl>

Guitar chord construction

The most interesting use of guitarChords is in using it to map from chord names to noteworthy strings.

Chord information

gc_info() can be used to filter guitarChords. In the examples below, you can see that multiple chord names can be supplied at once. All are used to filter the chord dataset. If the inputs do not exist, an empty tibble with zero rows is returned. The result is not vectorized to match the number of entries in the input; it is simply a row filter for guitarChords.

gc_info("a") # a major chord, not a single note
#> # A tibble: 6 × 12
#>   id    lp_name root  octave root_fret min_fret bass_string notes        frets  
#>   <fct> <chr>   <chr>  <dbl>     <dbl>    <dbl>       <int> <chr>        <chr>  
#> 1 M     a,:5    a          2         0        0           5 a,ead_'e'    xo222o 
#> 2 M     a,:5    a          2         5        5           6 a,ead_'e'a'  577655 
#> 3 M     a,:5    a          2         5        2           6 a,d_ead_'a'  542225 
#> 4 M     a:5     a          3         7        7           4 ae'a'd_''    xx79(1…
#> 5 M     a:5     a          3        12       12           5 ae'a'd_''e'' x(12)(…
#> 6 M     a:5     a          3        12        9           5 ad_'e'a'd_'' x(12)(…
#> # ℹ 3 more variables: semitones <list>, fretboard <chr>, open <lgl>
gc_info("ceg a#m7_5") # only third entry is a guitar chord
#> # A tibble: 6 × 12
#>   id    lp_name  root  octave root_fret min_fret bass_string notes        frets 
#>   <fct> <chr>    <chr>  <dbl>     <dbl>    <dbl>       <int> <chr>        <chr> 
#> 1 m7_5  b_,:m7_5 b_         2         1        0           5 b_,a_d_'e'   x1x12o
#> 2 m7_5  b_,:m7_5 b_         2         1        1           5 b_,ea_d_'    x1212x
#> 3 m7_5  b_,:m7_5 b_         2         6        5           6 b_,a_d_'e'   6x665x
#> 4 m7_5  b_:m7_5  b_         3         8        4           4 b_d_'e'a_'   xx8654
#> 5 m7_5  b_:m7_5  b_         3         8        8           4 b_e'a_'d_''  xx8999
#> 6 m7_5  b_:m7_5  b_         3        13       12           5 b_a_'d_''e'' x(13)…
#> # ℹ 3 more variables: semitones <list>, fretboard <chr>, open <lgl>
gc_info("ceg a#m7_5", key = "f")
#> # A tibble: 6 × 12
#>   id    lp_name  root  octave root_fret min_fret bass_string notes        frets 
#>   <fct> <chr>    <chr>  <dbl>     <dbl>    <dbl>       <int> <chr>        <chr> 
#> 1 m7_5  b_,:m7_5 b_         2         1        0           5 b_,a_d_'e'   x1x12o
#> 2 m7_5  b_,:m7_5 b_         2         1        1           5 b_,ea_d_'    x1212x
#> 3 m7_5  b_,:m7_5 b_         2         6        5           6 b_,a_d_'e'   6x665x
#> 4 m7_5  b_:m7_5  b_         3         8        4           4 b_d_'e'a_'   xx8654
#> 5 m7_5  b_:m7_5  b_         3         8        8           4 b_e'a_'d_''  xx8999
#> 6 m7_5  b_:m7_5  b_         3        13       12           5 b_a_'d_''e'' x(13)…
#> # ℹ 3 more variables: semitones <list>, fretboard <chr>, open <lgl>

gc_info("a,m c d f,")
#> # A tibble: 23 × 12
#>    id    lp_name root  octave root_fret min_fret bass_string notes       frets  
#>    <fct> <chr>   <chr>  <dbl>     <dbl>    <dbl>       <int> <chr>       <chr>  
#>  1 m     a,:m    a          2         0        0           5 a,eac'e'    xo221o 
#>  2 m     a,:m    a          2         5        5           6 a,eac'e'a'  577555 
#>  3 m     a,:m    a          2         5        2           6 a,cea       5322xx 
#>  4 m     a:m     a          3         7        7           4 ae'a'c''    xx79(1…
#>  5 m     a:m     a          3        12       12           5 ae'a'c''e'' x(12)(…
#>  6 m     a:m     a          3        12        9           5 ac'e'a'     x(12)(…
#>  7 M     c:5     c          3         3        3           5 cgc'e'g'    x35553 
#>  8 M     c:5     c          3         3        0           5 cegc'e'     x32o1o 
#>  9 M     c:5     c          3         8        8           6 cgc'e'g'c'' 8(10)(…
#> 10 M     c:5     c          3         8        5           6 cegc'e'c''  875558 
#> # ℹ 13 more rows
#> # ℹ 3 more variables: semitones <list>, fretboard <chr>, open <lgl>

The same properties of gc_info() apply to wrapper functions around it, namely, gc_notes() and gc_fretboard().

Map chord names to notes

gc_notes() takes chord names that exist in guitarChords and returns the noteworthy strings needed for phrase construction. Remember, the is just a basic filter. If you specify chords imprecisely, the result will contain many more chords than were in the input. If you specify chords completely unambiguously, then there is one result for each input. However, this also requires you do not provide any chord names that are not in guitarChords, or these will be dropped.

Keep in mind that these functions are under active development and the approaches they take may change prior to the next CRAN release of tabr. Currently, the ways you can add precision to your chord mapping include passing the following optional arguments:

In this example, possible matches from guitarChord are filtered to any whose root fret is in 0:2.

x <- gc_notes("a,7 b,m", root_fret = 0:2, ignore_octave = FALSE)
summary(x)
#> <Noteworthy string>
#>   Timesteps: 2 (0 notes, 2 chords)
#>   Octaves: tick
#>   Accidentals: flat
#>   Format: space-delimited time
#>   Values: <a,egd_'e'> <b,g_bd'g_'>

Notice that the octave information helps further restrict the chord set with ignore_octave = FALSE.

Fretboard diagrams

When creating tablature and sheet music with LilyPond, you may wish to include a chord chart containing fretboard diagrams of the chords as they are played. Currently, tabr uses the chord_set() function to prepare a named character vector of chords that have quasi-LilyPond chord names and fretboard notation values, ready to be passed to score() for proper injection into LilyPond.

This process and the structure of the data objects involved may change soon (I have not decided for certain yet). But for the time being, this is still the process. Therefore, the new function gc_fretboard() performs the same manipulation using chords from guitarChords.

gc_fretboard("a,m c d f,", min_fret = 0:1)
#>           a,:m            c:5            d:5           f,: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;"