Adventures in Hi-Fi
As I blogged earlier I am writing a game (and yes, it's pretty much playable already).
One thing I didn't mention is that I never wrote a game before. Yeah, I know everyone does it as one of his first projects, but I never did.
So, there are some things I really have no clue about [1], like sound and moving graphics around.
For the graphics stuff, QCanvas is just fine and dandy, but to make things bloop and warble and squeak when the time is right, I found Qt's sound support somewhat depressing.
Come on, NAS? Who uses that? And what about music? I had no idea.
So, I started trying to follow one of my leading principles of development: find a way to make it Someone Else's Problem (TM).
The usual way to do that is finding a library that handles the problem, write minimal glue, stick it to the side of the program, tell the program that's his new arm, and forget about it quickly.
Here's what I found.
Mi Dios!
I thought I should start by adding one of those annoying little tunes every game has. It's just a game tune, I don't want to have to include a 3MB OGG file for it, so I wanted an instrument-based format.
I remembered MIDI tunes. You may know them as ringtones nowadays, but they used to be just cheesy tunes generated by your SBPro's FM generator, not your phone.
In fact, I remember having a little proggie called playmidi, that would do that in Linux.
Well, it seems that in the past few years, either sound cards have forgotten how to play them, they fell out of fashion, or something, because the only things I found that could play MIDI are monstrosities that require a 9MB digital instrument set. And how was I to include that along with my 25KB game???
So, what's next? I had a C64, so...
MOD me up!
MOD files are like MIDI files, only the MOD includes it's own instrument set, called samples, and instructions on how to repeat and alter those samples to make a tune.
Good news: there are nice-sounding, funny MOD files that are about 30KB in size.
Better news: There is a popular library to play them! It's called Mikmod, and your distro has it (and it's a dependency for KDE's multimedia packages too).
Even better news: It has support for playing simple sounds (samples in mod lingo) by calling a couple of functions.
Awesome news: It includes a software mixer so you can just tell it to play this, then play that, then that, and a tune in the background, and everything sounds at the same time.
So, we have a winner. This baby can handle everything I need for the game!
But... is that a snake in your pocket?
I can't find a Python binding for it. I am sure as soon as I post this article someone is going to come up and tell me, here they are, moron! But I just can't find any.
So, I decided to do something I wanted to do already and learn to use Pyrex. Pyrex is a tool to write python extensions, with almost-free access to C libraries, in an almost-python language (only minor syntax differences).
That way, I could write a Python module to use Mikmod.
You know what? It was almost scarily simple [2]. I didn't wrap all of Mikmod [3] because I don't need it, but now I can do stuff for games and apps almost trivially.
Even more: Pyrex has awesome distutils support, so building the extensions, usually a pain in the rear, is trivial (mostly you just copy and delete stuff, with some search and replace).
One thing I found I did nicely is this: Mikmod requires you to call Mikmod_Update every once in a while so it fills the soundcard's buffer with stuff to play. If you don't, it skips.
So, I just started a thread that loops and takes care of it. You don't even have to know about it to use the extension. Oh, sure, if your Mikmod is not threadsafe, it breaks. Well, get a decent Mikmod package, then.
How does it look?
Here's a whole noisy proggie
#Load the modules import mikmod, time #Init the library mikmod.init() #40 voices, 20 for music, 20 for random sounds (overkill) mikmod.setNumVoices(20,20) #Enable sound, starts the thread that pushes sound, too mikmod.enableOutput() #Create a module, that is, a music track module=mikmod.Module("BasicInstinct.mod") #Load two samples, just a couple of noises s1=mikmod.Sample("lost.wav") s2=mikmod.Sample("swap.wav") #Start playing the song module.play() #For the duration of the song, each second, make some noise while module.active(): s1.play() time.sleep(0.5) s2.play() time.sleep(0.5) #Close the mikmod library, stop the thread, etc. mikmod.exit()