Saturday, January 29, 2011

I found the perfect project to dive into Haskell

I've had a recurring project in my life, a modular software synth. See here to get an idea of what I'm doing. The difference being that with software synths, you're not limited to how many components you have and how they're configured. I somehow thought I came to this revelation on my own years ago, but there's plenty of this sort of thing out there, such as SuperCollider, which sounds like it's fairly popular.

I've been trying to get into Haskell, but have been struggling to get myself out of my Python comfort zone. A friend of mine actually told me about SuperCollider recently, and I realized that my own synth would be the perfect project to get me started on Haskell, and one day last week I got inspired to get started. I found a simple example to start with of a sine wave being played through Pulse Audio and I went from there. Here's my repo

Unfortunately, you need PulseAudio to run this. I'm working on either getting Alsa output or file generation working soon.

When I was making this a few times before, I made it in C++, the latest instance being several years ago. Amazingly enough, despite still being a novice in the language, I found that doing this in Haskell is easier. The infinite lazy lists work perfectly as signals. Before I considered each component to be an object that had a value, and input signals. And I had to have a global "tick" that conveyed info between items. (And I thought that was really neat at the time.) Now I just have components be functions that "output" (return) infinite lists, and take infinite lists as inputs. It all sortof just sorts itself out.

Speed is sacrificed to be sure, at least so far, but real-time synths have been made for Haskell, so I bet I can profile it and optimize it significantly.

Also you will notice that everything is hard coded! I sortof like it that way, it's amusing, particularly when it starts making beat sequences (which is a point I got to in my old version), but I'll probably make an interface at some point. Or maybe not, Haskell is a nice interface.

Here's why I'm writing now of all times though. On top of being functional with the lazy infinite lists and such, Haskell also has a type system from Nazi Germany. This is actually an advantage, though. I have some trouble remembering all the unit conversions involved in the oscillators, when I'm dealing with cycles, seconds, and samples. So today, I made a type framework that provided functions that did the conversions properly. When I was writing out an improved version of my oscillator function, I used these types.

It took me a long time to figure out exactly how I wanted it all to work, and how to make it work. I would start on an expression, and then realize that I was adding different units, and Haskell wouldn't let me do it. Or sometimes the compiler told me so. But eventually I got through it. This is the monstrosity that resulted.

And the kicker: I used this to make a new version of the square wave oscillator, and it sounded exactly the same as the old one, the first time I ran it.

No comments: