Now we have a patch that sounds the way we want and that we can configure a bit to our taste, and it presents an API we can work with in the native apps.
Let’s look at the app code for the Android and iOS platforms side by side, and see how we use libpd to control the patch.
If you’re not familiar with one of the platforms, just ignore that code or com-pare and get a sense for similarities and differences. In pretty much all the cases used in these apps, the code is strikingly similar.
4. http://libpd.cc/documentation/
5. http://www.gnu.org/copyleft/gpl.html 6. https://www.gnu.org/licenses/lgpl.html
Setting Up Pd
Let’s start with Android. The structure of the Android app is pretty standard, with one main activity, TasksActivity. Since there are a few different user inter-actions in this activity and the list item, I’ve consolidated the libpd setup and interaction into a singleton, PdInterface. When we want a sound to occur any-where in the Android view code, we simply tell this singleton to play the sound we want. In the life-cycle method onCreate, in the following code, you can see where we initialize PdInterface, passing in a reference to a context as the activity.
android/TasksProject/Tasks/src/main/java/com/programmingsound/tasks/TasksActivity.java
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tasks);
listView = (ListView)findViewById(R.id.task_list);
PdInterface.getInstance().initialize(this);
loadTasks();
}
Inside PdInterface initialization methods, the main libpd setup takes place.
android/TasksProject/Tasks/src/main/java/com/programmingsound/tasks/audio/PdInterface.java private void initializePd() throws IOException {
AudioParameters.init(context);
int sampleRate = Math.max(MIN_SAMPLE_RATE, AudioParameters.suggestSampleRate());
int outChannels = AudioParameters.suggestOutputChannels();
PdAudio.initAudio(sampleRate, 0, outChannels, 1, true);
Android developers are intimately familiar with dealing with different devices and capabilities in their code. Libpd makes it easy to configure itself by using AudioParameters to suggest a few things based on the current system, such as sample rate and number of output channels.
We initialize the parameters singleton with our context, decide on a sample rate and number of output channels, and initialize another singleton called PdAudio. The minimum sample rate we’ll accept, 44100, is configured in the constant MIN_SAMPLE_RATE.
Next we deal with unpacking the patch out of the app’s resources, shown in the following code. The standard way of shipping supporting files like the Pd patches with an Android app is to put them in the raw resource directory.
Android doesn’t like anything strange in the filenames of these resource files, like a .zip or even a tilde, as we have in bellvoice~.pd, so when we ship the patch with the app we compress all of the patches we use—task_tones.pd, bellvoice~.pd, and adsr~.pd—into a ZIP file and name it patches in the raw resources directory.
The following code unpacks this into the content’s files directory.
android/TasksProject/Tasks/src/main/java/com/programmingsound/tasks/audio/PdInterface.java File dir = context.getFilesDir();
File patchFile = new File(dir, "task_tones.pd");
InputStream patchStream = context.getResources().openRawResource(R.raw.patches);
IoUtils.extractZipResource(patchStream, dir, true);
Notice that libpd comes with a convenient helper class, IoUtils, to take care of the unzipping operation.
After the patches are unzipped, we use the File object created in the preceding code and tell libpd to load it.
android/TasksProject/Tasks/src/main/java/com/programmingsound/tasks/audio/PdInterface.java PdBase.openPatch(patchFile.getAbsolutePath());
PdBase.sendFloat(MODULATOR_1_TUNE, 0);
PdBase.sendFloat(MODULATOR_1_DEPTH, 0);
PdBase.sendFloat(MODULATOR_2_TUNE, 0.5f);
PdBase.sendFloat(MODULATOR_2_DEPTH, 10000);
}
Then we configure the patch with the same values as were in the Synthy Tone message box we used to test inside the patch. Notice that we use constants such as MODULATOR_1_TUNE instead of string literals like mod1_tune, so we don’t have magic strings laying all over the code referencing Pd objects and messages in case these change.
To communicate with Pd, we use the appropriate PdBase class method. In this case, we want to send some messages, just like we did from the message box in the patch. They’re float values, so we use sendFloat. Simple, isn’t it?
Now Pd is initialized and configured the way we want for the current Android system. Let’s look at the iOS setup. The app’s structure is also idiomatic iOS.
I’ve consolidated the libpd setup and interaction into a single PdInterface class with a reference to an instance on the AppDelegate. When we want a sound to occur anywhere in the view code, we simply tell this instance to play the sound we want.
In the life-cycle method application:didFinishLaunchingWithOptions: in the following code, you can see where we initialize a PdAudioController with a sample rate and some options.
ios/Tasks/Tasks/AppDelegate.m
self.pdAudioController = [PdAudioController new];
PdAudioStatus status = [self.pdAudioController configureAmbientWithSampleRate:44100 numberChannels:2
mixingEnabled:YES];
if (status == PdAudioError) {
// handle audio initialization error - probably by doing nothing.
} else {
self.pdInterface = [PdInterface new];
}
This object is responsible for initializing the Apple audio libraries the way we want. Here we’ve told libpd to initialize with ambient playback allowing mixing, which means that we should not shut off any other audio playing in the background as we open this app.
Next, if that initialization works out, we create an instance of PdInterface. The following is its initialization code:
ios/Tasks/Tasks/PdInterface.m - (id) init {
self = [super init];
if (self) {
scaleDegrees = @[@0, @2, @4, @5, @7, @9, @11, @12, @14, @16];
self.pdDispatcher = [[PdDispatcher alloc] init];
[PdBase setDelegate:self.pdDispatcher];
patch = [PdBase openFile:@"task_tones.pd"
path:[[NSBundle mainBundle] resourcePath]];
// Initialize the FM operator settings. Could be read from config [PdBase sendFloat:8. toReceiver:kModulator1Tune];
Here we store an array of numbers to be used to determine position in a scale, which we’ll talk about shortly. Then we initialize a PdDispatcher and add it as a delegate to PdBase. Next we load up our patch file through PdBase. Unlike with Android, we don’t need to zip up the patches; we can just include them in the main bundle.
Finally, we send a series of messages to Pd to configure everything the way we did using the Bell Tone message box in the patch. Again, notice that we use constants such as kModulator1Tune instead of string literals like mod1_tune, so we don’t have magic strings laying all over the code. Then it’s as simple as calling sendFloat:toReceiver: on PdBase, and we’re communicating with Pd from iOS!
Now that we’re initialized and configured, let’s see how we can make all the sounds we want to make.