Skip to content

Sample Code for Android

Yuya Matsuo edited this page Feb 1, 2020 · 3 revisions

Some code snippets to help get you started with playing Pd patches in your Android applications.

Unpacking patches

String patchDir = "/sdcard/mypatches";
try {
  /* here we are unpacking the resource in res/raw/patch.zip to the sdcard;
      the final argument specifies whether to overwrite any existing files
      or not */
  IoUtils.extractZipResource(getResources().openRawResource(R.raw.patch),
          new File(patchDir), true);
} catch (IOException e) {
  Log.e("PdTag", e.toString());
}
/* here we're telling Pd to search the path to the patch we just unpacked;
    this is useful if you unpack several zipfiles with different sets of
    abstractions which you want on the path */
PdBase.addToSearchPath(patchDir);

Launching Pd

/* synchronize on this lock whenever you access pdService */
private final Object lock = new Object();

/* the reference to the actual launched PdService */
PdService pdService = null;

private final ServiceConnection serviceConnection = new ServiceConnection() {
  /* This gets called when our service is bound and sets up */
  @Override
  public void onServiceConnected(ComponentName name, IBinder service) {
    synchronized(lock) {
      pdService = ((PdService.PdBinder)service).getService();
      initPd();   /* see below */
    }
  }

  @Override
  public void onServiceDisconnected(ComponentName name) {
    /* this method will never be called */
  }
};

/* actually bind the service, which triggers the code above;
    this is the method you should call to launch Pd */
private void initPdService() {
  /* a separate thread is not strictly necessary,
      but it improves responsiveness */
  new Thread() {
    @Override
    public void run() {
      bindService(new Intent(MyPdDemo.this, PdService.class),
              serviceConnection, BIND_AUTO_CREATE);
    }
  }.start();
}

/* this is how we initialize Pd */
private void initPd() {
  /* here is where we bind the print statement catcher defined below */
  PdBase.setReceiver(myDispatcher);
  /* here we are adding the listener for various messages
      from Pd sent to "GUI", i.e., anything that goes into the object
      [s GUI] will send to the listener defined below */
  dispatcher.addListener("GUI", myListener);
  startAudio();  /* see below */
}

Starting and stopping Pd audio

/* this is where we'll save the handle of the Pd patch */
int patch = 0;

private void startAudio() {
  synchronized (lock) {
    if (pdService == null) return;
    if (!initAudio(2, 2) && !initAudio(1, 2)) {  /* see below */
      if (!initAudio(0, 2)) {
        Log.e("PdTag", "Unable to initialize audio interface");
        finish();
        return;
      } else {
        Log.w("PdTag", "No audio input available");
      }
    }
    if (patch == 0) {
      try {
        /* assuming here that the patch zipfile contained a single
            folder "patch/" that contains an _main.pd */
        String path = "/sdcard/mypatches/patch";
        /* open Pd patch and save its handle for future reference */
        patch = PdBase.openPatch(new File(path, "_main.pd"));
      } catch (IOException e) {
        Log.e("PdTag", e.toString());
        finish();
        return;
      }
      try {
        /* sleep for one second to give Pd a chance to load samples and such;
            this is not always necessary, but not doing this may give rise to
            obscure glitches when the patch contains audio files */
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        // do nothing
      }
    }
    pdService.startAudio(new Intent(this, ScenePlayer.class),
          R.drawable.notification_icon, "Pure Data", "Return to Pure Data.");
  }
}

/* helper method for startAudio();
    try to initialize Pd audio for the given number of input/output channels,
    return true on success */
private boolean initAudio(int nIn, int nOut) {
  try {
    pdService.initAudio(SAMPLE_RATE, nIn, nOut, -1);
    /* negative values default to PdService preferences */
  } catch (IOException e) {
    Log.e("PdTag", e.toString());
    return false;
  }
  return true;
}

private void stopAudio() {
  synchronized (lock) {
    if (pdService == null) return;
    /* consider ramping down the volume here to avoid clicks */
    pdService.stopAudio();
  }
}

Shutting Pd down

private void cleanup() {
  synchronized(lock) {
    /* make sure to release all resources */
    stopAudio();
    if (patch != 0) {
      PdBase.closePatch(patch);
      patch = 0;
    }
    dispatcher.release();
    PdBase.release();
    try {
      unbindService(serviceConnection);
    } catch (IllegalArgumentException e) {
      // already unbound
      pdService = null;
    }
  }
}

/* override default exit method to run cleanup() first */
@Override
public void onDestroy() {
  cleanup();
  super.onDestroy();
}

Receiving messages from Pd

/* We'll use this to catch print statements from Pd
    when the user has a [print] object */
private final PdDispatcher myDispatcher = new PdUiDispatcher() {
  @Override
  public void print(String s) {
    Log.i("Pd print", s);
  }
};

/* We'll use this to listen out for messages from Pd.
    Later we'll hook this up to a named receiver. */
private final PdListener myListener = new PdListener() {
  @Override
  public void receiveMessage(String source, String symbol, Object... args) {
    Log.i("receiveMessage symbol:", symbol);
    for (Object arg: args) {
      Log.i("receiveMessage atom:", arg.toString());
    }
  }

  /* What to do when we receive a list from Pd. In this example
      we're collecting the list from Pd and outputting each atom */
  @Override
  public void receiveList(String source, Object... args) {
    for (Object arg: args) {
      Log.i("receiveList atom:", arg.toString());
    }
  }

  /* When we receive a symbol from Pd */
  @Override public void receiveSymbol(String source, String symbol) {
    Logie("receiveSymbol", symbol);
  }
  /* When we receive a float from Pd */
  @Override public void receiveFloat(String source, float x) {
    Log.i("receiveFloat", x.toString());
  }
  /* When we receive a bang from Pd */
  @Override public void receiveBang(String source) {
    Log.i("receiveBang", "bang!");
  }
};

Sending messages into Pd

/* Here is an example of how to send a bang to Pd from Java,
    for example to send to [r hello] you would invoke x.sendBang("hello") */
public void sendBang(String s) {
  PdBase.sendBang(s);
}

/* Here is a more complex example of how to send a list of data to Pd.
    Here we're assuming that s looks like a Pd-list,
    for example s="foo bar blah".
    See also PdBase.sendFloat() and PdBase.sendSymbol() */
public void send(String dest, String s) {
  String[] pieces = s.split(" ");
  Object[] list = new Object[pieces.length];

  for (int i=0; i < pieces.length; i++) {
    try {
      list[i] = Float.parseFloat(pieces[i]);
    } catch (NumberFormatException e) {
      list[i] = pieces[i];
    }
  }

  PdBase.sendList(dest, list);
}

Miscellaneous

/* this is a useful method for making sure that your app does the correct
    thing when a phone call comes in; call this once during the setup of
    your app */
private void initSystemServices() {
  TelephonyManager telephonyManager =
            (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
  telephonyManager.listen(new PhoneStateListener() {
    @Override
    public void onCallStateChanged(int state, String incomingNumber) {
      synchronized (lock) {
        if (pdService == null) return;
        if (state == TelephonyManager.CALL_STATE_IDLE) {
          if (play.isChecked() && !pdService.isRunning()) {
            startAudio();
          }
        } else {
          if (pdService.isRunning()) {
            stopAudio();
          }
        }
      }
    }
  }, PhoneStateListener.LISTEN_CALL_STATE);
}