Posted March 10, 2024 by Idolon
#audio #music
This is an overview of the audio tech and design for UDLR_Modify.
The basis for the sound tech in this game is a single node with the audio handler script, and a bunch of child nodes underneath, each representing a particular sound, and then with AudioStreamPlayers under those for each variation of that particular sound.
When something in the game wants to play a sound, it calls a function on the audio handler script, which then locates the appropriate child node by name and then plays a random sound under that node. The random selection of sounds works with a pooling system to ensure that every sound is played at least once before playing the same sound again.
Suppose we want to play a sound called "Win". The process would look something like this:
The full function looks like this:
func PlaySound(name): var node = get_node(name); if node != null: #soundqueue[name] is an array of sound objects if soundqueue.has(name): if soundqueue[name].size() == 0: CreateSoundQueue(name); else: CreateSoundQueue(name); #when playing a random sound from soundqueue, remove that sound from the array. #soundqueue is rebuilt above when it becomes empty var sounds = soundqueue[name]; var index = floor(rand_range(0,sounds.size())); var player = soundqueue[name].pop_at(index); if player is AudioStreamPlayer: player.play(); func CreateSoundQueue(name): var node = get_node(name); if node != null: var sounds = node.get_children(); soundqueue[name] = sounds;
This system is not perfect, as there is the possibility of the same sound playing at the end of one pool and then immediately again at the start of the next. The easiest solution is to randomize the order of sounds once and then repeat that order every time, but the pattern may become obvious for small sound pools that play quickly. A more robust solution would be keeping track of how recently each sound was played and randomly selecting from the least recently played sounds, but this is a game jam and I don't care.
Sounds and music are fed into separate busses, allowing the player to mute just the music and not gameplay. Instead of muting busses, the volume of each is lerped over a short period of time to get a fade instead of a hard cutoff/beginning. Small detail, but feels nice.
The music functions pretty much the same as the sounds, but with repeating timers for each voice. The music has five different voices:
The timing of each voice playing is completely arbitrary. The music has no rhythm or tempo, so notes can be played at any time without being out of phase. The voices that play one long note are given a steady rhythm to give the music some semblance of tempo, like breathing or ocean waves. The voices that play a series of notes are given random timings to give an improvisational feel and allow more space for silence.
In addition to the voices playing random audio files at pseudo-random times, there is also a system in place that I called "Ensembles". Every 50 to 80 seconds, the different voices are pseudo-randomly turned on and off. This allows the music to slowly change over time instead of being monotonous. I say the voices are turned on and off "pseudo-randomly" because there is a fixed number of different ensembles that can be chosen:
var ensembles = ["11001","10101","01101","11100","00111","10011","10110","01011","01110","00110","11000","01010"];
Each string in this array is five characters long, with each character representing which voices are on and off. This system easily ensures that there are never too many or too few voices playing at the same time, and also allows me to curate the ensembles that can be chosen. Most ensembles have three voices and generally any combination of three voices would probably sound nice, but some ensembles are only two voices, which I did to have quieter moments in the music. When only two voices are playing, I want to pay careful attention to which voices are being played. For example, none of the two-voice ensembles use the noise pad, because it is a subtle texture rather than an instrument that occupies some melodic space.
I'll be real, I don't know a lot about how to do sound design "properly", and I just do things that feel right. Here's what I did that felt right:
I started with some free samples created by Floex (Musician of Samorost, Machinarium). I selected sounds that recall real objects rather than digital sounds. I think this helps give a more tactile feel to an otherwise completely abstract game, like clinking together the various bits of molded plastic from a ThinkFun game.
I lightly processed and EQ'd the sounds to feel more self-consistent and occupy less bandwidth to minimize conflicts of sounds. Did I do a good job of this? Probably not, but I tried. I also reduced the width of some sounds to make them feel more like they came from a specific location, even though no audio in this game is positional. Here's the effects chain for the "Bonk" sound that plays when you move into a wall:
The first effect is a "Hybrid Reverb" preset that's mostly dry. There was no particular reason for this, I just liked what it did to the sound.
Each different token you can pick up has a unique set of sounds associated with it, giving each its own unique personality. They are all bell sounds, but with varying tones. The "swap" token (yellow) also has a set of sounds for when the swap occurs, which is the normal "swap" sound but with a stepped oscillating pitch shifter. This helps the sound stick out and clues the player in that something unusual has happened.
Because of my unfamiliarity with Godot's audio systems, I was not confident I could implement any music that had rhythm besides just playing a bunch of overlapping loops and fading them in and out. I knew I wanted something dynamic that responded to the player, with each token having its own associated voice, playing pitches in tune with the background music.
Conveniently, puzzle games tend to benefit from having ambient music, usually at a slow tempo. Because of the reasons above, I went with tempo-less ambience, which also allowed me to take advantage of the existing sound randomization system I had built for the gameplay sounds. This way of playing back music in a game was inspired in part by Ryuichi Sakamoto's Plankton music, which has no obvious tempo, subtle changes in instrumentation over time, and lots of space between notes, often using complete silence.
For actually writing the music, I went with the pentatonic scale that's just the black keys on a piano. This is an easy shortcut to make any random selection of notes generally sound nice together - so easy, in fact, that many musicians can detect when this scale is used, and will think the composer is being lazy.
To avoid infinite shame, I added a C natural to the scale, which introduces a less neutral flavor to the mix. Very rarely an F natural is used, which with a root of D flat, places us on D flat major. Avoiding the major third of the scale keeps the actual key of the music somewhat ambiguous, which creates a mood that I like. Maybe this is D flat Dorian?? (It's not, as the C would be flat. But what if?)
More specifics on each voice:
I don't really know how I pulled all this off in a week, but I'm glad I did. It was fun and I learned a lot! I hope that if you bothered reading all of this, you learned something too.