Detecting Pitches in music with R

September 2, 2012
By

(This article was first published on Gary Sieling » R, and kindly contributed to R-bloggers)

In a previous post, I described a method to detect a chord using a Fourier transform in Java/Scala. I’ve re-implemented the same in R, detailed below.

This will generate an audio file containing the C-Major chord:

library(sound)
c<-261.63
e<-164.81
g<-196
len<-1
cData<-Sine(c,len)
eData<-Sine(e,len)
gData<-Sine(g,len)
audio<-normalize(cData+eData+gData)
saveSample(audio, "out\\ceg.wav", overwrite=TRUE)

And a series of helper functions:

magnitude<-function(x) { sqrt(Re(x) * Re(x) + Im(x) * Im(x)) }
maxPitch<-audio$rate/2-1
frq<-c(16.35,17.32,18.35,19.45,20.60,21.83,23.12,24.50,25.96,27.50,29.14,30.87)
noteNames<-c("C", "C#0/Db0", "D0", "D#0/Eb0", "E0", "F0", "F#0/Gb0", "G0", "G#0/Ab0", "A0", "A#0/Bb0", "B0")
lookup<-data.frame(noteNames,frq)

A function to find the middle frequency in each FFT bucket:

idxFrq<-function(idx) { (idx - .5 ) }

And a function to find the closest note in the lowest octave:

noteDist<-function(note1, note2) { abs(log(note1)/log(2) - log(note2)/log(2)) %% 1 }

Now to do the actual FFT:

fftdata<-fft(audio$sound)
half<-fftdata[1:maxPitch]
indexes<-c(1:maxPitch)
magnitudes<-magnitude(half)

Now, from each bucket, find the note that is closest to each bucket:

closest<-function(x){ subset(lookup, select=c(noteNames,frq),subset=(min(noteDist(frq, x)) == noteDist(frq, x)))$noteNames }
noteList<-mapply(closest, indexes-0.5)
mag<-data.frame(noteList, magnitudes)
barplot(by(mag$magnitudes, mag$noteList, sum))

This results in the following image, which has peaks at C, E, and G, as expected.

The calculation is very crude – all frequencies are mapped to a note evenly, which isn’t really correct.

This can be adjusted by de-emphasizing the fft buckets between notes:

scale<-function(x){ .5 + .5 * sin(pi * x*2 + pi/2) }
note<-function(x) { 12 * log(x/440)/log(2) + 49 }
scaleData<-scale(note(idxFrq(indexes)))
scaledMagnitudes<-scaleData*magnitudes
scaledMag<-data.frame(noteList, scaledMagnitudes)

This looks better – the three highest are the notes in the chord-

To leave a comment for the author, please follow the link and comment on his blog: Gary Sieling » R.

R-bloggers.com offers daily e-mail updates about R news and tutorials on topics such as: visualization (ggplot2, Boxplots, maps, animation), programming (RStudio, Sweave, LaTeX, SQL, Eclipse, git, hadoop, Web Scraping) statistics (regression, PCA, time series, trading) and more...



If you got this far, why not subscribe for updates from the site? Choose your flavor: e-mail, twitter, RSS, or facebook...

Comments are closed.