Can we build and play music by manipulating tidy data frames ?
The {gm} package by Renfei Mao is young and promising, this package is a wrapper of {gm} where I play with some ideas. You’ll need to install {gm} first.
Install with
remotes::install_github("moodymudskipper/tidygm")
I’ll illustrate {tidygm} by recreating a version of Coldplay’s song “Clocks”. I followed this great breakdown of the song by Rick Beato :
https://www.youtube.com/watch?v=7_warUFthJI&ab_channel=RickBeato
library(tidygm) library(magrittr, include.only = "%>%") #> Warning: package 'magrittr' was built under R version 4.0.4
The first bar of “Clocks” is a piano line that goes :
Eb6, Bb5, G5, Eb6, Bb5, G5, Eb6, Bb5
Let’s make a song out of it using the function new_song, which creates a tidy data frame, and play which plays it.
new_song)durations argument so new_song will guess these are all eighth notespiano_1 <- list("E-6", "B-5", "G5", "E-6", "B-5", "G5", "E-6", "B-5") song <- new_song(list(piano_1), key = "Fm", tempo = 129) song #> # A tibble: 1 x 7 #> bar_id tempo key meter instrument pitches durations #> <int> <dbl> <chr> <chr> <chr> <list> <list> #> 1 1 129 Fm 4/4 piano <list [8]> <list [8]> play(song)
This worked well! let’s write two bars in one go now :
piano_2 <- list("C#6", "A#5", "F5", "C#6", "A#5", "F5", "C#6", "A#5") song <- new_song(list(piano_1, piano_2), key = "Fm", tempo = 129) song #> # A tibble: 2 x 7 #> bar_id tempo key meter instrument pitches durations #> <int> <dbl> <chr> <chr> <chr> <list> <list> #> 1 1 129 Fm 4/4 piano <list [8]> <list [8]> #> 2 2 129 Fm 4/4 piano <list [8]> <list [8]> play(song)
We don’t want to build the object from scratch every time, so we provide utilities to build our song data frame as we go.
The 3rd is the same as the 2nd, so we copy and paste it :
song <- song %>% copy_and_paste(bar_id == 2) song #> # A tibble: 3 x 7 #> bar_id tempo key meter instrument pitches durations #> <dbl> <dbl> <chr> <chr> <chr> <list> <list> #> 1 1 129 Fm 4/4 piano <list [8]> <list [8]> #> 2 2 129 Fm 4/4 piano <list [8]> <list [8]> #> 3 3 129 Fm 4/4 piano <list [8]> <list [8]> play(song)
copy_and_paste has additional arguments subset_to, to indicate where to paste (to overwrite values), and what to indicate what to paste, but if left empty as done here we just copy everything to the end, with new values of bar_id.
The 4th bar is different, since we cannot copy and paste it, we add a new bar using add_bars()
piano_4 <- list("C6", "A-5", "F5", "C6", "A-5", "F5", "C6", "A-5") song <- add_bars(song, list(piano_4)) song #> # A tibble: 4 x 7 #> bar_id tempo key meter instrument pitches durations #> <dbl> <dbl> <chr> <chr> <chr> <list> <list> #> 1 1 129 Fm 4/4 piano <list [8]> <list [8]> #> 2 2 129 Fm 4/4 piano <list [8]> <list [8]> #> 3 3 129 Fm 4/4 piano <list [8]> <list [8]> #> 4 4 129 Fm 4/4 piano <list [8]> <list [8]> play(song)
The full sequence is then repeated to complete the piano intro.
song <- copy_and_paste(song, bar_id %in% 1:4) song #> # A tibble: 8 x 7 #> bar_id tempo key meter instrument pitches durations #> <dbl> <dbl> <chr> <chr> <chr> <list> <list> #> 1 1 129 Fm 4/4 piano <list [8]> <list [8]> #> 2 2 129 Fm 4/4 piano <list [8]> <list [8]> #> 3 3 129 Fm 4/4 piano <list [8]> <list [8]> #> 4 4 129 Fm 4/4 piano <list [8]> <list [8]> #> 5 5 129 Fm 4/4 piano <list [8]> <list [8]> #> 6 6 129 Fm 4/4 piano <list [8]> <list [8]> #> 7 7 129 Fm 4/4 piano <list [8]> <list [8]> #> 8 8 129 Fm 4/4 piano <list [8]> <list [8]> play(song)
The intro is then played again, but this time we’ll add other instruments on top, let’s copy 4 additional bars only for now.
song <- copy_and_paste(song, bar_id %in% 1:4) song #> # A tibble: 12 x 7 #> bar_id tempo key meter instrument pitches durations #> <dbl> <dbl> <chr> <chr> <chr> <list> <list> #> 1 1 129 Fm 4/4 piano <list [8]> <list [8]> #> 2 2 129 Fm 4/4 piano <list [8]> <list [8]> #> 3 3 129 Fm 4/4 piano <list [8]> <list [8]> #> 4 4 129 Fm 4/4 piano <list [8]> <list [8]> #> 5 5 129 Fm 4/4 piano <list [8]> <list [8]> #> 6 6 129 Fm 4/4 piano <list [8]> <list [8]> #> 7 7 129 Fm 4/4 piano <list [8]> <list [8]> #> 8 8 129 Fm 4/4 piano <list [8]> <list [8]> #> 9 9 129 Fm 4/4 piano <list [8]> <list [8]> #> 10 10 129 Fm 4/4 piano <list [8]> <list [8]> #> 11 11 129 Fm 4/4 piano <list [8]> <list [8]> #> 12 12 129 Fm 4/4 piano <list [8]> <list [8]> play(song)
except instruments are added on top.
We add the bass at bars 9 to 12, we use the function add_bars() again but with the at argument this time since we’re not appending to the end.
The base line is very simple, we repeat for each bar an eighth note 8 times.
bass_bars <- list( replicate(8, "D#3", F), replicate(8, "A#2", F), replicate(8, "A#2", F), replicate(8, "F3" , F)) song <- add_bars( song, pitches = bass_bars, instrument = "bass", at = 9:12) song #> # A tibble: 16 x 7 #> bar_id tempo key meter instrument pitches durations #> <dbl> <dbl> <chr> <chr> <chr> <list> <list> #> 1 1 129 Fm 4/4 piano <list [8]> <list [8]> #> 2 2 129 Fm 4/4 piano <list [8]> <list [8]> #> 3 3 129 Fm 4/4 piano <list [8]> <list [8]> #> 4 4 129 Fm 4/4 piano <list [8]> <list [8]> #> 5 5 129 Fm 4/4 piano <list [8]> <list [8]> #> 6 6 129 Fm 4/4 piano <list [8]> <list [8]> #> 7 7 129 Fm 4/4 piano <list [8]> <list [8]> #> 8 8 129 Fm 4/4 piano <list [8]> <list [8]> #> 9 9 129 Fm 4/4 piano <list [8]> <list [8]> #> 10 10 129 Fm 4/4 piano <list [8]> <list [8]> #> 11 11 129 Fm 4/4 piano <list [8]> <list [8]> #> 12 12 129 Fm 4/4 piano <list [8]> <list [8]> #> 13 9 129 Fm 4/4 bass <list [8]> <list [8]> #> 14 10 129 Fm 4/4 bass <list [8]> <list [8]> #> 15 11 129 Fm 4/4 bass <list [8]> <list [8]> #> 16 12 129 Fm 4/4 bass <list [8]> <list [8]> play(song)
On top of the bass a guitar is joining, the process is similar but now we show how we deal with simultaneous notes (chords)
guitar_bars <- list( replicate(8, c("G4","A#4", "D#5", "G5"), F), replicate(8, c("F4","A#4"), F), replicate(8, c("F4","A#4", "C#4"), F), replicate(8, c("G#4","C5", "D#5", "D#5"), F)) song <- add_bars( song, pitches = guitar_bars, instrument = "guitar", at = 9:12) song #> # A tibble: 20 x 7 #> bar_id tempo key meter instrument pitches durations #> <dbl> <dbl> <chr> <chr> <chr> <list> <list> #> 1 1 129 Fm 4/4 piano <list [8]> <list [8]> #> 2 2 129 Fm 4/4 piano <list [8]> <list [8]> #> 3 3 129 Fm 4/4 piano <list [8]> <list [8]> #> 4 4 129 Fm 4/4 piano <list [8]> <list [8]> #> 5 5 129 Fm 4/4 piano <list [8]> <list [8]> #> 6 6 129 Fm 4/4 piano <list [8]> <list [8]> #> 7 7 129 Fm 4/4 piano <list [8]> <list [8]> #> 8 8 129 Fm 4/4 piano <list [8]> <list [8]> #> 9 9 129 Fm 4/4 piano <list [8]> <list [8]> #> 10 10 129 Fm 4/4 piano <list [8]> <list [8]> #> 11 11 129 Fm 4/4 piano <list [8]> <list [8]> #> 12 12 129 Fm 4/4 piano <list [8]> <list [8]> #> 13 9 129 Fm 4/4 bass <list [8]> <list [8]> #> 14 10 129 Fm 4/4 bass <list [8]> <list [8]> #> 15 11 129 Fm 4/4 bass <list [8]> <list [8]> #> 16 12 129 Fm 4/4 bass <list [8]> <list [8]> #> 17 9 129 Fm 4/4 guitar <list [8]> <list [8]> #> 18 10 129 Fm 4/4 guitar <list [8]> <list [8]> #> 19 11 129 Fm 4/4 guitar <list [8]> <list [8]> #> 20 12 129 Fm 4/4 guitar <list [8]> <list [8]> play(song)
Repeat the last 4 bars, with all instruments
song <- copy_and_paste(song, bar_id %in% 9:12) song #> # A tibble: 32 x 7 #> bar_id tempo key meter instrument pitches durations #> <dbl> <dbl> <chr> <chr> <chr> <list> <list> #> 1 1 129 Fm 4/4 piano <list [8]> <list [8]> #> 2 2 129 Fm 4/4 piano <list [8]> <list [8]> #> 3 3 129 Fm 4/4 piano <list [8]> <list [8]> #> 4 4 129 Fm 4/4 piano <list [8]> <list [8]> #> 5 5 129 Fm 4/4 piano <list [8]> <list [8]> #> 6 6 129 Fm 4/4 piano <list [8]> <list [8]> #> 7 7 129 Fm 4/4 piano <list [8]> <list [8]> #> 8 8 129 Fm 4/4 piano <list [8]> <list [8]> #> 9 9 129 Fm 4/4 piano <list [8]> <list [8]> #> 10 10 129 Fm 4/4 piano <list [8]> <list [8]> #> # ... with 22 more rows play(song)
We won’t finish the song, but we have some building blocks we’d be able to reuse already.
{gm} has some limitations at the moment, and {tidygm} has more, but I see potential in a tidy approach, it would be very easy for instance to filter out an instrument from the mix here, or to shift the pitches up or down with appropriate functions.
I’m also interested in playing with modes and backward melodies to do automatically things like :
https://www.youtube.com/watch?v=eGsthz78Ajo&ab_channel=Shred
https://www.youtube.com/watch?v=gj4JnOZ2Ees&ab_channel=Shred
https://www.youtube.com/watch?v=Sptr5yS5gAM&ab_channel=SteveCruickshank
I’m not sure if I’ll move along with {tidygm} but thought I’d at least get the idea out there.