Sometimes my R processes take a long time and I can’t sit at my PC to see the moment it all finishes. I thought it would be cool to compose a choice of triumphant tunes to put at the end of my scripts, so that when the process executes successfully I can hear something that tells me everything went well.
So I explored the audio
package in R, which allows you to create and play sounds by interfacing with sample-based audio devices on your machine, and to save them as wave files. Strings of text and numbers can be manipulated into frequency values and then, combined with data on note length, into sine waves. This will then make a neat little tune that can play in your R session or on any media device.
Writing the Star Wars theme
Let’s start by loading the necessary packages:
library(dplyr)
library(audio)
Let’s also define values for notes in an octave. For this you need to think in semitones, to allow for flats and sharps which will be defined later:
notes <- c(A = 0, B = 2, C = 3, D = 5, E = 7, F = 8, G = 10)
Now lets write the notes for the Star Wars theme, ignoring the length of each note. You can do this from sheet music if you can read it. You can organize this in any way you wish in the format of a character vector as we will clean it up later. I decided to write a string for each bar, with notes separated by spaces within strings.
We will set the code up later to understand the symbol #
as a sharp and b
as a flat to make things intuitive for musicians.
We will need to separate octaves in the music. Later I will set up my code to interpret a plain note, eg A
as being in the fourth octave, and any other octave is denoted by putting the octave number after the note (eg A5
or B3
)
pitch <- paste("D D D",
"G D5",
"C5 B A G5 D5",
"C5 B A G5 D5",
"C5 B C5 A D D D",
"G D5",
"C5 B A G5 D5",
"C5 B A G5 D5",
"C5 B C5 A D D",
"E E C5 B A G",
"G A B A E F# D D",
"E E C5 B A G",
"D5 A D D",
"E E C5 B A G",
"G A B A E F# D5 D5",
"G5 F5 D#5 D5 C5 A# A G",
"D5 D D D",
"G D5",
"C5 B A G5 D5",
"C5 B A G5 D5",
"C5 B C5 A D D D",
"G D5",
"C5 B A G5 D5",
"G5 F5 D#5 Bb5 A5",
"G5 G G G G")
And then you can write a numeric vector with the length of notes, with one beat represented by numeric 1
. To keep my notation clear, I try to keep to one line for every bar.
duration <- c(0.33, 0.33, 0.33,
2, 2,
0.33, 0.33, 0.33, 2, 1,
0.33, 0.33, 0.33, 2, 1,
0.33, 0.33, 0.33, 2, 0.33, 0.33, 0.33,
2, 2,
0.33, 0.33, 0.33, 2, 1,
0.33, 0.33, 0.33, 2, 1,
0.33, 0.33, 0.33, 2, 0.75, 0.25,
1.5, 0.5, 0.5, 0.5, 0.5, 0.5,
0.33, 0.33, 0.33, 0.75, 0.25, 1, 0.75, 0.25,
1.5, 0.5, 0.5, 0.5, 0.5, 0.5,
1, 2, 0.75, 0.25,
1.5, 0.5, 0.5, 0.5, 0.5, 0.5,
0.33, 0.33, 0.33, 0.75, 0.25, 1, 0.75, 0.25,
0.75, 0.25, 0.75, 0.25, 0.75, 0.25, 0.75, 0.25,
3, 0.33, 0.33, 0.33,
2, 2,
0.33, 0.33, 0.33, 2, 1,
0.33, 0.33, 0.33, 2, 1,
0.33, 0.33, 0.33, 2, 0.33, 0.33, 0.33,
2, 2,
0.33, 0.33, 0.33, 2, 1,
0.33, 0.33, 0.33, 2, 1,
1, 0.33, 0.33, 0.33, 1)
Now we have all the raw materials we need.
Converting the notes to frequencies in sine waves
First we convert our pitch and duration vectors to a data frame where every note is an element that has pitch and duration. In the case of the Star Wars theme, there are 126 notes in total.
starwars <- data_frame(pitch = strsplit(pitch, " ")[[1]],
duration = duration)
Now we will extend this dataframe to include some new columns:
octave
which extracts from thepitch
notation which octave the note is to be played innote
which extracts the raw note value from our originalnotes
vector and then adjusts it for sharp, flat and octave variantsfreq
which convertsnote
into a frequency in Mhz
starwars <-
starwars %>%
mutate(octave = substring(pitch, nchar(pitch)) %>%
{suppressWarnings(as.numeric(.))} %>%
ifelse(is.na(.), 4, .),
note = notes[substr(pitch, 1, 1)],
note = note + grepl("#", pitch) -
grepl("b", pitch) + octave * 12 +
12 * (note < 3),
freq = 2 ^ ((note - 60) / 12) * 440)
We will now define a function that converts a note and duration into a sine wave. We will set a tempo in beats per minute and a sample rate (usually 44.1Khz) to do this:
tempo <- 150
sample_rate <- 44100
make_sine <- function(freq, duration) {
wave <- sin(seq(0, duration / tempo * 60, 1 / sample_rate) *
freq * 2 * pi)
fade <- seq(0, 1, 50 / sample_rate)
wave * c(fade, rep(1, length(wave) - 2 * length(fade)), rev(fade))
}
Finally we just apply the function to our freq
and duration
columns in the starwars
dataframe, and then simply play the tune.
starwars_wave <-
mapply(make_sine, starwars$freq, starwars$duration) %>%
do.call("c", .)
audio::play(starwars_wave)
You can also save the tune as a .wav
file for you use as you like, with
audio::save.wave(starwars_wave, "starwars.wav")
All the code you need is here for you to try this, but here’s the code link in GitHub if you want to pull it and play around with other tunes. Feel free to push your tunes back to the same GitHub repo in separate R files for the benefit of others to enjoy.
_Originally I was a Pure Mathematician, then I became a Psychometrician and a Data Scientist. I am passionate about applying the rigor of all those disciplines to complex people questions. I’m also a coding geek and a massive fan of Japanese RPGs. Find me on LinkedIn or on Twitter._
