The world’s leading publication for data science, AI, and ML professionals.

How to play Star Wars and other tunes in R

Make the sine waves for the Star Wars theme and have R play them on your media sampling device.

Photo by Liam Tucker on Unsplash
Photo by Liam Tucker on Unsplash

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 the pitch notation which octave the note is to be played in
  • note which extracts the raw note value from our original notes vector and then adjusts it for sharp, flat and octave variants
  • freq which converts note 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._


Related Articles