Tutorial: Handling Cross-Scene Audio

Share on Facebook0Share on Google+2Tweet about this on TwitterShare on LinkedIn0

sound-moduleFollowing up on last week’s “Goodbye Globals!” tutorial, today we’ll discuss how to manage audio files between scenes in a non-global method. In most apps, certain audio files are needed throughout the app while other audio files are only required in a specific scene. You may also encounter an instance where a particular audio file is loaded in one scene but “overlaps” into another scene, which can complicate the cleanup process.

So how do you manage this? Well, you could load your common sounds in main.lua and provide global handles to them, allowing them to be played from any scene. However, we always encourage you to avoid globals, and fortunately you can use the same method discussed last week to create your own “global” space that isn’t actually global.

Creating the “sfx.lua” Module

The first step is creating a module named sfx.lua to handle your audio. The first few lines are basic enough: we’ll require the myData.lua module as discussed last week (which stores some settings and other data), and then create a namespace to hold some variables.

Keep in mind that the first time a module is require‘d by Lua, its main chunk is executed only once. Things that you want to set/configure at the beginning can be done in the core code, for example:

Here, you’re simply creating handles for the sounds and telling the audio API to load each sound and store the handle in the sfx table. Since Lua lets you index these tables either in dot syntax or bracket syntax — sfx.boomSound or sfx[“boomSound”] — this lets you use the handle names to reference the sounds later. For example, if you require sfx.lua within a scene, you can play an audio file as follows:

Furthermore, you can extend this method to provide additional features. Let’s say you want to set up some additional audio parameters like these:

You can easily create an init function that you can call from the sfx table:

Now, masterVolume is stored in our sfx table. And, if you want to defer these actions until later, you can.

Another interesting trick is to make our own version of audio.play that will honor your app’s sound on/off settings. For example, if you find yourself coding many checks like this…

…we can avoid it by creating our own function called sfx.play. It will work similarly to audio.play but it will honor your sound settings:

Loading Scene-Specific Sounds

Using audio along with Storyboard creates a few challenging situations, including:

  • Loading sounds in the scene’s “main chunk” will cause problems if the sounds are disposed of — which they should be! — because the only way to completely “reload” them is to remove and recreate the scene.
  • The createScene event doesn’t necessarily load every time the scene loads, and loading large sounds may delay transitions.
  • The enterScene event will fire every time, but sounds won’t load until the scene is completely on the screen.

Given that the enterScene and exitScene events happen in pairs, enterScene is probably the best place to load scene-specific sounds. You can then dispose of them using audio.dispose() in the exitScene event (don’t forget to nil out the handle!).

But wait… what if your sound is still playing (and can’t just be abruptly stopped) when your app tries to go to a new scene? In the next scene, you no longer have access to the audio handle, so proper cleanup and disposal is tricky. Fortunately, this can be solved by loading the sound into the sfx table and using an anonymous function to dispose it on the onComplete phase. Consider this code:

Since Lua lets you write “anonymous functions” that onComplete events can call, you can use this method to dispose of an audio file after it’s finished. Notice that we still need to forward-declare the sound handle in the sfx table before it actually gets used.

You of course would play the sound where appropriate for your app.

In Summary…

Hopefully you can see the benefits of handling audio in a module, especially if you’re using Storyboard or another scene manager utility. We can’t emphasize enough that audio must be carefully and properly managed — and disposed of! — to prevent memory leaks and other erratic behavior. Using the module method to “isolate” your audio is one technique of doing exactly this.

Share on Facebook0Share on Google+2Tweet about this on TwitterShare on LinkedIn0
Rob Miracle

Rob Miracle creates mobile apps for his own enjoyment and the amusement of others. He serves the Corona Community in the forums, on the blog, and at local events.

This entry has 21 replies

  1. J. A. Whye says:

    Rob, I don’t get your last example. I know it’s just a snippet, but it looks like you’re playing that audio file again when you’re ready to leave the scene — which seems odd.

    So elsewhere in the code when you want that sound to play you’re just using audio.play() without the anonymous onComplete function, yes?

    If you load a sound in enterScene then typically you unload it in exitScene, which isn’t happening in this case — and it’s being played again?

    As I said, I don’t get it. 🙂


  2. Rob Miracle says:

    I see the confusion. I was just testing to make sure I could change scenes and it would clean up properly. You of course play the sound where appropriate and it will self-dispose of. I’ll tweak the text to clear it up.

  3. maz says:

    Is there anyway to change frequency of a song in corona … or even iOS?

  4. austin says:

    Just for confirmation, it seems like that the audio.play onComplete will dispose the audio even if the scene has changed and the audio is not complete in playing yet?

  5. Rob Miracle says:

    @maz, are you referring to resampling from like 44khz to 11khz? or something else?

    @austin, if you put the audio.dispose in the onComplete it will dispose of the sound when it’s done, regardless of the scene change. The problem people run into is that sometimes they will have a long sound like a voice over that once they change scenes, they can’t dispose of. This technique will take care of that, but it can create other problems.

  6. Hi Guys. I’m in a situation that I have:

    Intro.lua [0]
    Menu.lua [1]
    Options.lua [1.1]
    Levels.lua [1.2]
    Level1.lua [1.2.1]
    Level2.lua [1.2.2]

    well, When I go from intro.lua to Options.lua, I want to start a background sound with loop = -1

    So I can go from Menu to Options and from Menu to Levels and I want Audio to keep playing. Even if I return to menu again.

    But, If I go to Level1 or Level2, I want to stop the background music and start the game music.

    The way presented here, I understand I need to stop the music every time I change the scene.

    Is there a way to do the way I want? If yes, do you have a simple example?
    I don’t know, maybe making the audio handler a super global variable across scenes? (I don’t know if it is possible)…

    • Rob Miracle says:

      You certainly can work this to do what you want. We were just presenting one way of doing this. You can always check to see if your background music channel is playing when your enterScene() function fires and if it’s not start it, if it is let it be.

  7. kumar ks says:

    Hi Rob,
    i am working on an app, where i will be using many audio files. i am using audio.loadSound() and audio.pause() for the audio files . But my problem s , my app is working for 7 to 10 times properly , but after that its get stuck at some scene. At that point of time , the audio files are not playing and the app is not working . Pls suggest me some solution .

    • Rob Miracle says:

      I would recommend you look at your console log and see what errors you are getting. If you don’t know how to do that, please see this blog post.


      Also, you might want to consider the forums to continue this trouble shooting.

  8. mike kelly says:

    hi Rob,
    does purgeScene or removeScene take care of sound files at all?

    • Rob Miracle says:

      purgeScene() will not do anything with sounds. removeScene() will cause all of the pointers to the audio handles to be destroyed but it will not release the memory. You have to specifically call audio.dispose on any audio that you load in that scene.

  9. Henry says:

    Thanks, Rob Miracle.
    Could anyone here share a simple template here?
    Thank you very much in advance.

    • Rob Miracle says:

      I’m not sure what template you’re looking for Henry. The standard Storyboard template is a the bottom of the docs for storyboard: http://docs.coronalabs.com/api/library/storyboard/

      But if you’re looking at something specifically for audio handling, there probably isn’t a template for that because everyone’s sound needs are different and isn’t really templatable.


  10. Wolf says:

    Why would you nil out the handle at the start of your sample?

    — forward declare the handle
    sfx.longsound = nil

  11. Wolf says:

    Trying to get this to work for a few sounds that I will use on several scenes. So far it is giving me trouble.
    This is my sfx.lua module with one of the sounds:

    local myData = require(“myData”)
    local sfx = {} –create main SFX Table
    return sfx
    sfx.clickSound = audio.loadSound( “sfx/click.mp3” )

    Then in the first scene of my game I require the sfx module, declare the handle and then try to play the sound.

    local sfx = require(“sfx”)

    local sfx.clickSound = nillocal function buttonPressed(event)
    audio.fadeOut({ channel=0, time=3000 } )
    local id = event.target.id
    print (“Hello!”, id)
    if id == “startButton” then
    print (“start Button has been triggered”)
    audio.play( sfx.clickSound )
    storyboard.gotoScene( “page01”, “fade” )

    It’s not happy.

    • Rob Miracle says:

      return sfx

      needs to be the very last line in your sfx.lua file. You are returning before you ever execute loading the click sound

  12. Wolf says:

    Thank you! That was it. Sfx module is working now.

    Audio still fades on scene changes. working on that now. Need it to go across a few scene changes, if possible.

  13. Ben Akinlosotu says:

    The links where I can get the myData.lua file is not working! You get an epic 404 error. Please help. Thank you.

    • Rob Miracle says:

      I’m not sure what you mean by the link where you get your myData.lua. Normally you would use a text editor and create it in your project.


  14. kikito says:

    The first two links in the article body are broken. I believe they should point to http://coronalabs.com/blog/2013/05/28/tutorial-goodbye-globals/

    It explains how to create a `mydata.lua` file.

    • Rob Miracle says:

      I’ve updated the post to go a URL that works and we will look into why the URL wasn’t working.