Boodler for iPhone

Boodler is an open-source soundscape tool written in C and Python. It is able to generate a never-ending, never-repeating stream of sound based on soundscape code, written in Python.

A while back, I ported Boodler to the iPhone. It also works fine on the iPad. However, it does not have a UI, so if you want to use it, you’d better be able to use a terminal.

The easiest way to install Boodler for iPhone is through my Cydia repository. This will install a pre-built Boodler with all the dependencies. If you want to compile from scratch, read on. And make sure you have an on-iPhone development environment set up.

As always, feel free to contact me if something doesn’t work out.

The Easy Way

The easiest way to compile Boodler on the iPhone is with a patch. First, though, you’ll need to get the dependencies and the sources.

iphone:~ mobile$ sudo apt-get install python setuptools
iphone:~ mobile$ wget http://boodler.org/dl/Boodler-2.0.3.tar.gz
iphone:~ mobile$ wget http://gammalevel.com/forever/Boodler-2.0.2-iPhone.patch

Now, we’ll unpack the sources and apply the patch:

iphone:~ mobile$ tar xzvf Boodler-2.0.3.tar.gz
iphone:~ mobile$ cd Boodler-2.0.3
iphone:~/Boodler-2.0.3 mobile$ patch -p1 < ../Boodler-2.0.2-iPhone.patch

Finally, we’ll build, sign, and install our copy of Boodler.

iphone:~/Boodler-2.0.3 mobile$ python setup.py build
iphone:~/Boodler-2.0.3 mobile$ ldid -S build/lib.darwin-10.5-arm-2.5/boodle/cboodle_osxaq.dylib
iphone:~/Boodler-2.0.3 mobile$ sudo python setup.py install

(You may get a complaint about a missing AudioToolbox framework. If you do, copy it over from the official SDK, like we did during the environment setup. It should be in “(SDK)/System/Library/Frameworks/AudioToolbox.framework“. Be sure to merge them, though: don’t overwrite files, just copy the missing ones from inside the framework.)

Testing Boodler

To test your freshly-installed Boodler, run this command:

iphone:~/Boodler-2.0.3 mobile$ boodler.py --testsound

If nothing plays, check your volume levels. If it still doesn’t work, but there’s no error, something weird is going on. Feel free to contact me with questions.

If you hear something, congratulations! Boodler now works exactly as it does on the desktop! However, you should only use Boodler as mobile; it complains loudly when you use it as root. For more information on how to use Boodler, refer to the Boodler documentation.

The Hard Way

This method is exactly like the one used above, but instead of applying a patch, we’ll be editing files manually. This section is more for my reference in the future: it shows what I did, where, and why.

setup.cfg

First, we need to edit setup.cfg so that it uses the OS X AudioQueue driver by default:

[build_scripts]
default_driver=osxaq

Also, we need to specify what drivers we want, and which we don’t. We don’t really want to compile anything but the osxaq driver. Also, we should enable integer math instead of floating-point math.

The most important point here is disabling the macosx driver. Without disabling it, it will try to compile, and it will fail, always.

[build_ext]
with-drivers=osxaq
without-drivers=stdout,file,lame,vorbis,shout,macosx
intmath=1

(Note: I have since learned the iPhone is completely capable of fast floating-point math, but it needs to be out of “Thumb” mode. I’ll have to investigate this further when I have time.)

setup.py

I used to have a modification for setup.py, but if you copy over the development framework from the official SDK, you don’t need it anymore. Go figure.

src/cboodle/audev-osxaq.c

These mods are not really needed, and they’re a bit of a hack. They keep Boodler running even when the device is locked, though, so it’s nice to have.

At the top, near the other CoreAudio/AudioToolbox includes, add these:

#include <AudioToolbox/AudioServices.h>
#include <CoreFoundation/CoreFoundation.h>

We need a thread with a run loop, so that AudioServices can tell us when a call is coming in, or we need to be quiet. Add a new global pthread with the other global variables:

static pthread_t cfthread;

Now we need an AudioServices callback, to tell us when we need to shut up. Right below the playCallback function prototype, add this:

void interruptionListenerCallback(void* user, UInt32 intState)
{
    if (intState == 1) // begin interrupt
    {
        AudioQueuePause(aqueue);
        bailing = TRUE;
        running = FALSE;
        audev_close_device();
        exit(0); // a hack, to please AudioServices
    }
}

Now we need a function to run in our thread, to set up AudioServices and our callback, then to run the Run Loop:

void* cfthread_main(void* arg)
{
    AudioSessionInitialize(CFRunLoopGetCurrent(), NULL, interruptionListenerCallback, NULL);
    UInt32 sessionCategory = 'medi'; // media player, stays on when locked
    AudioSessionSetProperty('acat', sizeof(UInt32), &sessionCategory);
    AudioSessionSetActive(1);
    CFRunLoopRun();
    return NULL;
}

Finally, we need to actually start our thread. In audev_init_device, right before the block where all the properties of formataq are set, put this line:

pthread_create(&cfthread, NULL, cfthread_main, NULL);