-
Notifications
You must be signed in to change notification settings - Fork 1
/
RhythmAndMeter.Rmd
456 lines (272 loc) · 14.3 KB
/
RhythmAndMeter.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
---
title: Time, rhythm, and meter in humdrumR
author: "Nathaniel Condit-Schultz"
date: "`r Sys.Date()`"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Time, rhythm, and meter in humdrumR}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r, include = FALSE, message=FALSE, echo = FALSE }
source('vignette_header.R')
par(mar=c(1,1,1,1))
rhythmFunctions <- humdrumR:::rhythmFunctions
```
As a computational musicology toolkit, `r hm`'s tools for analyzing and manipulating rhythmic (timing) information are just about the most important tools in the toolbox.
For the most part, `r hm`'s rhythm tools are focused on the Western notions of rhythm and meter, but the tools can easily be generalized to more diverse notions of rhythm and time.
# Durations
The fundamental unit of rhythm is a "duration"---a span of time.
`r Hm` defines a suite of rhythm functions, notably `recip()` and `duration()`.
These functions all work in essentially the same way: they take in an input argument and output rhythm (duration) information in their own particular format.
For example,
```{r}
input <- c('4.c', '8d', '4e', '2.g')
duration(input)
recip(input)
notehead(input)
quarters(input)
```
Notice that the functions all recognize the rhythm part of these input tokens, ignoring the non-rhythm (pitch) information.
If you want to keep the non-rhythm part of your tokens, use the `inPlace` argument:
```{r}
input <- c('4.c', '8d', '4e', '2.g')
duration(input, inPlace = TRUE)
recip(input, inPlace = TRUE)
notehead(input, inPlace = TRUE)
quarters(input, inPlace = TRUE)
```
The cool thing is that each of these functions can read any of the other function's output.
So you can do things like:
```{r}
recip(0.375)
quarters('4.')
quarters(0.375)
#
#
recip("𝅘𝅥 𝅭 ")
```
----
The complete list of basic rhythm (duration) functions is:
```{r results = 'asis', echo = FALSE}
pfs <- rapply(humdrumR:::rhythmFunctions,
\(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, ' duration representations*'), pf), collapse = '\n'), names(top), top))
pfs <- Map(\(name, l) paste(c(paste0('+ **', name, ' duration representations**'), unlist(l)), collapse ='\n'), names(pfs), pfs)
cat(unlist(pfs), sep = '\n')
```
#### Documentation
The global documentation for *all* the rhythm functions can be seen by calling `?rhythmFunctions`.
You can also call documentation for any individual function, like `?recip`.
### Rhythm Arguments
The rhythm functions from the previous section have a few shared arguments that they *all* understand: `scale` and `unit`.
### Scale and Unit
The `scale` and `unit` arguments are conceptually different, but can be used to achieve very similar transformations, so it can be easy to confuse them.
The `scale` argument is the easiest to understand: it simply (re)scales the output duration of the function.
Would you like to [augment](https://en.wikipedia.org/wiki/Augmentation_(music)) durations? Perhaps doubling their length?
Simply specify `scale = 2`.
```{r}
recip(c('8','4','4.','2'), scale = 2)
```
Or maybe you'd like [diminish](https://en.wikipedia.org/wiki/Diminution) the durations, by cutting them in thirds?
```{r}
recip(c('8','4','4.','2'), scale = 1/3)
```
---
Unlike `scale`, which controls the *output* scale, `unit` controls the input scale.
This is most useful with numeric values.
What do we mean by "controls the input scale"?
Imagine we have a bunch of duration values as numbers, like `c(1, 1.5, 2, 1, 0.5, 1)`.
But what unit is these numbers counting?
By default, `r hm` treats them as *whole notes*, so `0.5` is a half note, right?
However, we might prefer to think of these numbers as units of quarter notes---a very common approach.
This is what the `unit` argument is for.
Because it is an argument for the rhythm parser/interpreter, you must pass `unit` through the `parseArgs` argument, which must be a named list of values:
```{r}
input <- c(1, 1.5, 2, 1, 0.5, 1)
recip(input)
recip(input, parseArgs = list(unit = '4'))
```
As you can see, you *can* achieve the same difference in output using the `scale` argument (`scale = 1/4`), but the `unit` argument is a little bit of a different way of thinking about it.
## Special Cases
### IOI
In `**kern` data, some durations correspond to *rests* in the music, rather than notes, indicated by an `"r"`.
In many analyses, we want to ignore rests and just consider the durations (timespans) between notes, ignoring the presence of rests.
These are called *inter-onset-intervals*, or IOIs.
The `ioi()` function can be used to convert duration data that includes rests, into IOIs.
For example:
```{r}
melody <- c('8g','8f#', '8g', '8r', '8g', '8f#','8e','8d', '4d#', '8r', '8f#', '4b', '4r')
ioi(melody)
```
What did this do?
1. The duration of each rest is append to the duration of the previous non-rest (if any).
+ The first `8g` becomes a `4g`
+ The `4d#` becomes `4.d#`.
2. Rests are replaced by null tokens (`"."`).
3. The last note even is `NA`, because there is no more onsets after it to create a inter-*onset*-interval.
The last step here makes conceptual sense, but may not be what you want!
Maybe you'd like the interval between the last onset and the *end* of the data?
For that, specify `finalOnset = TRUE`:
```{r}
ioi(melody, finalOnset = TRUE)
```
When used in a call to `with()` or `within()`, `ioi()` will automatically be applied within Files/Spines/Paths.
### Ties
A similar case to inter-onset-intervals are *ties*.
In `**kern`, the `[`, `_`, and `]` tokens are used to indicate groups of durations that are actually played as a single, longer duration.
The `sumTies()` function will automatically sum the durations of tied notes:
```{r}
melody <- c('[2d', '8d]', '8d', '8f#', '8g#', '4.a', '[8b-', '2b-_', '4b-]', '4a', '2g#')
sumTies(melody)
```
The tie tokens are removed, and tied-notes are replaced by null tokens (`"."`).
# Timeline
Traditional musical scores, and most humdrum data, encode rhythmic information as duration, like what we worked on in the previous section.
However, we often want to think about when musical events are happening, relative to a fixed reference: usually, relative to the beginning of the piece.
We can compute a *timeline* from sequences of duration values, using the `timeline()` function.
Let's say we have a melody like:
```{r}
row <- c('4c', '4c', '6c', '12d', '4e', '6e','12d', '6e', '12f', '2g',
'12cc', '12cc', '12cc', '12g', '12g', '12g', '12e', '12e', '12e', '12c', '12c', '12c',
'6g','12f', '6e', '12d','2c')
```
Let's look at a timeline for that melody:
```{r}
timeline(row)
```
The first note (`4c`) occurs at zero (the beginning) of the timeline; the last note (`2c`) lands `3.5` whole-notes later; etc.
If we combine this with a call to `semits()` (check out the [Pitch and Tonality][PitchAndTonality.html] guide) we can even make a plot of the melody:
```{r}
plot(x = timeline(row),
y = semits(row, Exclusive = 'kern'),
main = 'Row Row Row Your Boat')
```
---
The `timeline()` function has extra special functionality when working on actual humdrum datasets.
It will automatically (unless told not too) calculate timelines separately within each File/Spine/Path of a dataset, ignoring multiple stops.
For example:
```{r}
chorales <- readHumdrum(humdrumRroot, 'HumdrumData/BachChorales/chor.*.krn')
chorales |>
timeline()
```
So we get a separate timeline for each spine in each piece.
> Note: `timeline()` ignores all multi-stops.
> If you have spine paths in your data, you should look into the [expandPaths](ComplexSyntax.html#expanding-paths "Complex syntax article, expanding paths section") options.
## Controlling the Start
You'll notice that our timelines all start from zero, which makes sense of course.
But in some cases, you might want to have the timeline start at a different value.
For example, "Row Row Row Your Boat" is a round, so we'd want the second entrance of the round to start at the second measure.
We can do this with the `start` argument to timeline:
```{r}
timeline(row, start = 2)
```
----
### Pickups
The `pickup` argument gives us another option to control where our timeline starts.
In many datasets, the first few events in the music are an *anacrusis* or "pick up"; we'd generally like our timeline to start *after* the pickup.
To make this happen, the `pickup` argument can be passed a `logical` vector which is the same length as the input (`x`):
Any `TRUE` values at the beginning of the vector are considered a pickup;
Thus, the *first* `FALSE` value in the `pickup` vector is chosen as the start of th timeline.
For example, let's say we have the melody
```{r}
melody <- c('8c', '8d','2e', '2f','1e')
```
but the first two notes are a pickup.
Since we can *see* that the pickup has a duration of `.25` (quarter-note), we *could* use the `start` argument to do this:
```{r}
timeline(melody, start = -.25)
```
Now `0` is on the downbeat, and events before that `start` are negative on the timeline!
To do this with the `logical` `pickup` argument, we could do this:
```{r}
timeline(melody, pickup = melody != '2e')
```
This might seem *less* intuitive!
However, this approach can be very useful when working with actual humdrumR datasets.
In many humdrum datasets, pickup measures are indicated by have barlines labeled `=0` or `=-`.
When `r hm` reads a file, it counts the barlines and creates a field called `Bar`, and it numbers pickup measures as zero (or negative numbers, if there are more than one).
This means pickups will have `Bar < 1`.
So, in our Bach chorales:
```{r}
chorales |>
timeline(pickup = Bar < 1)
```
Now the `0` in our timelines corresponds to the first downbeat of each spine, in each file.
The key advantage is that this will work even if different pieces in the corpus have pickups of different lengths, and even if there is *no* pickup.
### Timestamp
The `timestamp()` function is a special variant of `timeline()` which outputs a timeline in clock-time, using the `dur()` format.
In order to do this `timestamp()` needs to know a tempo: by default, `r hm` will pass the `BPM` field from humdrum data (if there is one) to `timestamp()`.
If the `BPM` argument is not provided, the default is 60 beats-per-minute.
# Counting Beats
Ok, the `timeline()` gives us a timeline in whole-note units, giving fractional (decimal) output.
Often in music, we want to know how many *beats* have elapsed at a given time, rather than the exact time position.
For this, use the `timecount()`, and its `unit` argument.
The default `unit` is a whole-note, so `timecount()` will count which whole note you are on:
```{r}
row <- c('4c', '4c', '6c', '12d', '4e', '6e','12d', '6e', '12f', '2g',
'12cc', '12cc', '12cc', '12g', '12g', '12g', '12e', '12e', '12e', '12c', '12c', '12c',
'6g','12f', '6e', '12d','2c')
timecount(row)
```
Let's try quarter-notes instead:
```{r}
timecount(row, unit = '4')
```
`timecount()` will even work for irregular beat patterns, which must be entered as a list.
For example, the meter 7/8 is often played as three beats, with the last beat being longer than the first two: a pattern line `4 4 4.`.
`timecount()` can count these irregular beats!
```{r}
seven8 <- c('4', '4', '8','8','8','4','4','4','8','4','4','8','8', '8', '4')
timecount(seven8, unit = list(c('4', '4', '4.')))
```
This could be handy for counting subdivisions in swing time!
### Subposition
The counterpart to `timecount()` is `subpos()`.
When we count beats, some notes don't actually land *on* the beat, but somewhere "inside" the beat---in other words, between beats.
`subpos()` will tell us how far from the beat each attack is; the unit will be whole-notes, unless
we pass a `scale` argument to change the scale.
Let's look at our last few examples again, but using `subpos()`:
```{r}
subpos(row, scale = 12)
subpos(row, unit = '4', scale = 12)
subpos(seven8, unit = list(c('4', '4', '4.')), scale = 8)
```
# Meter
The `timecount()` and `subpos()` commands are great if we want count in a single beat/measure unit.
To take things to the next level(s), we need to consider musical *meter*.
From the point of view of `r hm`, "meter" is a set of multiple "beat levels" occurring at the same time (in parallel), with "lower" (faster/shorter) levels nested inside "higher" (slower/longer) levels.
`R Hm` defines a number of useful tools for applying metric analyses to rhythmic data.
### Metric levels
The first thing we might want to do, is take a sequence of rhythm durations and identify which metric level each onset lands on.
To do this, use `metlev()`:
```{r}
eighths <- c('8', '8', '8', '8', '8', '8', '8', '8')
metlev(eighths)
```
By default, `metlev()` is assuming a duple meter for us---basically 4/4 time---so these eight eighth-notes make one measure of 4/4.
The first onset lands on the downbeat, coinciding with the highest level in the meter (as defined by default)---this highest level beat is a whole-note, which is why the output is `"1"` (`**recip` notation for a whole-note).
Every odd eighth-note falls down at the eighth-note beat level, and is labeled `"8"`.
4/4 beats 2 and 4 (the back beats) fall on the quarter-note level, and labeled `"4"`.
Finally, beat 3 is on the half-note level (`"2"`).
If you prefer to have your levels simply numbered from highest to lowest, use `metlev(..., value = FALSE)`:
```{r}
eighths <- c('8', '8', '8', '8', '8', '8', '8', '8')
metlev(eighths, value = FALSE)
```
The whole-note level is `1` and (in this case) the eighth-note level is `4`.
### Metric position
Along with `metlev()`, `r hm` provides to complementary functions called `metcount()`, `metsubpos()`.
As you might guess, these are metric parallels of `timecount()` and `subpos()`.
The `metcount()` function counts beats at a particular beat level (the `level` argument) *within* the highest level in the meter.
The `metsubpos()` function calculates the rhythm offset between each onset and the nearest beat in the meter.
## Controlling meter
You can control the meter used by `metlev()` using the `meter` argument.
The simplest thing is to pass the meter as a humdrum time-signature interpretation:
```{r}
eighths <- c('8', '8', '8', '8', '8', '8', '8', '8')
metlev(eighths, meter = 'M3/4')
```