2012-03-15

#8 - libgdx Tutorial: Sound and Music

Libgdx version used on this post: 0.9.2 (download)
The last change before start implementing the Level screen is to add sound and music to the menu.

Important changes

  • I updated the libgdx JARs and SO files to the latest available.
  • The TyrianPreferences is now called PreferencesManager and lives inside the services package.
  • In the Tyrian class (our game class) the services are created inside #create().
  • In the AbstractScreen class I removed the disposal of the stage, since it was crashing the game in some 64-bit machines. I'm trying to understand this problem in depth, so I hope I can come up with a better solution in the future.
  • The ProfileService was renamed to ProfileManager, and now it uses a new file type: local (instead of external). In Android it will resolve to a folder private to the application in the local file system instead of the SD Card's root. This is good for three reasons: (1) when the application is removed, so are these files; (2) the SD Card is not always available; (3) the files are private to the application. In the Desktop it will resolve to the game's root folder.

Sound and Music

The com.badlogic.gdx.Audio interface allows us to easily handle sound and music in libgdx, and you can get a valid instance just by calling: Gdx.audio. When working with audio it's important to know that:
  • All sound and music should be disposed when they're no longer needed.
  • Sound instances are fully loaded to the heap and can have up to 1 MB.
  • You create sound instances through: Gdx.audio.newSound(FileHandle)
  • Music instances are streamed and are paused/resumed automaticaly when the game is paused/resumed.
  • You create music instances through: Gdx.audio.newMusic(FileHandle)
  • The supported formats for both sound and music are: WAV, MP3 and OGG.

The Sound and Music services

When dealing with basic audio the hard work lies on creating nice services to work with. Given the directives above it would be nice to:
  • Create separated sound and music services.
  • The music service should allow just one music to be played at any given time.
  • The sound service should cache the loaded sounds in order to improve performance by avoiding excessive IO reads. An LRU (Least Recently Used) cache would do the job, since we want to avoid reloading the most played sounds. After searching for some code on the web I came up with this reusable LRUCache class. I also added an eviction listener so I can dispose the sounds correctly.
  • Both services should manage the loaded resources, disposing them when they're no longer needed.
  • Both services may be turned on/off at any time, and they should respect the volume setting.
Please have a look at the source code for the MusicManager and the SoundManager. I tried to write a detailed Javadoc to ease their understanding. The Tyrian class holds references to all of our services, so I've also modified it to include our audio services. And let's not forget about the Options screen, where we can turn the audio on/off and adjust the volume.

Tyrian resources

The real Tyrian was made freeware some years ago, so it's very easy to find resources of the game on the web. I found some official sound tracks and sound effects using Google, and edited them a bit with Audacity to reduce the file sizes. We could work with lightweight MIDI files, but it's not that easy to do so because we have to create code that will run only on Android, and code that will run only on the Desktop. Also, we would have to manage them manually, so we're better off sticking to one of the supported formats.

As an example of using the audio module I modified the source code of all screens to play a click sound when clicking on any button, and the Splash screen starts playing the menu music when it's created.

Conclusion

We learned how to use the audio module of libgdx. In case you need advanced audio features you should try the gdx-audio extension. This is the tag for this post on the Subversion repository. On the next post we'll start to implement the game itself, so I guess it will take a while to finish writing it. Thanks for reading!

18 comments:

  1. Best Android/libGDX tutorial series on the web!!!

    ReplyDelete
  2. Really great tutorial! I'm excited about the future episodes.

    One thing that I miss on your blog is a flatter button. I think you have well-deserved a beer from us :->

    ReplyDelete
    Replies
    1. Ohh, now I see there is a g checkout possibility
      ... -> cheers

      Delete
    2. Thanks for the feedback, hugi! I'm about to finish the next post.
      It's the first time I'm openly sharing what I know (and what I'm learning) and it's been great. I appreciate your support! :)

      Delete
    3. Hi Gustavo,
      the g checkout button does not work for your site.
      I get the error: STG has sent Google a shopping cart with errors in it ...

      Delete
    4. It seems that as I created my Google Checkout account through the (old) Android Market as a developer I'm not able to integrate its functionalities with other services. :(

      Well, I'll have to go with the ugly PayPal button...

      Delete
  3. Hi. First of all I want to say thanks for all these great tutorials and clean source code to read.

    I've got a question about using LRUCache. I'm quite certain I understand how it works, but I don't really see a purpose of using it. I get that, if the game uses a lot of different sounds, it will get rid of those seldom used, but it may lead to loading the sound in question to memory time and time again. As a trade-off we don't get that much of the memory freed, since Sounds are supposed to be small files anyway. So I guess my question is: why bother?

    ReplyDelete
    Replies
    1. Nice question. Basically, there are two approaches to the resource loading problem: the lazy way (load-as-needed) and the eager way (load-everything-in-advance).

      Both approaches have their problems, so I came up with this LRUCache to provide an hybrid solution. I don't want to occupy the device's memory with data I'll use rarely (and also forcing the user to wait for boring load screens), and I don't want to (re)load frequently used data all the time (which may consume the device's battery or create some lag during the gameplay).

      In the end it's up to the game we're creating which strategy to use. This kind of decision should be taken based on observations. I just wanted to present some alternative method.

      Delete
  4. hey, first off, love the tutorial! learned so much so far and can't wait for the rest.

    I had a question about the sound though. I'm using a similar SoundManager as yours (ok it's your code ;) And it works wonderfully playing clicks and other short sounds...except for the first time the sound is supposed to be played.

    For example, the first time I click a menu button, the CLICK sound is not played. The rest of the times it is. Any ideas to why this might be happening? It only happens on the ANDROID app as well...

    (I know missing a single click doesn't seem like a big deal, but I use this for other sounds and it those are more important)

    ReplyDelete
    Replies
    1. LogCat has a tag of "SoundPool" and a text of "Sample 1 not ready" if that helps. Thanks!

      Delete
    2. Hi kser, i have exactly the issue as yours, for now i solved
      it by reloading all my sound in SoundManager constructor(files size are small anyway)

      maybe someone else have better solution,


      // try reload all on start to avoid the first time bug
      for (MyGameSound sound : MyGameSound.values()) {
      FileHandle soundFile = Gdx.files.internal( sound.getFileName() );
      Sound soundToPlay = Gdx.audio.newSound( soundFile );
      soundCache.add( sound, soundToPlay );
      }

      Delete
    3. I think that we should be using the time provided by the splash screen to do these things.

      I've seen a premade action (like fadeIn, fadeOut, sequence, ...) to parallelize actions, together with the run action we could do the job. We just need to call to a new method (e.g., "loadResources") of the sound services.

      Delete
  5. This comment has been removed by the author.

    ReplyDelete
  6. Hi, you're rocking with your tutorials here! congrats! I would like to ask you. Are there any methods/tricks I can use to explicitly set the playing position of a currently playing song?
    Greeting from Chile! ;)

    ReplyDelete
    Replies
    1. I need to rewind or fastforward a song and I don't know how do it.

      Delete
    2. Hi Cristopher Oyarzun,

      You could use a seekbar. and use the value of the seekbar to play the music with the corresponding music value. For example, for animation forward and rewind, I'm using the seekbar and in its changedListener method I set the value of the stateTime according to which the animation of the images take place. So like stateTime you could use a similar variable that controls your music value, I'm guessing probably it would be time.

      Delete
    3. +1 here,
      The problem as I see it, is that there isn't a variable or method (like setPosition), that can be invoked on the Music class, meaning that I don't think it is supported by LibGdx.
      Am I wrong? I hope so :/

      Delete