Skip to content

174. 3D Dungeon – adding sound

October 18, 2014

I’m happy with my empty dungeon, now I’m starting to work on the contents.

This post will cover finding music, compiling it into a single file, playing clips from the file, running a theme clip continuously, and smoothing the transition between clips. The approach I’ve taken is not the only way, and it may not be the best way, but I’ll share it anyway.

(Don’t watch this video if you get scared easily).

Sound is very important in making a dungeon creepy. The options in Codea are pretty simple

  • a sound function that plays little electronic noises like beeps and boops
  • a music function that plays mp3 clips (maybe others too), one at a time, which has some properties you can set, like volume, position in the track, and whether it loops.

So of course I’m going to use the music function.

Finding sound clips

I found a great website with thousands of sounds to play with. (Now I am not planning to publish a game, so I have just taken whatever I wanted, but look at the licensing for each clip if you are planning to publish your game).

Background theme

I want a constant background theme running all the time, interrupted by other clips like zombies attacking.

So I need a way to interrupt the background music, play another clip, then return where I left off in the background music.

Managing sound clips

One problem with Codea is that you can’t download sounds from a website within Codea itself, like you can images. Maybe this is not a problem if you create an app and bundle everything with it. But for now, I have the problem that if I have 25 sound clips, I don’t want to clutter my Codea Dropbox folder with 25 separate music items (and anyone who plays this dungeon game will have the same problem).

So I’m going to combine all my clips into one (1) mp3 file, and only play little snippets at a time.

I am doing this using Audacity, a free music editing program, which is pretty easy to use. I am adding clips one at time, separated by about 3 seconds of silence, to avoid clips accidentally overlapping. I can then export an mp3 file, copy it to my Codea Dropbox folder, sync from within Codea, and I’m ready to go. I’ve made a careful list of where each clip starts and stops.

 Programming sounds in Codea

I’ve created a sound library called Fx, where I can put all my sound. That sounds impressive, but really all I’ve done is created a table.


The reason I do this is to keep all the sound stuff together, and by prefixing everything with Fx, I avoid accidentally re-using any function or variable names elsewhere in the project. It just keeps things neat. (I didn’t use a class, as I only have one music library for the whole game).

So now I can start adding stuff. First the name of the file, and the list of clips.

Fx.file="Dropbox:Dungeon"  --the mp3 file

    ["spider chatter"]={start=179,finish=184,volume=0.25},
    ["zombie bite"]={start=186,finish=189},
    ["brains 2"]={start=201,finish=204},
    ["shotgun fire"]={start=206,finish=208},
    ["zombie moan"]={start=211,finish=218}

The clips table holds the names of the clips, where they start and end (in seconds), and (where applicable), things like volume.

 Starting and stopping music

Obviously, if you are playing a clip, you need to start and stop it in the right places, and Codea doesn’t have functions for that. So this is what I did. I’ve numbered some lines, see the notes underneath.

function Fx.Play(clip) --clip is just the name of the clip
    if not then  --[1]
        music(Fx.file,false,0,0) --no looping, nil volume
    local f=Fx.clips[clip] --make a shorter name for convenience [2]
    --set defaults for anything not defined in the table above
    f.volume=f.volume or 1
    f.pan=f.pan or 0 --I don't use this but included it anyway
    music.currentTime=f.start  --[3]
    music.paused=false  --[4]
    Fx.endTime=ElapsedTime+f.finish-f.start  --[5]

function Fx.Update()  --[6]
    if Fx.endTime and ElapsedTime>Fx.endTime then  --[7]
        Fx.Stop() end

function Fx.Stop() --[8]

[1] If Codea doesn’t have the music file loaded (ie the name of the file is nil), start playing our file (this is the only way to load it), then pause it immediately. We will only need to play the file once. After this, we just move the current position around to play different tracks.

[2] If you have long variable names, it can make your code much more readable if you set a temporary variable equal to them, and use that instead. And a temporary local variable actually runs faster than a variable involving a table lookup.

[3] I position Codea at the start of the clip (so if the clip is “brains”, this would be 191)

[4] I un-pause the music

[5] I store the time when the clip finishes (ElapsedTime is number of seconds since the program began)

[6] This function tests for the end of the clip. It needs to be run from the main draw function.

[7] If we have a clip playing and we have gone past the end, stop.

[8] The Stop function stops the music and clears the endTime variable

But what about the theme?

This code will play any clip out of our “library”, but we want the theme clip to play continuously (ie loop), and if it is interrupted by another clip, to continue afterwards.

I know which clip is the theme, because I put its name in Fx.theme earlier above.

So the basic functionality is

  • the theme clip replays over and over
  • if interrupted by another clip, the theme carries on afterwards, where it left off

I got this to work, and discovered the transition between sounds was a bit noisy. So I included a 0.5 second fade-in at the start and end of each clip, ie the volume starts at 0 and takes 0.5 seconds to reach normal levels, and at the end, I reduce it to nil in the last 0.5 seconds. This also applies when interrupting, and gives a smooth transition between clips.

My revised code is below. Again, see the numbered notes underneath.

Fx.phaseTime=0.5 --phase in/out time  --[1]
function Fx.Play(clip)
    if not Fx.On then return end
    if not then
    local f=Fx.clips[clip] --get details for new clip
    --if the theme has been interrupted, remember where it stopped
    if clip~=Fx.theme and Fx.currentClip==Fx.theme then  --[2]     
    f.volume=f.volume or 1
    f.pan=f.pan or 0
    f.offset=f.offset or 0
    music.currentTime=f.start+f.offset  --[3]
    Fx.endOfPhaseInTime=Fx.startTime+Fx.phaseTime --[4]
    Fx.endTime=ElapsedTime+f.finish-f.start-f.offset --[5]
    Fx.startOfPhaseOutTime=Fx.endTime-Fx.phaseTime --[4]
    Fx.currentClip=clip                          --[6]

function Fx.Stop()
    --reset offset if we completed the theme clip  --[7] 
    if Fx.currentClip==Fx.theme then Fx.clips[Fx.theme].offset=0 end

function Fx.Update()
    if not Fx.currentClip then return end
    local v=Fx.clips[Fx.currentClip].volume
    if ElapsedTime<Fx.endOfPhaseInTime then --[8]
    elseif ElapsedTime>Fx.startOfPhaseOutTime then --[9]
        if ElapsedTime>Fx.endTime then Fx.Stop()

[1] Specify how long the phase in (and out) is going to be

[2] If the new clip is not the theme, and the theme clip has been playing, remember where we got to

[3] The start position of the clip should allow for any offset so we start where we left off previously

[4] The two variables labelled [4] are just there to reduce the number of calculations in the update function. If we can calculate them here, we don’t need to keep calculating them over and over, 60 times a second, in the Update function. They simply tell us where the phase in and out start/stop.

[5] the end time must also be adjusted for any offset

[6] we store the name of the current clip, so we can tell (later on) if we are interrupting the theme

[7] if the theme clip has completed fully, then reset its offset to 0, so it can start again from the beginning

[8] if we are within the phase in period, calculate the volume

[9] and the same, for the phase out period

The development process – a note for beginners

I began developing the music without a clear idea of where I was going, and even after I began writing this post, I made major changes. I tried several different approaches, and I must have written the whole thing at least twice over. And if I accidentally deleted the code now, I would probably write a better version. This is what is called an exploratory approach, where you try different things, and when you find something that does what you want, then you “refactor” (clean up) your code – often because it is a tangled mess from all the exploring.

So if you haven’t done a lot of programming, and you find yourself rethinking, rewriting, getting stuck, even starting over, don’t assume it’s because you’re stupid, and that professionals just sit down and it comes out perfectly. They use the same process, and while they undoubtedly will do it better than you or me, they also go up blind alleys, and they are not afraid to tear their code down and rewrite it. It’s a very healthy process, and all the thinking is great experience.

One thing you should try to do, though, is when you get things working, clean up your code, don’t just leave it in a mess. It may not matter for small programs, but you will quickly get into trouble as your programs get bigger. So practise refactoring, which according to Martin Fowler, an expert on the subject makes your code  easier to understand and easier to modify.

Or as he says..

Any fool can write code that a computer can understand. Good programmers write code that humans can understand


From → Games, Programming

Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: