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
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!