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.

Noteworthy strings

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.

Checking noteworthiness

A string can be checked directly with noteworthy().

x <- "a, r b,*2 ce_g cd#g"
noteworthy(x)
#> [1] TRUE
#> [1] FALSE

This reports whether the entire string conforms to all requirements for it to be valid noteworthy syntax.

The noteworthy class

Functions 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.

x <- "a# b_*2 c, d'' e3*2 g_4 c2e_2g2*2"
x <- as_noteworthy(x)
x
#> <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.

x <- as_noteworthy(x, format = "vector", octaves = "tick", accidentals = "flat")
x
#> <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.

Stricter note and chord validation

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.

x <- "a, r b,*2 ce_g cd#g HELLO_WORLD"
is_note(x)
#> [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.

Notable phrases

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.

p1 <- phrase("b, c d ec'g'~ ec'g'", "4( 4)- 2*3", "5*3 432*2")
p1
#> <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

x <- as.character(p1)
phrasey(x)
#> [1] TRUE
#> [1] TRUE

notable(p1) # safe logical check
#> [1] TRUE
notify(p1)
#> # 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

p2 <- p(phrase_notes(p1), phrase_info(p1), phrase_strings(p1))
identical(p1, p2)
#> [1] TRUE

With an understanding noteworthy strings, the next section covers a number of functions related to programming around musical scales.