Vault · archived work, not current thinking

WaveQueue

1994

A shell lands while the chopper is still overhead and a ricochet is ringing off a wall. Three sounds, one moment. Windows 3.1 gave you one wave output device and no way to mix, so the second sound either waited or stepped on the first, and either way the game sounded broken.

We hit this after the hard part was supposedly done. The animation worked. The tanks drove. Then we went looking for the sound layer and there wasn’t one, in the operating system or from anybody selling libraries. The manual I wrote later that year opens with the whole situation in about eighty words:

A little over a year ago we set out to write an action game for Windows that would set a new standard for Windows entertainment based on solid gameplay, nice animation, and quality sound effects. After writing all of our own animation routines from scratch we were eager to add sampled sound effects and finish the game. What we found out after a few weeks of searching was that the standard Windows Multimedia Extensions did not support any methods of mixing digitized wave sounds and furthermore there wasn’t any third party support either. This left us with two options: forget about sound effects or write our own mixer routines. We decided on the latter and they became WaveQueue.

Eleven functions, a 5.8K DLL, four sounds playing at once with four more waiting behind them.

The mix is an average

The mixer holds two 2048-byte buffers and refills whichever one the sound card has finished with. For each byte of output it walks the active channels, reads one sample from each, advances the pointers, and writes a single byte back. That byte is the entire mixing algorithm:

*lpD = nSum ? (BYTE)(wSum/nSum):0x80;

Divide by the number of channels playing. It’s an average, so the sum can never overflow a byte and there is nothing to tune. It also means every sound gets quieter the instant another one starts. Four tanks firing at once are each a quarter as loud as one tank firing alone. I don’t remember deciding that was acceptable, and it plainly was, because it shipped that way in a game that sold thirty thousand copies.

The 0x80 is silence. Eight-bit PCM is unsigned, so the middle of the range is the quiet part and zero would be a hard negative rail — a click on every gap.

Looping lives in the pointers. There is no repeat flag anywhere in the file. Each channel carries a start, a current, and an end; the start pointer is null for a one-shot and non-null for a repeat. When the current pointer walks off the end, a non-null start rewinds it and a null one retires the channel, and the retiring case shifts every channel above it down a slot so the live ones stay packed at the front of the array. One conditional handles both the loop and the ending. The chopper in Combat Tanks is that non-null branch: a short sample, set to repeat, killed by name when the helicopter leaves.

What the manual was for

The manual is perhaps the more interesting part now. Ten pages of Word 2.0, formal argument tables, a note that waves are not standard resources so you declare them as user-defined type WAVE in your RC file. Two guys in a college house wrote an API reference for a DLL, with a section explaining that the shareware version handles 8-bit mono only, “however, we are working at expanding the available types for a commercial version of WaveQueue.” We were going to license this. Somebody was going to pay us for the sound layer.

The manual advertises 22kHz. The source, dated five weeks later, initializes the format chunk at 11025 samples per second and never mentions it. One of those is wrong and I would bet on the manual, written in August against a library that got cut in half by September.

The demo app that shipped with it is here too, and it is the more interesting archaeology: EXPLODE.WAV, GUNFIRE.WAV, RICOCHET.WAV, WOUNDED.WAV, all four dated October 6, 1993, ten months before the mixer they were made to prove. Two of them are still in the browser rebuild under the same names. Push the buttons in the test app and you are firing the same shells at nothing at all.

Combat Tanks has a GrObQueue in its graphics code — a fixed array of drawable objects, packed at the front, walked once per frame. Same shape, same year, different medium. Whatever else Randy and I were, we were two people who solved a problem twice by putting things in a queue and dividing by however many showed up.

The library

← Back to tools