Pure Data through libpd


#1

I’m porting an iOS app with its own custom iOS audio engine to Android, so I thought I’d redo the audio engine in Pure Data – that’s simple enough. The PD patch works, so the next step is to get it integrated into the iOS app, and I can worry about Android later. Disclaimer: I have no clue how C++ works in Android apps, much less I have any experience with native Java, so I’m doing this to learn.

Anyway, to avoid madness, I’m trying to stick to C++ as much as I can, so I figured I’d use libpd’s C++ wrapper. However, I can’t find a single decent source of information on how to use the wrapper properly. It’s already awkward enough that I link to libpd-ios.a and then I manually import the C++ wrapper code. Maybe I’m getting it all wrong and going a little bit native is not such a bad idea – as in, using the ObjC wrapper in iOS and the Java wrapper in Android. I also saw there’s a PureDataNode block somewhere but I’m not sure how complete and fitting that would be.

Pointers?


Can WaveTable store arbitrary audio, and change playback speed?
#2

I used libpd from Cinder some time ago. It was on OS X and Windows not iOS, but it could be a starting point.


#3

Hey, thanks! I’ll have a look at your sample code.

Quick sanity-check question: since libpd is ultimately C, and the various wrappers are, I presume, calling C functions, it shouldn’t be a problem if I use the C++ wrapper and link to the static libraries that I got by cloning the pd-for-ios repo, right? I’m just asking because I usually get very confused with wrappers and libraries :slight_smile:


#4

Calling C code from C++ should work.


#5

The upshot of Cinder-PureDataNode is that it allows you to use libpd along with the rest of ci::audio. I don’t know if anyone has yet made it build on android but there is no reason it wouldn’t work, just a matter of dealing with Android’s constantly changing gradle / ndk system.

Another option would be to port the dsp portion of your custom engine to straight C++ and use it with ci::audio, you’d probably squeeze out more performance that way. Can be a big difference on mobile.


#6

Given the horrifying results I’m having with libpd (and the Pure Data node) I think it might be worth investing into porting the thing to C++. Right now, I managed to get some audio out of my patch, but it’s comes out horribly glitchy, as if the length of PD’s output buffer was shorter than that expected by Cinder, or something like that. The net result is that I get a distinctive rattling noise, plus all sorts of wrong pitches, presumably due to aliasing.

In short, my app is just a polyphonic sample player, with pre-recorded samples that need to play at different pitches (time stretching is acceptable, which is exactly what I’m doing in PD). Any pointers in that direction?


#7

You can take a look at the BufferPlayer sample to see how file playback can be done with ci::audio::Nodes, and the FallingGears sample to see one way to setup polyphony. One thing nice here over pd is that the number of voices is completely dynamic, you can scale it at runtime if needed.

Note that currently only ogg is implemented on android for file decoding, although I’d like to see an audio::SourceFile implementation for WavPack as well since it is a small library and would be straightforward to add. There’s libsndfile and libmpeg123 support in there too but not enabled by default, since they are GPL libraries.

Not sure what’s up with libpd / Cinder-PureDataNode on android, hopefully someone can figure that out because there’s no reason why it shouldn’t work and there is definitely a time and place to go that route (ex. you’re collaborating with someone who is familiar with pd). But considering your situation and that you’re starting with an engine already developed in a C-family language (using CoreAudio, I take it?), porting to cross-platform C++ seems appropriate to me.

cheers,
Rich


#8

Not sure what’s up with libpd / Cinder-PureDataNode on android, hopefully someone can figure that out because there’s no reason why it shouldn’t work

Oh, for now I’m still at “making it work on iOS”, so the issue I was having was not Android related!

But considering your situation and that you’re starting with an engine already developed in a C-family language (using CoreAudio, I take it?), porting to cross-platform C++ seems appropriate to me.

In practice, I was not even doing that :slight_smile: I was creating AUSampler presets, and using them through EPSSampler, so I was literally cheating my way out of the whole mess… Obviously I have no access to AUSampler on Android, hence my effort to make my own sampler.

I’ll have a look at those examples you linked. As for ogg, I’m OK with that – though right now I’m dealing with uncompressed wav files, so indeed having WavPack would be ideal. I may even take a look at that once I got the whole thing going.


#9

In fact, it seems this is similar to what I need to do… I could go the brute force way, but I’d rather not.


#10

OK, so. Oddly enough, I managed to get rid of the glitchy audio by changing line PureDataNode.cpp:54 from

mPdBase.processFloat( mNumTicksPerBlock, mBufferInterleaved.getData(), mBufferInterleaved.getData() );

to

mPdBase.processFloat( mNumTicksPerBlock/2, mBufferInterleaved.getData(), mBufferInterleaved.getData() );

and I suspect that /2 should rather be /getNumChannels(). This is how I set up the audio environment

auto ctx = audio::master();
auto format = audio::Node::Format().autoEnable().channels(2);
_pdNode = ctx->makeNode(new cipd::PureDataNode(format));
_pdNode->disconnectAll();
_patch = _pdNode->loadPatch(loadFile(_appBundlePath/"pd/player.pd"));
_pdNode >> ctx->getOutput();
ctx->enable();

Now, I feel like I should share my PD patch too, just to make sure I’m not going completely crazy.

Does anybody see anything obvious?

EDIT OK, scratch that. I tested this on a (rather old) device (an iPhone 5, in fact) and I had to revert to no division to get glitch-free audio. I was previously working on the iOS simulator. I’m more confused than ever. I’ll test on an iPad later today and see how it goes. This seems rather hit-and-miss, though, so any further pointers to re-implementing my sampler in pure C++ / Cinder Audio are highly appreciated.


#11

Is it possibly performance related? Is there any chance you’re starving the audio buffer?


#12

It (literally) sounds like that, but all indications that I could find in various code examples point to the fact that the whole thing should Just Work™.


#13

Hey! Maintainer of Cinder-PureDataNode here. If I remember correctly, ci::audio doesn’t work at all with the iOS simulator due to the randomness of the simulator audio buffer block sizes. They can be less than the default of 1024, and even non-pow-2 or multiples of small powers of 2.

Personally, I get around this by maintaining a macOS build of my projects.

It doesn’t look like your patch is overly expensive… How frequently are you sending messages to Pd? Have you tried profiling your app to see how much CPU the audio thread is using, and which Pd nodes are hot? Are you running your app in release mode?


#14

Hi! Messages are pretty sparse, they only happen on touch events.

I’m starting to consider having a separate macOS target too, but also trying to figure out if I can replace my PD patch entirely. I’ll see which one is easier.