Audio breakdown


This is an overview of the audio tech and design for UDLR_Modify.

SOUND TECH

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:

  1. Look for a pool named "Win". If it doesn't exist, make an empty pool.
  2. If the pool is empty, fill the pool with every AudioStreamPlayer child of the node named "Win".
  3. Pick a random index of the pool, pop it from the array, and play it.

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.

MUSIC TECH

The music functions pretty much the same as the sounds, but with repeating timers for each voice. The music has five different voices:

  • Pad: 6 audio files playing every 12.3 seconds
  • Electric piano: 18 audio files playing every 14 to 21 seconds
  • Bass pad: 5 audio files playing every 10.7 seconds
  • Bell arp: 6 audio files playing every 9 to 12 seconds
  • Noise pad: 1 audio file playing every 14 seconds

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.

SOUND DESIGN

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.

MUSIC DESIGN / SONGWRITING

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:

  • The voice for each token can play any note within a single octave of the scale, excluding the F. There isn't much to say here songwriting-wise, as the melody and rhythm of each voice is random.
  • The pad plays 6 different chords. I did not put a lot of thought into what these chords are, other than to choose a somewhat wide variety. The pad never plays F.
  • The bass pad plays 5 different single notes, sticking to the black keys. No chords were used because those tend to get muddy at low frequencies.
  • The bell arp plays a few different broken chords, mostly just playing four notes of the scale in a row. This voice also does not play F.
  • The noise pad technically plays a C, but it's literally just highpassed noise with no particular pitch. You could make the argument that an F might be in this sound somewhere, but I'm not counting it because the same argument says it is playing every other note that isn't in the scale.
  • The electric piano has the most prominent melodic role in the music. I improvised a bunch of short melodic phrases on my two-octave MIDI controller, split the note data into separate blocks, and exported each as its own sound file. This is the only part of the music that I performed by hand, and is also the only voice to play an F natural. The electric piano is allowed to play the F natural because the electric piano is jazz, and jazz is about breaking rules. Or something like that.
  • Lastly, there is a bell arp that plays when the player completes a level. To ensure that this bell arp is distinct from the bell arp in the background music, it is a different pattern, different tempo, and different tone.

CONCLUSION

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.

Files

Builds.zip Play in browser
50 days ago

Get UDLR_Modify

Download NowName your own price

Leave a comment

Log in with itch.io to leave a comment.