source('vignette_header.R') par(mar=c(1,1,1,1)) pitchFunctions <- humdrumR:::pitchFunctions
As a computational musicology toolkit, r hm
's tools for analyzing and manipulating pitch data are just about the most important tools in the toolbox.
For the most part, r hm
's pitch tools are focused on the Western system of tonality, and tools for representing pitches in a tonal context are the focus of this vignette---we also have standard tools for looking at pitch from a Western, 12-tone atonal setting as well.
r Hm
defines a suite of "pitch functions," like kern()
, solfa()
, interval()
, and semits()
.
These functions all work in essentially the same way: they take in an input argument and output pitch information in their own particular format.
For example, let's take a little bit of **kern
data:
input <- c('4.c', '8d', '4e', '2.g')
Notice that this data has both rhythmic information (4.
, 8
, 4
, 2.
) and pitch information (c
, d
, e
, g
).
What happens if we give this data to our "pitch functions"?:
kern(input) interval(input) semits(input) solfa(input)
Each function correctly reads the **kern
pitch information, ignoring the rhythm information, and outputs the pitch information in a different format.
This allows us to "translate" between different ways of representing pitch information.
If you want to keep the non-pitch (rhythm) information, use the inPlace
argument:
kern(input, inPlace = TRUE) interval(input, inPlace = TRUE) semits(input, inPlace = TRUE) solfa(input, inPlace = TRUE)
The cool thing is that each of these functions can read any of the other functions' output. So you can do things like:
kern('Cb6') pitch("eee-") pitch(-4) # semits kern('-4') solfa('A#6') semits('Ab3')
The complete list of basic pitch functions is:
pfs <- rapply(humdrumR:::pitchFunctions, \(func) paste0(' + `', ifelse(humdrumR:::.names(func) == '', func, paste0(humdrumR:::.names(func))), '`', ifelse(humdrumR:::.names(func) == '', '', paste0(' (', func, ')'))), how = 'list') pfs <- lapply(pfs, \(top) Map(\(name, pf) paste(c(paste0(' + *', name, ' pitch representations*'), pf), collapse = '\n'), names(top), top)) pfs <- Map(\(name, l) paste(c(paste0('+ **', name, ' pitch representations**'), unlist(l)), collapse ='\n'), names(pfs), pfs) cat(unlist(pfs), sep = '\n')
Each one of these functions represents a different way of representing equivalent pitch information.
The global documentation for all the pitch functions can be seen by calling ?pitchFunctions
.
You can also call documentation for any individual function, like ?kern
.
Some pitch representations encode scale degree, which depends on the key.
We can use the Key
argument to control how our pitch functions interpret key---either translating from degrees to absolute pitches, or vice versa.
Pass the Key
argument a humdrum key interpretation, like c:
(c minor) or A-:
(a-flat major).
absoluteInput <- c('c', 'd-', 'e-', 'g', 'a-') solfa(absoluteInput) solfa(absoluteInput, Key = 'A-:') degreeInput <- c('do', 're', 'mi', 'fi', 'so') kern(degreeInput) kern(degreeInput, Key = 'A-:')
The Key
argument is vectorized (see the R primer if you don't know what that means!); that means that you can actually read information with different/changing keys.
Let's have a look at using pitch functions on real data.
Let's load r hm
's built-in Bach chorales:
chorales <- readHumdrum(humdrumRroot, 'HumdrumData/BachChorales/.*krn')
These chorales are full of **kern
data in the (default) Token
field, which can easily be parsed/translated.
Maybe we'd like to convert the **kern
to semitones.
r Hm
's pitch functions can all be used "humdrum-style, so using semits()
is very easy:
chorales |> semits()
We could then, for instance, make a histogram of the semitone values:
chorales |> semits() |> pull() |> hist(main = 'Histogram of semits() in 10 chorales')
The chorale dataset has key information in its built-in Key
field.
If we use a pitch function in humdrum- or -tidy style, the Key
field is automatically passed as a Key
argument to the function!
That means if we apply solfa()
, we will get the correct scale degrees, given the keys in the data:
chorales |> solfa(Token)
**kern
data, like in these chorales, includes rests, indicated by r
in place of a pitch.
What happens when a pitch function sees a rest in our data?
Let's see:
solfa(c('8gg', '8ff', '4ee', '8dd', '8r', '2cc'))
The rest token, is output as a null (.
) value---this null .
is actually an R NA
value.
In fact, any token in the input which fails to parse as pitch will return as NA
/.
.
For example:
kern(c('C4', 'X', 'E4'))
Since all the "pitch functions" do the same sort of task, they share a number of common arguments.
The complete list of these pitch arguments can be found in the ?pitchParsing
and ?pitchDeparsing
man pages, but the most important ones are described here:
All pitch functions accept logical (TRUE
or FALSE
) generic
and specific
arguments.
If generic == TRUE
, only generic pitch information is returned.
This affects different pitch representations in different ways:
kern()
, pitch()
and lilypond()
, no accidentals are included in generic output;intervals()
, the interval quality is removed;solfa
, scale degree four will be "fa", never "fi" (sharp four).The specific
argument is simply the opposite of generic
, so you have the choice of indicating genric = TRUE
or specific = FALSE
---they are equivalent.
In code:
input <- c('E', 'G#', 'A', 'B-', 'F#', 'G') pitch(input, generic = FALSE) pitch(input, generic = TRUE) interval(input, specific = TRUE) interval(input, generic = TRUE) solfa(input, specific = TRUE) solfa(input, specific = FALSE)
All pitch functions accept logical (TRUE
or FALSE
) simple
and compound
arguments.
If simple == TRUE
, octave information is stripped from the output, leaving only "simple" pitch information.
This affects different pitch representations in different ways:
pitch()
and degree()
, the octave number is removed.lilypond()
and helmholtz()
, the octave marks '
and ,
are removed.solfa()
and deg()
, the contour indicators (explanation below) v
and ^
are removed.interval()
, only steps between 1--7 and returned (i.e., no 10ths).The compound
argument is simply the opposite of simple
, so you have the choice of indicating simple = TRUE
or compound = FALSE
---they are equivalent.
In code:
input <- c('c', 'a', 'dd#', 'ee', 'G', 'GG', 'GG-', 'CCC') degree(input, simple = FALSE) degree(input, simple = TRUE) lilypond(input, compound = TRUE) lilypond(input, simple = TRUE) semits(input, compound = TRUE) semits(input, compound = FALSE) solfa(input, compound = TRUE) solfa(input, compound = FALSE) interval(input, simple = FALSE) interval(input, simple = TRUE)
The generic
and simple
arguments are useful if you want to tabulate pitch classes, for example:
chorales <- readHumdrum(humdrumRroot, 'HumdrumData/BachChorales/.*krn') chorales |> kern(simple = TRUE) |> count() |> draw()
chorales |> kern(generic = TRUE) |> count() |> draw()
chorales |> kern(simple = TRUE, generic = TRUE) |> count() |> draw()
All of r hm
's pitch functions have built-in transposition functionality.
You can use this functionality by passing a named list of arguments to the transpose()
function, as the transposeArgs
argument to the pitch function call, like in this example using the by
argument:
kern(c('c', 'd', 'e', 'f', 'f#', 'g'), transposeArgs = list(by = 'M2'))
Another option is to transpose by key.
You can the to
argument through to tranpose()
.
Let's say we have music in A major that we'd like to transpose to C major:
Amelody <- c('8A', '8B', '4.c#', '16d', '8c#','8f#', '8e', '8d#', '4e') kern(Amelody, Key = 'A:', transposeArgs = list(to = 'C:'))
So the Key
argument to kern()
indicates the key the music is coming from, and the to
argument in tranposeArgs
indicates where to transpose it to.
Since humdrum/tidy functions automatically use Key
information from a data set, this makes it very easy to, say, transpose all **kern
tokens in your data to C major:
chorales <- readHumdrum(humdrumRroot, 'HumdrumData/BachChorales/.*krn') chorales |> kern(simple = TRUE, transposeArgs = list(to = 'C:')) |> count() |> draw()
This looks exactly like calling solfa()
:
chorales |> solfa(simple = TRUE) |> count() |> draw()
What if we want to calculate melodic intervals in each part of a score?
This the purpose of the mint()
(melodic intervals) command.
We can use mint()
on any pitch data:
input <- c('d', 'B-', 'A', 'G', 'D', 'd', 'B-', 'G#', 'A', 'f', 'e', 'd') mint(input)
If you use mint()
in humdrum- or tidy- style on a r hm
dataobject, mint()
will automatically be called within each spine/path in each file (this is accomplished using the groupby
argument to mint()
):
chorales <- readHumdrum(humdrumRroot, 'HumdrumData/BachChorales/.*krn') chorales |> mint()
mint()
has a number of special options, which you can read about in the manual by checking out ?mint
.
For now, we'll just show you the classify
argument, which can be used to classify output intervals as either Unison
, Step
, Skip
, or Leap
:
chorales |> mint(classify = TRUE)
In parallel to mint()
is hint()
(harmonic intervals) command.
hint()
works just like mint()
except it calculates intervals from left to right in each record of a file:
chorales |> hint()
A special trick is to use the lag
argument to calculate harmonic intervals all in relation to the same spine.
For example, you can calculate harmonic intervals between each voice and the bass voice like so:
chorales |> hint(lag = Spine == 1)
Or, harmonic intervals from the soprano like so:
chorales |> hint(lag = Spine == 4)
Check out the "Logical lags" section of the ?hint
manual for details.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.