-
Notifications
You must be signed in to change notification settings - Fork 10
/
tabr-helpers.Rmd
156 lines (113 loc) · 9.55 KB
/
tabr-helpers.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
---
title: "Phrase helpers"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Phrase helpers}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r setup, include = FALSE}
options(crayon.enabled = TRUE)
sgr_wrap <- function(x, options){
paste0("<pre class=\"r-output\"><code>", fansi::sgr_to_html(x = htmltools::htmlEscape(x)), "</code></pre>")
}
knitr::knit_hooks$set(output = sgr_wrap)
knitr::opts_chunk$set(
collapse = TRUE, comment = "#>", message = FALSE, warning = FALSE, error = FALSE, tidy = FALSE, out.width = "100%"
)
library(tabr)
```
This section covers helper functions related to phrases. These functions assist with improving readability, organization, and limiting the amount of typing required to specify phrases where possible.
## Aliases
Briefly, there are some convenient aliases for `tabr` functions that are used often. These aliases have short function names to help keep the music notation R syntax a more concise if desired. These include:
* `p()`, an alias for `phrase()`.
* `tp()`, an alias for `transpose()`.
The most common conflicts to watch out for are using `tabr` with `shiny` where `p()` is a function and if you use `purrr` to help wrangle some of your more complex tablature, it has its own `transpose()` function. Pay attention to package load order and/or use explicit names spaces if working with these packages loaded.
## Repeat and concatenate
The previous section on phrases presented an opportunity to introduce `pn()` for repeating strings and `pc()` for joining them together. These were used on strings passed to `phrase()` but work on any character string.
To recap, `pn()`, is for repeating a phrase `n` times; is a simple wrapper around `rep()` that maintains the `phrase` class when passed a phrase object and uses spaces when collapsing the repeated input. `pc()` is a similar wrapper around `paste()`. It can be passed an arbitrary number of input simple character strings or phrase objects. The `phrase` class is applied to the output as long as at least one input passed to `pc()` is a phrase object. It is the responsibility of the user to ensure that in such a case a simple character string also represents valid phrase content so that the output phrase is valid.
```{r join}
pc("c d e", "f g a")
pn("c d e", 2)
```
## Rests
The `rest()` function is a simple wrapper around `rep()` much like `pn()`. It is useful when you need to propagate a number of rests, perhaps not all of equal duration. It accepts two vectorized arguments. The first is the duration of the rest. The second is the number of times to repeat it.
For example, say a score has multiple tracks. During a certain period in the score, one track goes silent and needs to be carried over with a sequence of rests. Perhaps the instrument dropped out in the middle of a measure and returns in the middle of another, with several full measures in between.
This can easily be notated using `pn()` and `pc()`, but it could be more convenient with `rest()`, or at least offer more interpretable code if you prefer.
In this example, using `rest()` is compared with writing out the full `notes` string as well as using some combinations of `pn()` and `pc()`. It is a matter of preference which approach you take and dependent on the content being repeated and pasted together.
Note that using the terminal in-string multiplicative expansion operator, `*`, in cases of many adjacent single-element repetitions, is by far the best option as far as shortening code is concerned.
If this method is preferred, it essentially removes any edge case preference there may have been for `rest()`.
```{r rest}
"r8 r8 r8 r1 r1 r1 r1 r1 r1 r1 r1 r1 r1 r4."
pc(pn("r8", 3), pn("r1", 10), "r4.")
rest(c(8, 1, "4."), c(3, 10, 1))
"r8*3 r1*10 r4."
```
## Ties
Tied notes were introduced momentarily in the previous section on phrases. The example given showed that ties are denoted by appending the pitch letter of the note with `~` and this will create a tie in the output from this note to the next one.
This can be frustrating to type and to read when done for six-string chords for example. `tie()` can make this more bearable. Chords receive further discussion in subsequent sections. They were shown briefly at the end of the last section so that space-delimited time and simultaneous notes could be demonstrated clearly. For now, this is enough to demonstrate `tie()`.
```{r tie}
em <- "e,a,dgbe'"
tie(em)
```
## Hammer on/pull off
There is a simple helper function, `hp()`, for hammer ons and pull offs, or for slurs in general. Like several other helpers it is a wrapper around paste. It ensures slurs come in an even number so there is always a starting and stopping point and accepts several input styles.
```{r hp1}
hp("16 16")
hp("16", "16")
hp(16, 16, 8, "2.")
```
It is particularly useful for long runs of similar repeated hammer ons and/or pull offs that would be frustrating to type out directly.
```{r hp2}
hp(pn("16 8", 8))
```
## Transpose
In the last section there was an example showing a sequence of notes followed by the same sequence transposed one octave up. At the time, it was written out twice. Here, the same sequence is reproduced using `transpose()`. Transposing up one octave is done by specifying an increase of 12 semitones or half steps.
```{r tp1}
notes1 <- "c b, c d e e d c b, c c c'"
notes2 <- "c' b c' d' e' e' d' c' b c' c' c''"
all(transpose(notes1, 12) == as_noteworthy(notes2))
```
Transposing down is done with negative integers. The default (`n = 0`) returns the input, `x`, with no transposition. The alias `tp()` can be used to limit typing. Examples are shown below.
```{r tp2}
transpose("a_ b_' c''", 0)
tp("a_ b_' c'", -1)
tp("a_ b_' c'", 1)
tp("a# b' c#'", 11)
tp("a# b' c#'", 12)
tp("a# b' c#'", 13)
```
If no other information is passed to `transpose()`, positive `n` will sharpen naturals. Negative `n` will flatten naturals. This is not ideal because it may not fit a key signature. A standard key signature is composed of notes that may include flats or sharps but not both. To avoid simply defaulting to one or the other based on direction of transposition, specify they key signature with `key` to be formal, e.g. `key = "dm"`, or just specify `"flat"` or `"sharp"`" because this is really all that is needed.
Since `notes` can be arbitrarily short, even a single note, there is no reason for `transpose()` to attempt to guess the original or new key from the notes present and degree of transposition. `notes` may easily include accidentals anyhow. `transpose()` works with either integer or tick style octave numbering as shown below and the output `style` can be forced either way.
```{r tp3}
tp("a3 b4 c5", 2, key = "f")
tp("a3 b4 c5", 2, octaves = "tick", key = "g")
tp("a b' c''", 2, accidentals = "flat")
tp("a, b c'", 2, octaves = "integer", accidentals = "sharp")
```
The key of F has one flat (B flat) and the key of G has one sharp (F sharp). Transposing the above notes up by a whole step (two semitones or half steps) results in a new note sequence that includes either a D flat or C sharp for the second note. This pitch does not occur (except as an accidental) in either of those keys.
Nevertheless, because F is a key with some number of sharps and G is a key with some number of flats, transpositions resulting in any non-natural notes are represented as flats and sharps, respectively. The key of C major and its relative A minor have only natural notes. Setting `key "c"` or `key = "am"` results in the default behavior, falling back on sharp notes for transposing up and flat notes for transposing down.
## Tuplets
An important feature is the tuplet. This will evenly fit a sequence of notes into a number of beats not otherwise permitted by the time signature. The most common and default tuplet returned by `tuplet()` is the triplet. There is also an alias function, `triplet()` for this. The triplet fits three equally spaced notes into two beats that would normally be taken up by two of those notes, meaning each note in the triplet lasts for two thirds the normal duration. The examples below show the equivalence of default `tuplet()` settings and `triplet()` as well as other specifications for `tuplet` and the ability to specify multiple consecutive tuplets of a fixed pattern with a single call to `tuplet()`.
```{r tuplet1}
x <- "c' d' e'"
tuplet(x, 8)
triplet(x, 8) # equivalent
tuplet(pn(x, 2), 8, a = 6, b = 4) # 6 notes per 4 beats
tuplet(pn(x, 4), 8) # multiple tuplets, one call
```
Like `phrase()`, these functions accept a `string` number argument, which is critical as usual for guitar tablature (try engraving the final phrase below without explicit string numbers and see the mess you get). This example includes some rests.
```{r tuplet2, results="hide", eval=FALSE}
p1 <- c(
triplet("c' r e'", 8, "4 3 3"),
tuplet("f' g' a' b' c'' b'", 8, "3 2 2 1 1 1", 6, 4),
tuplet("a' r f' g' r e' f' e' d' e' d' c'", 16, "2 2 3 2 3*7 4"),
tuplet("b a g f e", 16, "4 4 5*3", 5, 4)
)
track(pc(p1, p("d c", "8 4.", "6 6"))) |> score |> tab("ex12.pdf")
```
<p><img src="https://github.com/leonawicz/tabr/blob/master/data-raw/vignette-pngs/ex12.png?raw=true" class="centerimg" width="100%"></p>
By itself `triplet()` is of limited utility. Notice that it does not allow any additional note info to be included. If you have more note info, `phrase()` supports triplets directly (not general tuplets though). It is recommended to provide note info to `phrase()` that uses the `t`-prefix triplet notation, e.g., `"t8"`. Notes and chords with this triplet timing will be parsed into LilyPond triplet syntax like above, but also retaining other note info.
```{r cleanup, echo=FALSE}
unlink("*.mid")
```