Index ¦ Archives ¦ Atom

Haskell Spectrograms 1: Loading a Sound File’

Spectrogram of a short string loop

Intro

So I took it up as a challenge to generate a spectrogram in Haskell. How hard could it be? Turns out that what’s a few lines of python code using numpy and matplotlib took me down quite a rabbit hole.

For the impatient, you can find the finished code at https://github.com/ssfrr/scimitar in the “haskell” directory.

Disclaimer: I am not an experienced Haskell programmer, and I’m likely doing things all wrong. Please correct me via Twitter

hsndfile

I chose to use the hsndfile wrapper around Erik de Castro Lopo’s immensely useful libsndfile to load the file. This immediately forced me into the abundance of vector libraries for Haskell. This was a huge sticking point for me. There are literally about a dozen different libraries that one can use in Haskell to represent a vector of floats (or vectors of anything storable, but I digress).

Luckily hsndfile only has bindings to 2 of them, which cut down my options. I ended up using hsndfile-storablevector, which is available for installation through cabal. Loading a sound using hsndfile looks a little like:

import qualified Sound.File.Sndfile as Snd
import qualified Sound.File.Sndfile.Buffer.StorableVector as BV
import qualified Data.StorableVector as V

readWavFile :: String -> IO (V.Vector Double)
readWavFile fileName = do
   handle <- Snd.openFile fileName Snd.ReadMode Snd.defaultInfo
   (info, Just buf) <- Snd.hGetContents handle :: \
      IO (Snd.Info, Maybe (BV.Buffer Double))
   return (BV.fromBuffer buf)

To unpack this a little Snd.openFile takes in a filename, a mode, and an Info instance (check this out for more details on the individual datatypes), and gives you a handle to the file (wrapped up in an IO monad, of course).

Snd.hGetContents takes a handle and gives you a IO-wrapped pair. The first item is another Info instance that describes the soundfile you just read, and the second might be a buffer (or Nothing). The tricky bit here is that Haskell doesn’t know what type that it’s supposed to be, so you need to make the type explicit with the annotation shown above to make it a buffer of Doubles. The cool thing here is that if the datatype you request does not match the datatype of the original sound, hsndfile will do the conversion for you.

Once you have the buffer of doubles, you use the fromBuffer function to get back a normal StorableVector.Vector that you can do other fun things with. Come back next time for some of those fun things!

© Spencer Russell. Built using Pelican. Theme by Giulio Fidente on github. .