Search code examples
androidlibgdx

Libgdx,change pitch in Music


I want to implement a cool effect, when there is an explosion the music gets slightly slower for a moment.

The Music class of libgdx doesn't allow changing the pitch of the sound, I tried using the Sound class instead of the Music to play my music but its very slow at loading the files ( 5 sec for a 5 mb file, on Desktop! ).

So, the question is if there is a way to workaround this, or if there is an external library, that works both on desktop and android and can work along with libgdx.


Solution

  • After some searching I found a way by editing the libgdx source code.

    You need to use the AudioDevice object to play your music sample by sample ,to do that you need to include the audio-extension of libgdx.

    We gonna edit the libgdx source code so you need to download it and replace the gdx.jar , gdx-backend-android.jar and gdx-backend-lwjgl.jar with the correct libgdx projects(they have the same name without the jar extension)

    1)Edit the AudioDevice.java inside com.badlogic.gdx.audio package of the gdx project

    add the following code inside the interface

    public void setSpeed(float val);
    

    2)Edit the AndroidAudioDevice.java inside com.badlogic.gdx.backends.android package of the gdx-backend-android project

    The Android side of the AudioDevice class relies on the AudioTrack class of the Android sdk ,this class has a setPlaybackRate(..) method.

    add the following code inside the class

    @Override
    public void setSpeed (float speed) {
        track.setPlaybackRate((int)(track.getSampleRate()*speed));
    }
    

    3)Edit the OpenALAudioDevice.java inside com.badlogic.gdx.backends.lwjgl.audio package of the gdx-backend-lwjgl project

    The Desktop side of the AudioDevice relies on OpenAL (the opengl of audio) which has a handy set pitch method

    add the following inside the class

    @Override
    public void setSpeed (float speed) {
        alSourcef(sourceID, AL_PITCH, speed);
    }
    

    4)Play the audio

    Here is the code for loading and playing the sound file

    import com.badlogic.gdx.Gdx;
    import com.badlogic.gdx.audio.AudioDevice;
    import com.badlogic.gdx.audio.io.Mpg123Decoder;
    import com.badlogic.gdx.files.FileHandle;
    
    public class MusicBeat {
    
    static short[] samples = new short[2048];
    Mpg123Decoder decoder;
    AudioDevice device;
    static FileHandle externalFile;
    public boolean playing=true;
    public MusicBeat(String name )
    {
            FileHandle file=Gdx.files.internal(name);
            FileHandle external=Gdx.files.external("myappname/"+name);
            if(!external.exists())file.copyTo(external); //copy the file to the external storage only if it doesnt exists yet
            decoder = new Mpg123Decoder(external);
            device = Gdx.audio.newAudioDevice(decoder.getRate(),decoder.getChannels() == 1 ? true : false);
            playing=false;
            externalFile=file;
    }
    
    void play()
    {
        playing=true;
        Thread playbackThread = new Thread(new Runnable() {
            @Override
            public synchronized  void run() {
                int readSamples = 0;
                while ( playing) {
                    if(decoder!=null){
                        if((readSamples = decoder.readSamples(samples, 0,samples.length))<=0){
                            decoder.dispose();
                            decoder = new Mpg123Decoder(externalFile);
                            playing=false;
                        }
                        device.writeSamples(samples, 0, readSamples);
                    }
                }
            }
        });
        playbackThread.setDaemon(true);
        playbackThread.start();
    }
    
    public void stop(){
        playing=false;
        decoder.dispose();
    }
    public void setVolume(float vol){
        device.setVolume(vol);
    }
    public void setSpeed(float speed){
        device.setSpeed(speed);
    }
    

    }

    Where to get the audio extension?(required for the AudioDevice)

    The audio extension seems to have been deprecated and the jars cant be found easily, I have uploaded them here, its an old version but should work just fine.

    Easier way?

    If your game is only intent to run on desktop ,the Music class of libgdx on desktop relies again on OpenAL which gives as the power to play with the pitch ,so you just need to edit the Music interface(OpenALMusic on desktop) instead of the AudioDevice and get out the whole play sample by sample thing out of the equation,unfortunately as dawez said the Music class on android relies on MediaPlayer which is not giving us the pitch change option.

    Conclusion :This method doesnt seem nice to me ,If your game really needs the pitch thing and it doesn't makes sense without it then go with it ,otherwise its just too much effort for such a small detail .