tabr
provides a music notation syntax system represented
in R code and a collection of music programming functions for
generating, manipulating, organizing and analyzing musical information
structures in R. While other packages focus on working with acoustic
data more generally, tabr
provides a framework for creating
and working with musical data in a common music notation format. The
package offers functions for mapping between pitch and other quantities
like frequency and wavelength, but the main purpose is to manipulate
musical information in notation form. Music programming in the notation
syntax provided by tabr
can be used for a variety of
purposes, but it also integrates cohesively with the package’s
transcription functions.
Before exploring many of the music programming functions in the
package, it is worth introducing an important concept that is expressed
throughout tabr
: noteworthiness. Prior to construction of
phrase
class objects that can be passed to LilyPond for
sheet music engraving, you start out with simple character strings.
These strings contain letters representing musical notes, and possibly
some other characters that indicate such things as sharps and flats,
octave number, and rests.
There are a number of requirements strings must meet to have valid
tabr
music notation syntax that can be transformed
meaningfully and unambiguously into LilyPond syntax. A string is
considered noteworthy if it meets all of these requirements
that differentiate it from arbitrary character strings.
It is important to be familiar with the requirements of a noteworthy
string so that you can construct them properly. There is also the
noteworthy
class object built upon strings that have valid
music notation syntax.
A string can be checked directly with noteworthy()
.
#> [1] TRUE
#> [1] FALSE
This reports whether the entire string conforms to all requirements
for it to be valid noteworthy
syntax.
noteworthy
classFunctions in tabr
that take noteworthy strings as input
perform internal checks of noteworthiness for you and will throw an
error if you provide an unworthy string. These checks are skipped if the
input already has the noteworthy
class. If a function
returns a noteworthy string, the class will also be
noteworthy
.
It is important to understand that the notion of noteworthy strings
is implemented throughout tabr
. One purpose is thorough and
strict input validation. This leads to more robust function behavior and
consistent user programming experience by rejecting problematic string
input early.
as_noteworthy()
can be used to coerce to the
noteworthy
class. Coercion will fail if the string is not
noteworthy. The class offers its own print
and
summary
methods for noteworthy strings as well as a number
of other generic method implementations. While some functions are
intended to aggressively interpret a character string as noteworthy,
performing the check and coercion directly, many functions in the
package require an explicit noteworthy
object. This is
especially true of any generics, which dispatch methods based on the
class of the input object.
Functions like as_noteworthy()
have the added benefit of
conforming all notes to the same formatting. This will clean up any
presence of combined flats and sharps and combined tick and integer
octave numbering.
#> <Noteworthy string>
#> Format: space-delimited time
#> Values: b_ b_ b_ c, d'' e e g_' <c,e_,g,> <c,e_,g,>
#> <Noteworthy string>
#> Timesteps: 10 (8 notes, 2 chords)
#> Octaves: tick
#> Accidentals: flat
#> Format: space-delimited time
#> Values: b_ b_ b_ c, d'' e e g_' <c,e_,g,> <c,e_,g,>
When coercing a noteworthy character string to one that has the
noteworthy
class, formatting is inferred from the
notes
input. Precedence is given to ticks for octave
numbering and flats for accidentals. You can specify formatting
attributes by providing explicit arguments. This is useful for coercing
to another format.
#> <Noteworthy string>
#> Format: vectorized time
#> Values: b_ b_ b_ c, d'' e e g_' <c,e_,g,> <c,e_,g,>
#> <Noteworthy string>
#> Timesteps: 10 (8 notes, 2 chords)
#> Octaves: tick
#> Accidentals: flat
#> Format: vectorized time
#> Values: b_ b_ b_ c, d'' e e g_' <c,e_,g,> <c,e_,g,>
Some functions like transpose()
also expose these
formatting arguments directly. For other functions, format is strictly
inferred from the input notes, but you can always coerce to another
format with as_noteworthy()
.
As an aside, tick octave notation is not the default just because it
is used by LilyPond. The vignettes on transcription show that conversion
from integers to ticks for the phrase
objects used in
transcription is automatic. Another reason to use the tick format
regardless is that it is more robust. In tabr
, integer
octaves cannot go below zero without leading to problematic syntax, but
you can always add more commas as octaves decrease. An even more
critical reason is that the music
class, which assembles a
noteworthy
class object and a noteinfo
class
object together, strictly uses tick format for octave numbering because
numbers are needed to unambiguously describe time juxtaposed with octave
numbering.
noteworthy()
is built upon the more specific, vectorized
functions is_note()
and is_chord()
, which
provide more detailed information on the space-delimited entries in a
string. is_note()
and is_chord()
return a
logical vector reporting whether each entry contains a valid note or
valid chord representation, respectively.
Notice how the vectorized results account for the expansion operator
in b,*2
.
#> [1] TRUE TRUE TRUE TRUE FALSE FALSE FALSE
#> [1] FALSE FALSE FALSE FALSE TRUE TRUE FALSE
Functions like these serve simple purposes that you are unlikely to use them when writing sheet music. However, they can be highly useful in music data analysis, particularly when building more complex musical manipulations on top of simpler functions.
Before moving on, briefly consider the quasi-counterpart to
noteworthy strings: notable phrases. If you have used tabr
,
you are familiar with turning strings into phrases, assembling these
phrases into tracks and scores, and sending them on to LilyPond to
create sheet music. In this context, you might think of phrase objects
as the fundamental unit of musical information and the strings from
which you create them seem more like raw data.
tabr
offers some ability to reverse direction and
decompose phrases back into their component parts: notes
,
info
and string
character strings. This is
done using notify()
, which returns a tibble data frame.
For complex phrases, this can be challenging. There should be no
expectation of true one to one functional transformation. For example,
notify()
is not complex enough to handle unfolding repeat
sections or text notations attached to notes inside phrases. Certainly,
this will not work for LilyPond syntax that was originally created in
LilyPond rather than with tabr
because tabr
only provides access to a tiny fraction of what LilyPond can do. But in
many simpler cases, you can successfully invert a phrase previously
created from strings in R. Such a phrase is considered
notable.
phrasey()
can be used to check if a string at least
loosely resembles the content of a valid phrase object. Additional
related functions are shown below, which take a phrase through a
complete cycle of deconstruction and reconstruction.
#> <Musical phrase>
#> <b,\5>4( <c\5>4)\glissando <d\5>2 <e~\4 c'~\3 g'~\2>2 <e\4 c'\3 g'\2>2
#> [1] TRUE
#> [1] TRUE
#> [1] TRUE
#> # A tibble: 5 × 3
#> notes info string
#> <chr> <chr> <chr>
#> 1 b, 4( 5
#> 2 c 4)- 5
#> 3 d 2 5
#> 4 e~c'~g'~ 2 432
#> 5 ec'g' 2 432
#> [1] TRUE
With an understanding noteworthy strings, the next section covers a number of functions related to programming around musical scales.