Making the silences feel eerie with carefully sparse sound effects
To be fair, the BBC Micro's sound system isn't its greatest strength. It does a reasonable job, but compared to contemporaries like the Commodore 64, it's a bit simple. But for the Sentinel, simplicity is the right choice, because it's not about the sound effects so much as the eerie silences between.
Indeed, you could argue that The Sentinel is a bit of a pioneer in its use of negative space in its soundscape. During normal gameplay, there is little more than the regular "chug" of the enemy's rotation, but that sound is laced with nail-biting potential, as any one of those rotations can suddenly bloom into the siren-like sound of the scanner, at which point you know you've been found and the panic sets in. It's very clever, yet very simple.
There are six sound effects in The Sentinel, one of which is allocated to the game's music. The music is absolutely brilliant, and accompanies major game events like transferring to a new robot or completing a landscape; it's so impressive that it has its own deep dive, but we'll cover it briefly here too.
The sound effects split into two groups: simple sound effects, and more complex sound effects that need processing. The simple sound effects are as follows:
| Number | Description | Sound |
|---|---|---|
| 0 | Enemy rotation | |
| 1 | Meanie rotation | |
| 2 | Create or absorb an object | |
| 5 | Ping (for errors) |
Simple sound effects can be made by calling the MakeSound routine and passing in the sound number. This routine sets the correct sound envelope for the specified sound effect by calling DefineEnvelope, and then it looks up the relevant sound commands to make for the specified sound, which are defined in the soundNumberData and soundData tables. The sounds themselves are made by the MakeSoundEnvelope routine, which calls the operating system's OSWORD routine to make the sound (this call is the assembly language equivalent of BBC BASIC's SOUND instruction).
The rotation sounds are both made in two parts, with each part on a different channel; the first part is on channel 0 (the noise channel), while the second part is on channel 1 (one of the tone channels). This gives the rotation sounds a pitch as well as a white noise effect.
For more complex sound effects, there is an additional layer that implements a basic sound effects processor. These are the sound effects that use the sound processor:
| Number | Description | Sound |
|---|---|---|
| 3 | Music (see music deep dive) | |
| 4 | Scanner | |
| 6 | Game over |
The sound processor enables these sounds to be managed and manipulated over a longer period of time. So, for example, the game over sound can decay from a higher pitch to a lower pitch while the game over screen is being displayed, or the scanner sound can be repeated so it doesn't end until the scanning stops. Note that the scanner example above starts with an enemy rotation sound, and the scanner is punctuated by pings as energy is sapped from the player, so this example actually includes two types of simple sound (rotation and ping) and one complex sound (scanner).
Unlike the simple sounds, the sound processor effectively works in the background. Sounds are processed by the ProcessSound routine, which is called regularly throughout the code. Note that this routine isn't called by the interrupt handler, which is perhaps a surprise for a task that needs to be run regularly; instead, the routine is called manually from eight different places that are dotted throughout the code, as follows:
- DecayScreenToBlack
- DitherScreenBuffer
- DrawLandscapeView (Part 2 of 3)
- DrawTileAndObjects
- FinishLandscape
- GetRowVisibility (Part 1 of 2)
- MainGameLoop (Part 1 of 2)
- ProcessGameplay
This approach ensures that sound is only processed in a controlled manner, so it doesn't prevent time-critical code from being run efficiently, though it can affect the sound timings if the system is particularly busy. For example, here are two recordings of the hyperspace music, and in the second one you can clearly hear it missing a beat in the fourth chord:
| Description | Music |
|---|---|
| Hyperspace | |
| Hyperspace (with a blip) |
Given how little sound there is in the game, this prioritisation of game code over sound effects is perhaps understandable, as you wouldn't want the sound effects to affect the game's graphics.
Complex sounds aren't made by calling the MakeSound routine directly. Instead, we start making a sound by setting the relevant variables, and the regular calls to ProcessSound will then take care of the rest by calling MakeSound at the correct time with any sound processing applied. The variables are as follows:
- soundEffect is the type of sound effect to make:
- 0 = no sound (i.e. disable sound processing)
- 3 = music
- 4 = scanner sound
- 6 = game over sound
- soundCounter defines a delay to apply before processing the sound effect, measured in 1/50s of a second.
- gameOverSoundPitch is a pitch timer for the game over sound.
To make a sound effect using the sound processor, we therefore set soundEffect to the number of the sound effect, and optionally set soundCounter and gameOverSoundPitch. The non-zero value of soundEffect triggers the sound processor in each call to ProcessSound, which then makes the sound.
The value of soundCounter counts down once every 1/50 of a second until it reaches zero, at which point it stops counting. This is done in the interrupt handler at IRQHandler. The first thing ProcessSound does is to check the value of the counter, and if it is non-zero, it aborts, so this ensures that sound is only processed once the timer has counted down.
The sound counter is used in a number of places:
- We can set the value of soundCounter to a sound's duration before making the sound, and we can then test the value of soundCounter to see if it has counted down, which will check whether the sound has finished yet. This is done in the ProcessVolumeKeys routine, where we set the counter to 10/50 = 1/5 of a second before making a ping at the current volume setting, and then we wait for the counter to tick down before making another ping. This ensures that the pings are nicely separated, even if the volume keys are held down.
- The sound counter is used in the ProcessMusic routine when playing music, to set the duration of notes and chords.
- The sound counter is used in the sound processor to add randomness to the duration of the white noise in the game over sound.
- The sound counter is used in the sound processor to set the repeat duration of the scanner sound.
The sound processor itself is implemented by ProcessSound, which is a specialised routine that checks the sound effect number and processes the sound appropriately, before calling MakeSound to actually make the sound. Here's what it does for each sound effect:
- For sound effect #3 (music), the sound processor jumps to the ProcessMusic routine to play the next note. See the deep dive on music for details.
- For sound effect #4 (scanner), the sound processor sets soundCounter = 50 to disable other sound processing for one second, and then it sets the pitch and amplitude before calling MakeSound to make sound #3 (the pulsating scanner sound). This ensures that the scanner sound repeats each second while the scanner is active, a process that is controlled by the GetPlayerDrain routine, which sets soundEffect to 0 or 4 to disable or enable the sound effect.
Note that because rotations and pings are simple sound effects, they can still be made while the scanner sound is active, and they will not clash because they are on different channels: the scanner sound is on channel 2, while rotations use channels 0 and 1 and the ping uses channel 3, as defined in the soundData table. - For sound effect #6 (game over), the sound processor confirms that gameOverSoundPitch is 80 or higher, and if it is, it calls MakeSound to make sound #6, using a different entry point to set the sound's pitch to that in gameOverSoundPitch. It then fetches a random number from the landscape seeds, sets soundCounter to a random number between 1 and 4, and decrements gameOverSoundPitch, so the pitch of the sound drops over the course of the sound effect, but in a non-uniform manner.
As with the rotation sounds, the game over sound is made in two parts by the MakeSound routine. The first part is on channel 0 (the noise channel), while the second part is on channel 1. This gives the sound a clear pitch as well as a white noise effect, with the pitch dropping over the course of the sound effect to give a palpable feeling of decay.
That's the sound system covered, but for more on the subject, see the deep dive on music.