Thursday, December 10, 2009

What the bleep?! Sounds and Music in the Z-machine

Before I get to the topic of this post, I'll tell you where I got the idea for my blog's name. When I was trying to decide on a title, I knew I wanted something which was relevant to my project, but also was reminiscent of Infocom. It didn't take long to find. After I made my choice, I felt it was particularly apt considering my goal is to recreate the Z-machine, which runs quite possibly the most 'classic' games of all, using a modern language and object oriented design principles. So how is it related to Infocom? My favorite Infocom game is 'The Lurking Horror'. In it, you play a college student at G.U.E. Tech who is trying to finish an assignment at the last minute in the campus computer lab during a winter snowstorm. You begin the game in the lab holding an assignment. If you read the assignment (which is not needed to win the game), you'll find the name of the course is "The Classics in the Modern Idiom".

Even if you are an old pro at Infocom's text adventures, sound is not likely to come to mind when thinking about them. That's because few Infocom games actually made sounds at all. Of the thirty or so games Infocom released just two had sound effects: The Lurking Horror and Sherlock. To make matters worse, only selected versions of those two games actually played sound effects. Beyond this there were nine other games which could make simple beeping sounds referred to as 'bleeps' in the Z-machine standards document. Long after the age of Infocom came to a close, the Z-machine was extended to play music as well, but we'll get to that a bit later.

All sound effect manipulation in the Z-machine, including loading, playing, stopping, and unloading is done using the seriously overloaded sound_effect opcode #245. A remark at the bottom of the table of opcodes:

The notation "5/3" for sound_effect is because this plainly Version 5 feature was used also in one solitary Version 3 game, 'The Lurking Horror' (the sound version of which was the last Version 3 release, in September 1987).

I take issue with that statement because several version 4 games also make use of the sound_effect opcode, including 'Trinity', 'A Mind Forever Voyaging', and 'Bureaucracy'. What I think the author of the standard was getting at is only one version 3 game played sampled sound effects. It would be a mistake to fail to include support for the sound_effect opcode in version 4 games.

The sound_effect opcode is rather messy to implement due to the way its operands are handled. It can take 1 to 4 operands. The first operand is the sound effect number. A value of 1 indicates a high pitched beep should be played, while 2 indicates a low pitched one. Values 3 and higher indicate sampled sound effects and require additional operands. A value of zero means all sounds and is allowed only if a second operand is provided and has a value of 3 or 4 which means stop all sounds and unload all sounds respectively (this was clarified in the 1.1 standard). If a sampled sound effect is indicated, a second operand is required. The additional operand has the following meanings: 1:load, 2:play, 3:stop, and 4:unload. Values 1 and 4 are merely hints as to what the game needs at any given time and may be implemented in any convenient way. If the second operand is 2 (play) then a third operand is required. The third operand's lower byte contains the volume to play the sound at. From version 5 onward, the upper byte contains the number of times to play the sound. In the single version 3 game with sampled sounds the sound either played once or looped forever until stopped. This looping information was contained in the sound file itself and not in the game code. Starting with version 5 a fourth operand can be supplied containing the address of a routine to be called when the sound completes but not if it is stopped prematurely by another sound being started.

In addition to the operand madness above, the behavior of playing sampled sounds can be complicated as well. Originally, it was meant to be simple: only one effect can play at a time and starting another will terminate a currently playing one. This becomes more complicated today because 'The Lurking Horror' plays several sounds in sequence. Computers at the time of the game's release would be slow enough to play each sound, but faster computers will cut off the sounds prematurely, so we need a mechanism to prevent this. Another complication is that the 1.1 update to the standard introduced the concept of 'music' as a second sound channel. Sound effects only interrupt other sound effects and music only interrupts music. When we play a sound, the channel is chosen using the file type of the sampled sound itself...yuck. It would have made far more sense to introduce a new opcode at this point, instead of complicating the behavior of sound_effect. The effort required to introduce a new opcode into existing interpreters is far less than the work required to support the new music file types needed and so would not have been an additional hardship on interpreter writers. One could make the argument that the chosen method did not require any updates to Inform to create games supporting music, but if you are going to do something, then I think it is worth doing it right.

In my implementation, I provide the full sound_effect capabilities from version 3 onward***. This includes the fourth operand which is restricted to version 5 and higher in the standard because the version 5 game Sherlock is the only Infocom game ever to use it. It also includes the 1.1 music extension which does not specify supported versions. I felt that the extra work to exclude features from version 3, then restrict them even further in version 4 only to reintroduce them in version 5 to be excessive considering all the existing games are compatible with the notion that these capabilities existed from version 3 onwards. The extra capabilities simply go unused in current version 3 and 4 games.

Next time...we'll see some code.

***Update: I've changed my mind on this (see next post).