Pure Data for libGDX

April 28, 2014

android
audio
game
ios
libgdx
libpd
pure data
robovm

In the last months I dwelled a bit in the game development world, playing around with libGDX and Spine 2D.

I think that they are really good for 2D games (especially if you want to support Android and iOS) and that they perform very well in renderings and animations.

However I think that libGDX support for audio can be improved a bit.

Don’t get me wrong: libGDX has a good support for audio but I miss the days when I played around with Pure Data.

So I decided to write a small libGDX backend for libpd, the famous library that turns Pure Data into an embeddable audio synthesis library.

The repository is called pd-for-libgdx and you can find it in my github: https://github.com/manhluong/pd-for-libgdx.

It’s open source, Apache v 2.0.

Technically I just wrote a thin layer that let you call pd-for-andoid and pd-for-ios, through bindings of RoboVM‘s Bro, from your platform-specific libGDX projects.

The backend is still a work in progress (actually it supports only Android and iOS) but it already let you play sounds, like in the pianotest project:

This backend is designed to be used from source, cloning the repository and adding the projects and dependencies to your Eclipse workspace.

Also, keep in mind that I am still using the old way how libGDX handle the projects. That means no Gradle, for now.

Please read futher if you want to know how to use the backend and for some technical details about libGDX.

Why Pure Data?

Pure Data is a visual programming language designed to generate and process sound. It is open source and has a nice community around it. You can also see it as an open source cousin of the Max programming language.

What I really like about Pure Data and so libpd, is that it helps you to clearly separate the audio part from the rest, from all point of views.

From a software architecture and design point of view it helps you to have a less entangled code; from a human point of view, it helps you to clearly separate what the sound designer can and must do.

Moreover, Pure Data is generally a more familiar environment for a sound designer.

If you are looking for a good book about libpd, look at “Making Musical Apps” by Peter Brinkmann

How to use the backend

In this paragraph I will use the test project of the repository, pianotest, to describe how to use the backend.

So for this mini-tutorial, we will assume that pianotest is our game, just clone the repository here: https://github.com/manhluong/pd-for-libgdx.

Of course pianotest is a libGDX project, so it must satisfy all the framework’s dependencies. All these dependencies are created for you by the framework. Please follow the libGDX wiki to know how to create a game project.

Then add a project dependency in the Java Build Path section of the platform agnostic project of your game (pianotest) to the project gdx-backend-libpd:

pianotest_java_build_dep

After that add two dependencies in the Java Build Path section of the Android project of your game (pianotest-android) to the projects gdx-backend-libpd and gdx-backend-libpd-android:

pianotest-android_java_build_dep

Then do the same for the iOS project of your game (pianotest-robovm), pointing to the projects gdx-backend-libpd and gdx-backend-libpd-robovm:

pianotest-robovm_java_build_dep

Finally, resolve the dependencies of libpd: just add AndroidMidi and PdCore projects in your workspace. For the iOS part, the RoboVM backend comes with a libpd-ios.a library that I compiled and packaged for you (at gdx-backend-libpd-robovm/libds/ios/libpd-ios.a): just copy it in your RoboVM project and edit the robovm.xml accordingly.

pianotest_libpd_dep

Now the pianotest should compile and run, both for Android and for iOS:

Android (Nexus 4)

pianotest_android_nexus4

iOS (iPad Air)

pianotest_ios_ipadair

pianotest uses this patch as its audio engine:

pianotest_patch

The switch part is there only as a test to receive a message from the patch: when the user tap the left-most button, pianotest will paint the keys in white or black.

Initialization

My backend uses the libGDX mechanism to call custom platform-dependent code.

So to initalize it, you have to first create a proper platform-dependent instance of GdxPD and pass it to your game (your ApplicationListener):

Android (MainActivity.java)

private GdxPD audio;

audio = new GdxPDAndroid(
  this, AudioParameters.suggestSampleRate(), 0, 2, 8, true);

game = new PianoTest(audio);

initialize(game, cfg);

iOS (RobovmLauncher.java)

private GdxPD audio;

audio = new GdxPDiOS(44100, false, 2, true);

game = new PianoTest(audio);

return new IOSApplication(game, config);

Then in your ApplicationListener (PianoTest.java), init libpd and load the patch:

audio.init();

audio.loadPatch("pure_data", "piano_test.pd");

App lifecycle

Both iOS and Android apps has platform-specific lifecycle events which must be mirrored to the libpd audio engine.

Android (MainActivity.java)

@Override

protected void onResume() {

    super.onResume();

    audio.startAudio();

    }



@Override

protected void onPause() {

    super.onPause();

    audio.stopAudio();

    }



@Override

public void onDestroy() {

    super.onDestroy();

    audio.dispose();

    }

iOS (RobovmLauncher.java)

@Override

public void didBecomeActive(UIApplication application) {

   super.didBecomeActive(application);

   audio.startAudio();

   }



@Override

public void willResignActive(UIApplication application) {

   audio.stopAudio();

   super.willResignActive(application);

   }



@Override

public void willTerminate(UIApplication application) {

   audio.dispose();

   super.willTerminate(application);

   }

Send to libpd

After you got an initialized GdxPD with an opened patch, just call the API methods to use libpd.

PianoKey.java

game.getAudioEngine().sendBang("stop");

game.getAudioEngine().sendFloat("freq", thisPtr.getHertz());

game.getAudioEngine().sendBang("trig");

Receive from libpd

To receive messages from your patch, you have to implement the platform-dependent PdListener:

Android (MainActivity.java)

import org.puredata.core.PdListener;

public class MainActivity extends AndroidApplication implements
  PdListener {

iOS (RobovmLauncher.java)

import com.luong.gdx.libpd.ios.bindings.PdListener;

public class RobovmLauncher extends IOSApplication.Delegate implements
  PdListener {

Then register the listener accordingly:

Android (MainActivity.java)

((GdxPDAndroid)audio).addAndroidListener("light", this);

((GdxPDAndroid)audio).addAndroidListener("dark", this);

iOS (RobovmLauncher.java)

((GdxPDiOS)audio).addiOSListener("light", this);

((GdxPDiOS)audio).addiOSListener("dark", this);

Finally, handle the received message:

Android (MainActivity.java)

@Override

public void receiveBang(String source) {

      game.switchTheme();

      }

iOS (RobovmLauncher.java)

@Override

public void receiveBangFromSource(NSString source) {

      game.switchTheme();

      }

The whole point here is to create, register and handle a platform-specific PdListener: you can do it as you want, of course, according to your code style.

Code diving

Bro

RoboVM uses Bro as a bridge to iOS native code. It is some sort of JNA for Objective-C.

You can find my bindings for libpd here: https://github.com/manhluong/pd-for-libgdx/tree/master/gdx-backend-libpd-robovm/src/com/luong/gdx/libpd/ios/bindings

The best source to learn how to write a binding is Blue River Interactive repository: https://github.com/BlueRiverInteractive/robovm-ios-bindings

The bindings are not too hard to write, especially with the last version: Bro uses the Selector mechanism but offer you annotations to hide most of the complexities.

Just watch out for objective-c methods’ names!

Android assets

libpd must read the patch file and any other needed resource files.

It seems that the easiest way is to copy these files in a directory first, so keep in mind that your game will have a “startup time” at first launch.

I think that this is the ugliest part of my backend and sooner or later I will try to implement another way, at least using a thread to copy the files.

Please check this Stackoverflow for more: http://stackoverflow.com/questions/4447477/android-how-to-copy-files-in-assets-to-sdcard

pianotest_assets