Skip to contents

humdrumR includes a easy-to-use but powerful system for parsing pitch information: various basic pitch representations (including numeric and character-string representations) can be "parsed"---read and interpreted by humdrumR. For the most part, parsing automatically happens "behind the scenes" whenever you use any humdrumR pitch function, like kern() semit(), or solfa().

Usage

tonalInterval(...)

# S3 method for tonalInterval
tonalInterval(x, ...)

# S3 method for logical
tonalInterval(x, ...)

# S3 method for `NULL`
tonalInterval(x, ...)

# S3 method for numeric
tonalInterval(x, ..., Exclusive = NULL, multiDispatch = FALSE)

# S3 method for character
tonalInterval(x, ..., Exclusive = NULL, multiDispatch = FALSE)

# S3 method for factor
tonalInterval(x, Exclusive = NULL, ...)

# S3 method for token
tonalInterval(x, Exclusive = NULL, ...)

Arguments

Exclusive

An exclusive interpretation to guide parsing of the input.

Must be NULL, or a character vector which is either length 1 or length(x).

str

The input vector.

Must be either character or numeric.

Key

The diatonic key used to interpret the pitch information.

Defaults to NULL.

Must be a diatonicSet or something coercable to diatonicSet; must be either length 1 or length(x)

For example, use of implicitSpecies (see advanced parsing section) is dependent on the Key. The output tonalInterval is output within the key: thus, tonalInterval('C#', Key = "A:") returns the tint representing a Major 3rd, because C# is the major third of A major.

Value

tonalInterval() returns a tonalInterval object of the same length and dimensions as x. NULL inputs (x argument) return a NULL output. NA values in the input x are propagated to the output.

Details

The underlying parser used by all humdrumR pitch functions can be called explicitly using the function tonalInterval(). The tonalInterval parser will attempt to parse any input information into a tonalInterval object---a back-end pitch representation that you probably don't need to care about! When you use one of the main pitch functions, like kern() or semits(), the input is parsed into a tonalInterval object, then immediately deparsed to the representation you asked for (e.g., **kern or **semits). Thus, the underlying pipeline for humdrumR pitch functions looks something like:

  • Input representation (e.g., **pitch or **semits) |>

    • Parsing (done by tonalInterval()) |>

    • Deparsing |>

  • Output representation (e.g. **kern or **solfa)

This documentation talks about the parsing step. For an overview of the "deparsing" process, look here. To learn about the "deparsing" of specific representations, start here or go straight to the docs for specific functions--- for example, call ?kern to learn about kern().

Dispatch

The pitch parser (tonalInterval()) is a generic function, meaning it accepts a variety of inputs and automatically "dispatches" the appropriate method for parsing ehe input. R's standard S3 system is used to dispatch for either numeric or character-string input: Generally, numeric (or integer) inputs are interpreted as various atonal pitch representations while character strings are interpreted as various tonal pitch representations. Given either a character string or a number, humdrumR then uses either regular-expression matching or humdrum exclusive interpretation matching to dispatch specific parsing methods.

Tonal Parsing (character-string inputs)

Since humdrum data is inherently string-based, the most powerful part of the humdrumR pitch-parser is its system for parsing pitch (mostly tonal) information from character strings. (This includes character tokens with pitch information embedded alongside other information; Details below.) The pitch parser (tonalInterval()) uses a combination of regular-expressions and exclusive interpretations to decide how to parse an input string. There are twelve regular-expression patterns for pitch that tonalInterval() knows how to parse automatically:

RepresentationExclusiveExample
Kern**kernee-
Scientific Pitch**pitchEb5
Helmholtz pitchnoneeb'
Lilypond pitchnoneees'
German pitch notation**TonhS5
Interval**hint/**mint/**int+m3
Scale degree**deg or **degree^^3- or 3-/5
Pitch Class**pc3
Relative-do Solfege**solfa^me
Fixed-do Solfege**solfgmi~b5
Swara**bhatkg

Exclusive Dispatch

If you call tonalInterval() (or any pitch function) on a character-string vector, with a non-NULL Exclusive argument, that Exclusive argument will be used to choose the input interpretation you want, based on the "Exclusive" column in the table above. For example, kern(x, Exclusive = 'solfa') will force the parser to interpret x as **solfa data. Similarly, solfa(x, Exclusive = 'kern') will force the parser to interpret x as **kern data. If you use any pitch function within a special call to withinHumdrum, humdrumR will automatically pass the Exclusive field from the humdrum data to the function---this means, that in most cases, you don't need to explicitly do anything with the Exclusive argument! (If you want this not to happen, you need to explicitly specify your own Exclusive argument, or Exclusive = NULL.)

Regex Dispatch

If you call tonalInterval() (or any pitch function) on a character-string vector, but the Exclusive argument is missing or NULL, humdrumR will instead use regular-expression patterns to select a known interpretation. For example, pitch('so') will automatically recognize that 'so' is solfege, and will interpret the data accordingly (the output should be G4). If there are more than one matches, humdrumR will use the longest match, and if they tie, pick based on the order in the table above (topmost first).

If there is no match, tonalInterval (and all other pitch function) return NA values. Remember, if Exclusive is specified, it overrides the regex-based dispatch, which means that pitch('so', Exclusive = 'kern') will return NA, because 'so' can't be interpreted as a **kern value.

"In place" parsing

In lots of humdrum data, character strings are encoded with multiple pieces of musical information right besides each other: for example, **kern data might include tokens like "4.ee-[. The humdrumR pitch parser (tonalInterval()) will automatically "pull out" pitch information from within strings, if it can find any, using the appropriate known regular expressions. Various pitch parsing functions have an option to keep the original "extra" data, using their inPlace argument.

Advanced Tonal Parsing Options

The eleven tonal representations listed above are parsed through a common intesrface. By using "advanced" parsing arguments, you can tweak how this parsing is done, so as to accommodate even more input representations! This means we are controlling the behavior of tonalInterval(), in the second step of our pipeline:

  • Input representation |>

    • Parsing (done by tonalInterval(PARSE ARGS GO IN HERE!)) |>

    • Deparsing |>

  • Output representation

Note that these arguments are similar or identical to parallel "advanced" deparsing arguments used by various pitch functions. The following "advanced" parsing arguments are available (read all the details about them further down):

  • Steps

    • step.labels

    • step.signed

  • Species (accidentals or qualities)

    • qualities

    • specifier.maximum

    • Accidentals

      • natural, flat, sharp, doubleflat, doublesharp

    • Qualities

      • perfect, major, minor, augment, diminish

    • Implicit vs Explicit Species

      • implicitSpecies

      • absoluteSpecies

      • memory, memoryWindows

  • Octave

    • octave.integer

    • up, down, same

    • octave.offset

    • octave.round

    • octave.relative, octave.absolute

  • String parsing

    • parts

    • sep

These "advanced" arguments can be used directly in any pitch function, or in a call to tonalInterval itself. To use them with tonalInterval just specify them directly as arguments: for example, tonalInterval(x, qualities = TRUE). To use them with other pitch functions, you can either...

  • Put them in the parseArgs argument:

    • kern(x, parseArgs = list(qualities = TRUE))

  • Or use the "syntactic sugar" short-hand form:

    • kern(x, parse(qualities = TRUE))

Each of the known Exclusive/Regex-dispatch combo (see the table above) is associated with default parsing arguments. For example, if you set Exclusive = 'kern' or just use data that look like **kern, the flat argument is set to "-", However, if you had, for example, input data that looked like **kern except it used a different flat symbol, like "_", you could modify the parser: kern("EE_", parse(flat = "_")) This overrides the default value for **kern---notice, that it also updates the **kern regular expression accordingly, so it works exactly the same as the standard kern() function.

Steps

Any representation of "tonal" pitch information will include a representation of diatonic steps. You can control how the parser reads diatonic steps from a pitch representation using the step.labels argument. The step.labels argument must be an atomic vector of unique values, with a length which is a positive multiple of seven. Examples of step.labels arguments that are currently used by preset humdrumR pitch parsers include:

  • parse(step.labels = c('A', 'B', 'C', 'D', 'E', 'F', 'G')) --- (**Tonh)

  • parse(step.labels = c('d', 'r', 'm', 'f', 's', 'l', 't')) --- (**solfa)

  • parse(step.labels = c('I', 'II', 'III', 'IV', 'V', 'VI', 'VII')) --- (roman numerals)

If step.labels is NULL, steps are assumed to be represented by integers, including negative integers representing downward steps.

There is also a step.signed (logical, length == 1) argument: if step.signed = TRUE, lowercase versions of step.labels are interpreted as negative (downward) steps and uppercase versions of step.labels are interpreted as positive (upwards) steps. This option is used, for example, by the default kern() and helmholtz() parsers.

Species

In tonal pitch representations, "specific" versions of tonal pitches---the tonal "species"---are indicated by "specifiers": either accidentals or qualities. The qualities (logical, length == 1) argument indicates whether accidentals are used (qualities = FALSE) or qualities (qualities = TRUE). Some specifiers can be repeated any number of times, like "triple sharps" or "doubly augmented"; The specifier.maximum (integer, length == 1) argument sets a maximum limit on the number of specifiers to read. For example, you could force all triple sharps ("###") or double sharps ("##") to parse as just "#", by specifying specifier.maximum = 1L.

Accidentals

If qualities = FALSE the parser will look for accidentals in the input, recognizing three types: naturals, flats, and sharps. The natural, flat, and/or sharp (character, length == 1) arguments can be used to indicate how accidentals are represented in the input. For example, if the input strings look like c("Eflat", "C"), you could set the argument flat = "flat".

Examples of accidental argument combinations that are currently used by preset humdrumR pitch parsers include:

  • parse(flat = "b", sharp = "#") -> **pitch

  • parse(flat = "-", sharp = "#") -> **kern

  • parse(flat = "-", sharp = "+") -> **degree

The doubleflat, and doublesharp (character, length == 1) arguments are NULL by default, but can be set if a special symbol is used to represent two sharps or flats. For example, you might have an input which represents double sharps as "x". You could call kern("Fx", parse(doublesharp = "x")) and the output will be "F##".

Qualities

If qualities = TRUE the parser will look for qualities in the input, recognizing five types: perfect, minor, major, augmented, and diminished. The perfect, major, minor, diminish, and/or augment (character, length == 1) arguments can be used to indicate how qualities are represented in the input. (Note: we are talking about interval/degree qualities here, not chord qualities!) For example, if the input strings look like c("maj3", "p4"), you could set the arguments major = "maj" and perfect = "p". Examples of quality argument combinations that are currently used by humdrumR pitch functions include:

  • parse(major = "M", minor = "m", perfect = "P", diminish = "d", augment = "A")

  • parse(diminish = "o", augment = "+")

Implicit vs Explicit Species

In some musical data, specifiers (e.g., accidentals or qualities) are not explicitly indicated; instead, you must infer the species of each pitch from the context---like the key signature!.

From the Key

The most important argument here is implicitSpecies (logical, length == 1): if implicitSpecies = TRUE, the species of input without an explicit species indicated is interpreted using the Key. For example,

  • kern('C', Key = 'A:', parse(implicitSpecies = TRUE)) is parsed as "C#"

    • C is sharp in A major.

  • kern('C', Key = 'a:', parse(implicitSpecies = TRUE)) is parsed as "C"

    • C is natural in A minor.

  • kern('C', Key = 'a-:', parse(implicitSpecies = TRUE)) is parsed as "C-"

    • C is flat in A-flat minor.

By default, if you input already has specifiers, they are interpreted absolutely---overriding the "implicit" Key---, even if implicitSpecies = TRUE. Thus, if we are in A major:

  • kern("C#", Key = 'A:', parse(implicitSpecies = TRUE)) is parsed as "C#".

    • The "#" is unnecessary.

  • kern("Cn", Key = 'A:', parse(implicitSpecies = TRUE)) is parsed as "C".

    • The "n" overrides the Key.

  • kern("C#", Key = 'a:', parse(implicitSpecies = TRUE)) is parsed as "C#".

    • The "#" overrides the Key.

However! You can also change this behavior by setting the absoluteSpecies (logical, length == 1) argument to FALSE. If you do so, the specifiers in the input are interpreted "on top of" the key accidental:

  • kern("C#", Key = 'A:', parse(implicitSpecies = TRUE, absoluteSpecies = FALSE)) is parsed as "C##".

    • The "#" from the input is added to the "#" from the Key, resulting in double sharp!

This is an unusual behavior, for absolute pitch representations like **kern. However, for use with scale or chord degrees, absoluteSpecies = FALSE might be appropriate. For example, if we are reading a figured bass in the key of E minor, a "b7" figure above an E in the bass should be interpreted as a double flat (diminished) 7th (Db over E)! If this is how your data is encoded, use absoluteSpecies = FALSE.

Memory

In some musical data, it is assume that a accidental on a note "stays in effect" on that scale step until the next bar, or until a different accidental replaces it. Fortunately, the humdrumR parser (tonalInterval()) also knows how to parse data encoded with "memory" this way. If memory = TRUE, the accidental (or quality) of each input note is "remembered" from previous appearances of that scale step. For example,

  • kern(c("D#", "E", "D", "E", "Dn", "C", "D"), parse(memory = TRUE))

    • is parsed as c("D#", "E", "D#", "E", "D", "C", "D")

If we want the "memory" to only last when specific time windows (like bars), we can also specify a memoryWindows argument. memoryWindows must be an atomic vector which is the same length as the input (x argument). Each unique value within the memoryWindows vector is treated as a "window" within which memory operates. The most common use case would be to pass the Bar field from a humdrumR dataset to memoryWindows!

The memory and memoryWindows argument work whatever values of implicitSpecies or absoluteSpecies are specified! Though all the examples here use accidentals, these arguments all have the same effect if parsing qualities (qualities = TRUE).

Octave

The final piece of information encoded in most (but not) all pitch representations is an indication of the "compound pitch"--- incorporating octave information. In humdrumR octaves are always defined in terms of scale steps: so two notes with the same scale degree/letter name will always be the same octave. This mainly comes up with regards to Cb and B#: Cb4 is a semitone below ; B#3 is enharmonically the same as middle-C.

Integer Octaves

The simplest way octave information can be encoded is as an integer value, as in Scientific Pitch. If you need to parse integer-encoded octaves, set the octave.integer (logical, length == 1) argument to TRUE. By default, humdrumR considers the "central" octave (octave == 0) to be the octave of , or equivalently, a unison. However, if a different octave is used as the central octave, you can specify the octave.offset (integer, length == 1) argument.

To illustrate, the default Scientific Pitch parser used the arguments:

  • kern('C5', parse(octave.integer = TRUE, octave.offset = 4)

    • Returns "cc" (the octave above middle C).

Non-integer Octave Markers

If octave.integer = FALSE, the humdrumR parser instead looks for three possible symbols to indicate octave information. These symbols are controlled using the up, down, and same (character, length == 1) arguments. A same symbol, or no symbol, is interpreted as the "central" octave; repeating strings of the up or down symbols indicate increasing positive (up) or negative (down) octaves. For example, in lilypond notation, , represents lower octaves, and ' (single apostrophe) represents upper octaves. So the default lilypond() parser uses these arguments:

  • pitch(c("c", "c", "c'"), parse(octave.integer = FALSE, up = "'", down = ",", octave.offset = 1))

    • Returns c("C2", "C3", "C4").

(Note that lilypond makes the octave below the central octave, using octave.offset = 1.)

Octave "Rounding"

In some situations, pitch data might interpret the "boundaries" between octaves a little differently. In most absolute pitch representations (e.g., kern(), pitch()), the "boundary" between one octave and the next is between B (degree 7) and C (degree 1). However, if for example, we are working with data representing intervals, we might think of an "octave" as spanning the range -P4 (G) to +P4 (f). In this case, the "octave boundary" is centered around the unison (or ), rather than starting at middle-C/unison. If our data was represented this way, we could use the octave.round argument; octave.round must be a rounding function, either round, floor, ceiling, trunc, or expand. These functions indicate how we interpret simple pitches "rounding" to the nearest C/unison. The default behavior for most pitch representations is octave.round = floor: each scale step is rounded downwards to the nearest C. So B is associated with the C 7 steps below it. If, on the other hand, octave.round = round, then scale-steps are "rounded" to the closest C, so B and A are associated with the closer C above them. Indeed, octave.round = round gets us the -P4 <-> +P4 behavior we mentioned earlier!

When working parsing intervals, the octave.round option allows you to control how the "simple part" (less than an octave) of a compound interval is represented. For example, we might think of a ascending major 12th as being an ascending octave plus a ascending perfect 5th: ** +P8 + P5**. Or we could encode that same interval as two ascending octaves minus a perfect fourth: + P15 - P4. The following table illustrates how different octave.round arguments "partition" compound intervals into simple parts and octaves:

roundfloorceilingtruncexpand
-P12:-P15 + P4-P15 + P4-P8 - P5-P8 - P5-P15 + P4
-P11:-P8 - P4-P15 + P5-P8 - P4-P8 - P4-P15 + P5
-P8:-P8 + P1-P8 + P1-P8 + P1-P8 + P1-P8 + P1
-P5:-P8 + P4-P8 + P4+P1 - P5+P1 - P5-P8 + P4
-P4:+P1 - P4-P8 + P5+P1 - P4+P1 - P4-P8 + P5
P1:+P1 + P1+P1 + P1+P1 + P1+P1 + P1+P1 + P1
+P4:+P1 + P4+P1 + P4+P8 - P5+P1 + P4+P8 - P5
+P5:+P8 - P4+P1 + P5+P8 - P4+P1 + P5+P8 - P4
+P8:+P8 + P1+P8 + P1+P8 + P1+P8 + P1+P8 + P1
+P11:+P8 + P4+P8 + P4+P15 - P5+P8 + P4+P15 - P5
+P12:+P15 - P4+P8 + P5+P15 - P4+P8 + P5+P15 - P4
+P15:+P15 + P1+P15 + P1+P15 + P1+P15 + P1+P15 + P1
+P18:+P15 + P4+P15 + P4+P22 - P5+P15 + P4+P22 - P5
+P19:+P22 - P4+P15 + P5+P22 - P4+P15 + P5+P22 - P4

Notice that, if octave.floor is being used, all simple intervals are represented as ascending.

When parsing "absolute" pitch representations, the octave.round option allows you to control which octave notes are associated with. The following table illustrates:

roundfloorceilingtruncexpand
FF:F2F2F3F3F2
GG:G3G2G3G3G2
C:C3C3C3C3C3
F:F3F3F4F4F3
G:G4G3G4G4G3
c:C4C4C4C4C4
f:F4F4F5F4F5
g:G5G4G5G4G5
cc:C5C5C5C5C5
ff:F5F5F6F5F6
gg:G6G5G6G5G6
ccc:C6C6C6C6C6
fff:F6F6F7F6F7
ggg:G7G6G7G6G7

Absolute or Relative (contour) Octave

In some notation encoding schemes, the "octave" of each note is interpreted relative the previous note, rather than any absolute reference. The most prominent system is Lilypond's relative octave entry style. This style is often used in combination with scale degree representations---as in the RS200 corpus. For example, a data set might say Do Re Mi vSo La Ti Do, with the "v" indicating a jump down to So. To activate relative-octave parsing, set octave.relative = TRUE---alternatively, you can use octave.absolute = FALSE, which is equivalent.

In a relative-octave data, we assume that octave indications indicate a shift relative to the previous note. This would usually be used in combination with octave markers like "^" (up) or "v" (down). Different combinations of octave.round allow us to parse different behaviors:

  • If octave.round = round, a same marker (or no marker) indicates that the note is the pitch closest to the previous pitch. Octave markers indicate alterations to this assumption. As always, this is based on scale steps, not semitones! Any fourth is "closer" than any fifth, regardless of their quality: So C F# is ascending and C Gb is descending! A ascending diminished 5th would be written C ^Gb---with up = ^.

  • If octave.round = floor, a same marker (or no marker) indicates that the note is in the octave above the previous pitch. Octave markers indicate alterations to this assumption. With this setting, going from C down to B always requires a down mark.

String Parsing

In addition to the three types of musical parsing considerations reviewed above (steps, species, and octaves), there are also some general string-parsing issues that we can consider/control.

Parts and Order

So far (above) we've discussed various ways that tonal pitch information (step, species, and octave) can be encoded, and how the humdrumR parser can be modified to handle different options. However, there are two general parsing issues/options to consider: what information is encoded, and in what order? The parts argument can be specifyied to indicate this. The parts argument must be a character vector of length 1--3. The characters in the must partial match either "step", "species", or "octave". The presense of any of these strings in the parts vector indicate that that information should be parsed. The order of the strings indicates what order the pieces of pitch information are encoded in input strings.

To illustrate, imagine that we had input data which was identical to a standard interval representation---e.g., M2 and P5---except the quality appears after the step---e.g., 2M and 5P. We could call interval(c("2M", "5P"), parse(parts = c("step", "species"))) and sure enough we'd get the correct parse!

One final string-parsing argument is sep, which indicates if there is a character string separating the pitch information components: The most common case would be a comma or space. For example, we could use a parse command like this: kern("E flat 5", parse(flat = "flat", sep = " ")).

Atonal Parsing (numeric inputs)

The humdrumR pitch parser (tonalInterval()) will interpret numeric inputs as atonal pitch information. By default, numbers are interpreted as semitones. However, parses for midi(), cents(), and frequencies are also defined. Dispatch to these different parsers is controlled by the Exclusive argument.

RepresentationExclusiveExample
Semitones**semits (or NULL)3 -> e-
MIDI**midi63 -> e-
Cents**cents300 -> e-
Frequency (Hz)

Enharmonic Interpretation

When converting from an atonal representation to a tonal one, we must decide how to interpret the tonality of the input---specifically, which enharmonic spelling of notes to use. The humdrumR numeric parser interprets atonal pitches in a "enharmonic window" of 12 steps on the line-of-fifths. The position of this window is set with the enharmonic.center (integer, length == 1) argument. By default, enharmonic.center = 0, which creates a window from a -5 (b2) to +6) (#4). If you prefer #1 instead of b2, set enharmonic.center = 1. For all flats, set enharmonic.center = -1. For all sharps, set enharmonic.center = 4.

enharmonic.center01234567891011
-21b22b334b55b66b7b1
-11b22b334b55b66b77
01b22b334#45b66b77
11#12b334#45b66b77
21#12b334#45#56b77
31#12#234#45#56b77
41#12#234#45#56#67

The enharmonic.center argument will work the same when translating to any pitch representation, like kern(). However, we present the table above in terms of scale degrees because the atonal -> enharmonic calculation is centered on a key. So, if Key argument is specified, the "enharmonic window" is centered around that key. So if you are translating to kern and the Key = F#:, the output will range from Gn to B#. If you don't want this, set Key = NULL.

Melodic Interpretation of Chromatic Notes

It is very common for chromatic notes in melodic passages to be labeled based on their melodic contour: i.e., ascending chromatic notes labeled sharp and descending chromatic notes labeled flat. This behavior can be engaged by setting the accidental.melodic (logical, length == 1) argument. When accidental.melodic = TRUE, the input is first centered in the enharmonic window (above), but then any places where a chromatic alteration proceeds upwards to a non-chromatic note will be altered (if necessary) to a sharp, and vice verse for a descending notes and flats. For example, while kern(0:2) returns c("c", "d-", "d"), kern(0:2, parse(accidental.melodic = TRUE)) returns c("c", "c#", "d").

See also

All humdrumR pitch functions make use of the parsing functionality.

Examples


tonalInterval('II#', step.labels =c('I', 'II', 'III','IV','V','VI','VII'))
#> tonalInterval
#> [1] -d7

kern('E x 5', parse(doublesharp = 'x', sep = ' '))
#> **kern (character)
#> [1] ee##