This tutorial walks the user through the creation of spectrographic images of .wav files of birdsong using R.
Make a folder on your desktop titled “spectro” and place your .wav files there. Then open RStudio and set your working directory to that folder:
setwd("C:/Users/Shelby Palmer/Desktop/spectro")
Next, you need to install the packages we will use to handle the sound files and create the spectrograms. I commented out these lines of code (added # symbols at the beginning so that they would not run) because these packages are already installed on my computer. For your purposes, remove the # symbols to run the code.
# install.packages("seewave")
# install.packages("tuneR")
library(tuneR)
library(seewave)
We are now ready to begin working with the sound files.
First you need to import one of the sound files into your R
environment to begin working with it. This is done using the function
readWave() and the name of one of your sound files, as
shown below:
readWave("XC350304_Black-capped-chickadee.wav")
##
## Wave Object
## Number of Samples: 1530938
## Duration (seconds): 34.72
## Samplingrate (Hertz): 44100
## Channels (Mono/Stereo): Stereo
## PCM (integer format): TRUE
## Bit (8/16/24/32/64): 16
Shortcut: By running this line of code
list.files(pattern = ".wav")
## [1] "XC350304_Black-capped-chickadee.wav" "XC742516_White-eyed-vireo.wav"
## [3] "XC814764_Indigo-bunting.wav"
you can get an output of all of the file names in your working
directory that are .wav files. Then you can just copy-paste one into the
parentheses of the readWave() function. Or, if you want to
be more elegant, running the line of code below
readWave(list.files(pattern = ".wav")[1])
##
## Wave Object
## Number of Samples: 1530938
## Duration (seconds): 34.72
## Samplingrate (Hertz): 44100
## Channels (Mono/Stereo): Stereo
## PCM (integer format): TRUE
## Bit (8/16/24/32/64): 16
will give you the same result as copy-pasting the first file name
(because the [1] after list.files() grabs just
the first entry in the list of file names).
We’ve successfully read a sound file into R now, but we can’t
manipulate it unless we give it its own name in R. We need to make it
into an R object. We can do that using the <- symbol,
and by giving the .wav file any name we want (I’m going to go with the
abbreviated name of the Black-capped chickadee).
bcch <- readWave("XC350304_Black-capped-chickadee.wav")
We will now use bcch anytime we want to do something
with this object in R. To make sure it is the right type of file, we can
run
class(bcch)
## [1] "Wave"
## attr(,"package")
## [1] "tuneR"
We want it to be an object of class "Wave".
Most sound files have some amount of low-level ambient noise that we
may want to get rid of so it doesn’t end up in our spectrogram. We can
filter out certain frequencies of sound that we know don’t include our
target sound using the function fir(). Most functions
require that you provide certain specifications (called “arguments”) so
that they can customize their functionality to your needs. You can
figure out what arguments fir() requires by running
?fir in your console, which will pull up the
documentation page (basically a mini how-to manual).
We will need to give fir()
bcch)bcch <- fir(wave = bcch,
from = 2000, # lower bound frequency in Hz
to = 6000, # upper bound frequency in Hz
bandpass = TRUE,
output = "Wave")
Notice that we named the filtered version of bcch also
bcch. This overwrites the original unfiltered
bcch recording, since we don’t need it anymore. Some people
advise against this, but I’d rather do this than clutter up my workspace
with bcch2, bcch3, etc. Personally I hate
having a million R objects that I can’t remember all the names of. To
each their own.
Now we are ready to start actually looking at our sound file using
the function spectro(). Let’s begin by running
spectro() on our sound file without specifying any
arguments and seeing what happens.
spectro(wave = bcch)
Cool, there are all the Black-capped chickadee songs. But they are
kind of tiny and hard to see. The first thing you may notice is that the
frequency (y) axis goes all the way up to 20000 Hz/20 kHz, way higher
than we need for this species. We can fix that by adding an argument
flim in spectro(). And let’s also get rid of
the amplitude scale bar while we’re at it (because I think it’s
unsightly).
spectro(wave = bcch,
flim = c(0,8), # frequency limits in kHz
scale = FALSE)
fir(), but the wording
required by these two different functions is extremely different:
from = [X], to = [Y] in fir(), and
flim = c([X],[Y]) in spectro(). That’s one of
those pain-in-the-ass things about R: different functions don’t always
agree on how to ask you, the R user, for essentially the same
thing…which can result in cryptic errors and unnecessary stress. The fix
for this is to always use ?[function name] to make sure
you’re getting the wording of the arguments right.)Now we have a more sensible frequency axis, but we can’t get a very
good idea of the shape of the notes because they’re so squished on the
time (x) axis. What if we looked at just 3 of the songs? We can see from
looking at the previous spectrogram that the third song ends at probably
8-ish seconds, so let’s use the tlim() argument to trim it
at 10 seconds.
spectro(wave = bcch,
flim = c(0,8), # frequency limits in kHz
tlim = c(0,10), # time limits in seconds
scale = FALSE)
And now let’s go down to a single song
spectro(wave = bcch,
flim = c(0,8), # frequency limits in kHz
tlim = c(0,2), # time limits in seconds
scale = FALSE)
There are a lot of other arguments in spectro() that you
can change to your liking. Mess with them if you want until you get an
image you really like. It’s especially fun to mess with the color of the
signal traces using the argument palette. The options
are:
temp.colors
reverse.gray.colors.1
reverse.gray.colors.2
reverse.heat.colors
reverse.terrain.colors
reverse.topo.colors
reverse.cm.colors
terrain.colors
topo.colors
cm.colors
And here are my personal favorite settings to use for publication-quality images.
spectro(bcch,
wl = 512, # window length (determines frequency resolution)
ovlp = 90, # percentage window overlap
collevels = seq(-30,0,5), # sets amplitude threshold and gradient levels for color
flim = c(0,8), # will change based on species
tlim = c(0,2), # will change based on species
scale = F, # get rid of scale
colgrid = "gray", # makes the background grid gray
palette=reverse.gray.colors.2 # makes the signal traces black & white
)
To export your spectrogram to your working directory (the “spectros”
folder), you need to use the function png(). It’ll ask you
for an output file name, a width measurement, and a height measurement
(the measurement unit default is pixels, but you can change that to
inches or centimeters if you prefer. I use inches, which then requires
you to provide a resolution argument, for which I use 1000 for a nice
crisp image). Then you can copy-paste your spectro()
function and all its arguments below the png() line. And
then below that, you’ll put the function dev.off(). What
happens when you run these three lines sequentially–png()
opens an empty .png object in R, spectro() creates an image
to fill it, and dev.off() basically kicks the image out of
your R environment, so it ends up getting saved to your working
directory. Don’t ask me for any more detail than that!
png(filename = "bcch_spectro.png",
width = 12,
height = 8,
units = "in",
res = 1000)
spectro(bcch,
wl = 512,
ovlp = 90,
collevels = seq(-30,0,5),
flim = c(0,8),
tlim = c(0,2),
scale = F,
colgrid = "gray",
palette=reverse.gray.colors.2
)
dev.off()
## png
## 2
And that’s it!! Check the “spectros” folder for your image; it should
be in there, and if it’s not the way you wanted it, mess with the
arguments in png() and spectro() until it’s to
your liking. I’ve also given you songs of the White-eyed vireo and the
Indigo bunting to mess around with if you want. Hope you enjoyed!!