Programming with musical scales can be assisted by a collection of
functions in tabr
that check and manipulate musical scales,
modes and key signatures.
For key signatures, keys
lists valid key signature
abbreviation strings for each key as used in tabr
. There
are several key_is_*
functions that return a logical result
regarding properties of keys:
There are also the functions, key_n_flats()
and
key_n_sharps()
, that give the number of respective
accidentals in a key signature.
keys()
#> [1] "c" "g" "d" "a" "e" "b" "f#" "c#" "f" "b_" "e_" "a_"
#> [13] "d_" "g_" "c_" "am" "em" "bm" "f#m" "c#m" "g#m" "d#m" "a#m" "dm"
#> [25] "gm" "cm" "fm" "b_m" "e_m" "a_m"
key_is_flat("f")
#> [1] TRUE
key_n_flats("f")
#> [1] 1
The previous section gave an overview of noteworthy strings. While
some of the functions that help enforce proper notation in R seemed like
they did not offer much utility in terms of direct use, it was clear
that they were integral to the robustness of other tabr
functions. Now, looking at these key signature helpers, it may be
tempting to dismiss their utility even more quickly because a trained
musician does not need to invoke them at the command line to know what
result they will return.
These functions are not provided to answer basic questions in an
interactive R session so much as they are for programming. These are
some of the initial building blocks on top of which more complex
functions are built, including many functions in tabr
. As
the collection of music programming helper functions in
tabr
grows, it becomes easy to do more with less.
Several predefined musical scales are provided and accessible by
calling various scale_*
functions.
scale_hungarian_minor(key = "am", collapse = TRUE)
#> <Noteworthy string>
#> Format: space-delimited time
#> Values: a, b, c d# e f g#
You can specify whether the vector result should be returned as is,
for convenient vectorized programming pipelines, or collapsed to a
single string in keeping with the space-delimited time syntax format
common throughout tabr
. Many functions in tabr
accept both formats as inputs and/or offer them as outputs.
You can also specify if octave numbering should be included or
stripped. Octave numbering is included by default because this maintains
pitch order when the scale does not start on C
. Every note
in a noteworthy string has an implicit octave-3 (the octave below middle
C, C4
) position if not explicitly stated. Octave numbering
attempts to be somewhat balanced around C3
. If the result
is not what is desired, it can be shifted by 12 semitones with
transpose()
.
scale_major("f", TRUE, ignore_octave = TRUE)
#> <Noteworthy string>
#> Format: space-delimited time
#> Values: f g a b_ c d e
scale_major("f", TRUE, ignore_octave = FALSE)
#> <Noteworthy string>
#> Format: space-delimited time
#> Values: f g a b_ c' d' e'
See help("scale-helpers")
for details. Depending on the
scale, other arguments are available.
The seven modern modes are also available with mode_*
functions or through modern_mode()
by passing the mode
string name. Other functions include is_mode
and
rotate_mode
.
mode_ionian()
mode_dorian()
mode_phrygian()
mode_lydian()
mode_mixolydian()
mode_aeolian()
mode_locrian()
modes()
#> [1] "ionian" "dorian" "phrygian" "lydian" "mixolydian"
#> [6] "aeolian" "locrian"
mode_aeolian("c")
#> <Noteworthy string>
#> Format: vectorized time
#> Values: c d e_ f g a_ b_
The scale_chords()
function provides a list of diatonic
scale chords based on the root note and scale chosen. It returns triads
by default and can also return seventh chords.
scale_chords("b_", "major", "seventh", collapse = TRUE)
#> <Noteworthy string>
#> Format: space-delimited time
#> Values: <b_,dfa> <ce_gb_> <dfac'> <e_gb_d'> <fac'e_'> <gb_d'f'> <ac'e_'g'>
scale_chords("f#", "minor", "triad", collapse = TRUE)
#> <Noteworthy string>
#> Format: space-delimited time
#> Values: <f#ac#'> <g#bd'> <ac#'e'> <bd'f#'> <c#'e'g#'> <d'f#'a'> <e'g#'b'>
The functions scale_note()
and
scale_degree()
map between notes and degree in a given
scale. For chords in a noteworthy string, only the root note is
considered. For scale_degree()
, if a note is not diatonic
to the scale, NA
is returned. NA
is also used
when rests occur. Octaves are ignored. For scale_note()
,
degrees outside the range of the scale are recycled. See below. To see
if chords are fully diatonic, use is_diatonic()
or the more
general is_in_scale()
. chord_degree()
will
return a list comparable to scale_degree()
.
x <- "c e gb'd'"
scale_degree(x)
#> [1] 1 3 2
scale_degree(x, key = "a")
#> [1] NA 5 4
scale_degree(x, key = "am")
#> [1] 3 5 4
scale_degree(x, scale = "chromatic")
#> [1] 1 5 3
scale_note(1:7, "d")
#> <Noteworthy string>
#> Format: vectorized time
#> Values: d e f# g a b c#
scale_note(c(1:8), "dm", "harmonic minor")
#> <Noteworthy string>
#> Format: vectorized time
#> Values: d e f g a b_ d_ d
note_in_scale("a_ g#", "a_", strict_accidentals = FALSE)
#> [1] TRUE TRUE
x <- "r d dfa df#a f#ac#"
chord_degree(x, "d")
#> [[1]]
#> [1] NA
#>
#> [[2]]
#> [1] 1
#>
#> [[3]]
#> [1] 1 NA 5
#>
#> [[4]]
#> [1] 1 3 5
#>
#> [[5]]
#> [1] 3 5 7
is_in_scale(x, "d")
#> [1] NA TRUE FALSE TRUE TRUE
is_diatonic(x, "d")
#> [1] NA TRUE FALSE TRUE TRUE
Other functions in tabr
also work with scales and some
build upon functions introduced here.
Musical intervals can be referenced by a name or a numeric value
defining the separation of two notes. interval_semitones()
returns a positive integer describing the number of semitones spanned by
a common interval. It takes a name or abbreviation of a common interval
as input. Essentially, any entry from any other column in the
mainIntervals
dataset can be used to obtain the interval in
semitones. It’s a simple filter and match, but it is convenient to map
between different representations of the same property in
tabr
without typing the extra bit of code to do so each
time.
mainIntervals
#> # A tibble: 26 × 5
#> semitones mmp mmp_abb ad ad_abb
#> <int> <chr> <chr> <chr> <chr>
#> 1 0 perfect unison P1 diminished second d2
#> 2 1 minor second m2 augmented unison A1
#> 3 2 major second M2 diminished third d3
#> 4 3 minor third m3 augmented second A2
#> 5 4 major third M3 diminished fourth d4
#> 6 5 perfect fourth P4 augmented third A3
#> 7 6 tritone TT diminished fifth/augmented fourth d5/A4
#> 8 7 perfect fifth P5 diminished sixth d6
#> 9 8 minor sixth m6 augmented fifth A5
#> 10 9 major sixth M6 diminished seventh d7
#> # ℹ 16 more rows
interval_semitones(c("m3", "M7"))
#> [1] 3 11
Likely more useful for programming, the function
pitch_interval()
provides the number of semitones between
two input notes. This function does not relate to specific scales, but
is worth mentioning on the topic of interval helpers. It provides both
magnitude and direction. The result is negative if the first note is of
higher pitch than the second. It is vectorized and both inputs must have
the same number of timesteps.
Chords can be reduced to their root note (lowest pitch) for
comparison or forced to yield an NA
interval with respect
to its two adjacent timesteps (e.g., c
to ceg
is NA
and so is ceg
to c
). Rests
r
and silent rests s
also yield an
NA
for the interval from a prior note.
pitch_interval("a2", "c")
#> [1] 3
pitch_interval("c d e", "c c c")
#> [1] 0 -2 -4
pitch_interval("r c ceg c e g s", "a c d d f# a e")
#> [1] NA 0 2 2 2 2 NA
pitch_interval("r c ceg c e g s", "a c d d f# a e", use_root = FALSE)
#> [1] NA 0 NA 2 2 2 NA
Next is scale_interval()
. This function is similar to
pitch_interval()
in that it takes two noteworthy strings,
which together define an intervals element-wise. It is almost an inverse
of interval_semitones()
except that you provide notes
rather than the semitone distance of their intervals. The function
returns a main interval name or abbreviation from
mainIntervals
, depending on format
. The
results are name-only. They are not signed. Use
pitch_interval()
to obtain direction.
scale_interval("c c c c", "c, e g b")
#> [1] "P8" "M3" "P5" "M7"
scale_interval("a2", "c", format = "mmp")
#> [1] "minor third"
There are also lagged difference versions of these functions. You can
adjust the lag with n
. You can also retain (convenient for
data frames) or trim the n
leading NA
s. This
does not trim meaningful NA
s like those resulting from
rests.
pitch_diff("c d e f g a b")
#> [1] NA 2 2 1 2 2 2
pitch_diff("c d e f g a b", trim = TRUE)
#> [1] 2 2 1 2 2 2
scale_diff("c d e f g a b")
#> [1] NA "M2" "M2" "m2" "M2" "M2" "M2"
scale_diff("c d e f g a b", n = 2)
#> [1] NA NA "M3" "m3" "m3" "M3" "M3"
Lagged intervals respect rest timesteps. All timestep positions
including rests are retained, but the lag-n
difference
computation ignores them.
x <- "a, c r r r r g"
pitch_diff(x)
#> [1] NA 3 NA NA NA NA 7
scale_diff(x)
#> [1] NA "m3" NA NA NA NA "P5"
pitch_diff(x, n = 2)
#> [1] NA NA NA NA NA NA 10
scale_diff(x, n = 2, trim = TRUE)
#> [1] NA NA NA NA "m7"