Music events for libgdx

We just went public on twitter and tigsource with the music events tool that we created with the audio guy that I work with.

The MIT licensed source can be found here:

The idea is that your audio engineer is able to independently to define transitions between audio tracks and then the programmer is able to load them and fire events to cause the transitions. This results in much better and more immersive audio experience.

The tool supports the following effects:

  • Play / Stop
  • Fade in / Fade out (Linear)

Along with this, you can also do match position to seamlessly transition between tracks. This requires more work from the audio engineer, but produces much better results.

Since this is shared code, here is how I cludged more accurate looping and match position with libgdx, the built-in functions weren’t reliable enough:



public class MusicEvent {
    private final String name;
    private final String fileName;
    private boolean looping;
    private final ObjectMap<String, Effect> inTransitions = new ObjectMap<String, Effect>();
    private final ObjectMap<String, Effect> outTransitions = new ObjectMap<String, Effect>();

    private transient FileHandle fileHandle;
    private transient Music music;
    private transient float position = 0;


    public MusicEvent(){
        name = null;
        fileName = null;
    }
    public MusicEvent(final String name, final FileHandle fileHandle) {
        this.name = name;
        this.fileName = fileHandle.path();
        this.fileHandle = fileHandle;
        this.looping = false;
        music = Gdx.audio.newMusic(fileHandle);
        music.setOnCompletionListener(new Music.OnCompletionListener() {
            @Override
            public void onCompletion(Music music) {
                Gdx.app.log("SoundBoard", "Event complete " + name);
                position = 0;
                if(looping){
                    music.play();
                }
            }
        });
    }

    public String getName() {
        return name;
    }

    public FileHandle getFileHandle() {
        return fileHandle;
    }

    public Music getMusic() {
        return music;
    }

    public boolean isLooping() {
        return looping;
    }

    public void setLooping(boolean looping) {
        this.looping = looping;
    }

    public float getPosition() {
        return position;
    }

    public void setPosition(float position) {
        this.position = position;
        music.setPosition(position);
    }

    @Override
    public String toString() {
        return name + " (" + fileHandle.name() + ")";
    }

    public void update(float dt){

        if(music.isPlaying()) {
            position += dt;
        }
    }

    public void dispose() {
        if(music.isPlaying()){
            music.stop();
        }

        music.dispose();
    }

    public Effect endTransition(MusicEvent event){
        Effect effect = outTransitions.get(event.getName());

        if(effect == null){
            effect = new Stop();
        }

        effect.start(this, event);
        return effect;

    }

    public Effect startTransition(MusicEvent event) {

        Effect effect;
        if (event == null) {
            effect = new Play();
        } else {

            effect = inTransitions.get(event.getName());

            if (effect == null) {
                effect = new Play();
            }
        }

        effect.start(this, event);

        return effect;

    }


    public ObjectMap<String, Effect> getInTransitions() {
        return inTransitions;
    }

    public ObjectMap<String, Effect> getOutTransitions() {
        return outTransitions;
    }


    public void addInTransition(String eventName, Effect effect) {
        this.inTransitions.put(eventName, effect);
    }

    public void removeInTransition(String name) {
        this.inTransitions.remove(name);

    }
    public void addOutTransition(String eventName, Effect effect) {
        this.outTransitions.put(eventName, effect);
    }

    public void removeOutTransition(String name) {
        this.outTransitions.remove(name);

    }

    public void init(){
        this.fileHandle = Gdx.files.internal(this.fileName);
        this.music = Gdx.audio.newMusic(this.fileHandle);
        music.setOnCompletionListener(new Music.OnCompletionListener() {
            @Override
            public void onCompletion(Music music) {
                Gdx.app.log("SoundBoard", "Event complete " + name);
                position = 0;
                if(looping){
                    music.play();
                }
            }
        });
    }

To be honest, I’d be much happier without the cludge. If you know how to achieve more accurate looping / match position behavior with libgdx, I’d be happy to incorporate it to the library.

I don’t quite get what this does?

It allows a sound engineer or composer to create tracks and test then with predefined transitions?

I am not quite sure how much this is going to help, I don’t see why the sound engineer can’t just give you a 2 tracks and say “transist between these 2 between menu and game screen”.

How does the music event class work as well? Every music event you have to do and io operation. Show example? :slight_smile:

We found out that it is helpful for the audio engineer to test the sounds using the game’s tech. For example we still have some popping and hissing when we do match position on certain type of encodings and with the tool, the audio guy can get rid of those during development phase. Simultaneously, the programmer can wire the events in the game.

For example, in the game we are working on the music transitions from calm -> tension -> combat as enemies get closer, but goes to calm -> combat when first shots are fired. We wanted to separate this process from the game logic as much as possible and enable to audio engineer to work on the transitions independently.

The MusicEvent can be utilized the following way independent of the library:


MusicEvent combat = new MusicEvent("calm", FileHandle.internal("assets/music/combat.ogg");
combat.setLooping(true);
MusicEvent calm = new MusicEvent("calm", FileHandle.internal("assets/music/calm.ogg");
calm.setLooping(true);

calm.startTransition(null);

// Combat should start playing.
calm.startTransition(combat);


Libgdx does good job abstracting away the file IO. I guess you could categorize the tool as a productivity enhancer. We’ll see if we can put together a youtube video of how it’s used to further clarify the details.

v0.2 is now out:

  • Improved UI-skin
  • Music state can now contain multiple tracks