Sound effects in FocusWriter
October 21, 2010 13 Comments
Man, Linux audio is rough. I just added typewriter sound effects to FocusWriter, and what took the longest amount of time was figuring out how to play sounds in Linux. A big part of the problem was that I wanted to use Qt to play the sound effects, because I am not interested in writing platform specific code for playing audio. I also did not want to add more dependencies.
I started by looking at QSound, the audio API in Qt that has been around forever. It is essentially what I need, although it does require loading multiple copies of the sound file into memory. Since they are small files I figure that it isn’t too bad. Unfortunately, the only platform that QSound really works well on is the Mac. On Linux is uses NAS, which nobody has installed anymore (and doesn’t even work for me). And on Windows it chopped the sounds off (partly because the isFinished() function doesn’t work, and partly because it doesn’t want to mix the sounds even if I just used the static QSound::play() function).
So I moved on to Phonon. Ah, Phonon. While a good idea in principle, in practice it ends up making me want to tear my hair out. To start with, none of the backends worked very well for me. GStreamer played the sounds, but eventually stopped playing sound and reported no errors. Xine always played the sounds, but they sounded truly awful. VLC was a mixture of both in that it randomly stopped playing the sounds and it sounded bad. Plus, the latency of Phonon is crazy. There was a large delay whenever I created a MediaObject (which I can do at to the launch of FocusWriter, sure, but then it takes 15 seconds to launch!), and then there was a random delay before Phonon started playing a sound effect.
The last option was QAudioOutput. I had to write a WAV file reader, but that is a simple file format and it took very little time to do so. There was no latency, and it allowed me to share the audio buffers between sound effects. I thought I had finally figured out how I was going to play the sound effects! And then FocusWriter hung. Randomly. With no warnings and no errors. After a random amount time after launch, it would just stop working. From what I have been able to find out it looks like it is a problem with how QAudioOutput is using ALSA. So, definitely not an option.
I finally gave up using on Qt and went looking for other solutions, even though it meant adding a new library dependency. There really aren’t that many cross platform open source audio libraries, at least that I could find. If I’m wrong, please don’t hesitate to correct me! I could’ve used OpenAL or SDL_mixer, but requiring game libraries seems very strange to me. Then I happened across libao, which is a gem of a library!
There are many things to like about libao: it has a very basic API (good if all you want to do is fire off a sound effect and then forget about it), and it works across many platforms and backends. You want to allow users to use OSS, or ALSA, or PulseAudio? It’s got you covered! No need to write specific code for each of them in your program. There are a few minor downsides, though. First, it is a blocking API, so I have to create a separate thread for each sound effect (I share them, of course, so you only end up with a few extra threads overall). Second, when using OSS version 3 as the backend it won’t mix the sounds, so you hear one full keypress at a time. Lastly, I couldn’t get it working on the Mac. I still had the QSound code, so I use that on the Mac and I use libao everywhere else.
Long story short, FocusWriter now plays typewriter sound effects as you type. If you don’t want them, you can easily turn them off in the preferences dialog. I am debating whether or not they should be on by default (I probably won’t use them myself, but it is a highly requested feature).