/** * * For information on usage and redistribution, and for a DISCLAIMER OF ALL * WARRANTIES, see the file, "LICENSE.txt," in this distribution. * */ package org.puredata.core; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** * * PdBase provides basic Java bindings for pd. * * Some random notes: * * - This is a low-level library that aims to leave most design decisions to * higher-level code. In particular, it will throw no exceptions (except for the * methods for opening files, which use instances of {@link File} and may throw * {@link IOException} when appropriate). At the same time, it is designed to be * fairly robust in that it is thread-safe and does as much error checking as I * find reasonable at this level. Client code is still responsible for proper * dimensioning of buffers and such, though. * * - The MIDI methods choose sanity over consistency with pd or the MIDI * standard. To wit, channel numbers always start at 0, and pitch bend values * are centered at 0, i.e., they range from -8192 to 8191. * * - The basic idea is to turn pd into a library that essentially offers a * rendering callback (process) mimicking the design of JACK, the JACK Audio * Connection Kit. * * - The release method is mostly there as a reminder that some sort of cleanup * might be necessary; for the time being, it only releases the resources held * by the print handler, closes all patches, and cancels all subscriptions. * Shutting down pd itself wouldn't make sense because it might be needed in the * future, at which point the native library may not be reloaded. * * - I'm a little fuzzy on how/when to use sys_lock, sys_unlock, etc., and so I * decided to handle all synchronization on the Java side. It appears that * sys_lock is for top-level locking in scheduling routines only, and so * Java-side sync conveys the same benefits without the risk of deadlocks. * * @author Peter Brinkmann (peter.brinkmann@gmail.com) * */ public final class PdBase { private final static Map<String, Long> bindings = new HashMap<String, Long>(); private final static Map<Integer, Long> patches = new HashMap<Integer, Long>(); static { System.loadLibrary("pdnative"); initialize(); } private PdBase() { // do nothing }; /** * clears the search path for pd externals */ public synchronized native static void clearSearchPath(); /** * adds a directory to the search path * * @param s */ public synchronized native static void addToSearchPath(String s); /** * same as "compute audio" checkbox in pd gui, or [;pd dsp 0/1( * * Note: Maintaining a DSP state that's separate from the state of the audio * rendering thread doesn't make much sense in libpd. In most applications, * you probably just want to call {@code computeAudio(true)} at the * beginning and then forget that this method exists. * * @param state */ public static void computeAudio(boolean state) { sendMessage("pd", "dsp", state ? 1 : 0); } /** * releases resources held by native bindings (PdReceiver object and * subscriptions); otherwise, the state of pd will remain unaffected * * Note: It would be nice to free pd's I/O buffers here, but sys_close_audio * doesn't seem to do that, and so we'll just skip this for now. */ public synchronized static void release() { setReceiver(null); setMidiReceiver(null); for (long ptr : bindings.values()) { unbindSymbol(ptr); } bindings.clear(); for (long ptr : patches.values()) { closeFile(ptr); } patches.clear(); } /** * sets handler for receiving messages from pd * * @param handler */ public synchronized native static void setReceiver(PdReceiver handler); /** * sets up pd audio; must be called before process callback * * @param inputChannels * @param outputChannels * @param sampleRate * @return error code, 0 on success */ public synchronized native static int openAudio( int inputChannels, int outputChannels, int sampleRate); /** * raw process callback, processes one pd tick, writes raw data to buffers * without interlacing * * @param inBuffer * must be an array of the right size, never null; use inBuffer = * new short[0] if no input is desired * @param outBuffer * must be an array of size outBufferSize from openAudio call * @return error code, 0 on success */ public synchronized native static int processRaw(float[] inBuffer, float[] outBuffer); /** * main process callback, reads samples from inBuffer and writes samples to * outBuffer, using arrays of type short * * @param ticks * the number of Pd ticks (i.e., blocks of 64 frames) to compute * @param inBuffer * must be an array of the right size, never null; use inBuffer = * new short[0] if no input is desired * @param outBuffer * must be an array of size outBufferSize from openAudio call * @return error code, 0 on success */ public synchronized native static int process(int ticks, short[] inBuffer, short[] outBuffer); /** * main process callback, reads samples from inBuffer and writes samples to * outBuffer, using arrays of type float * * @param ticks * the number of Pd ticks (i.e., blocks of 64 frames) to compute * @param inBuffer * must be an array of the right size, never null; use inBuffer = * new short[0] if no input is desired * @param outBuffer * must be an array of size outBufferSize from openAudio call * @return error code, 0 on success */ public synchronized native static int process(int ticks, float[] inBuffer, float[] outBuffer); /** * main process callback, reads samples from inBuffer and writes samples to * outBuffer, using arrays of type double * * @param ticks * the number of Pd ticks (i.e., blocks of 64 frames) to compute * @param inBuffer * must be an array of the right size, never null; use inBuffer = * new short[0] if no input is desired * @param outBuffer * must be an array of size outBufferSize from openAudio call * @return error code, 0 on success */ public synchronized native static int process(int ticks, double[] inBuffer, double[] outBuffer); /** * @param name of the array in Pd * @return size of the array, or a negative error code if the array does not exist */ public synchronized native static int arraySize(String name); /** * read values from an array in Pd * * @param destination float array to write to * @param destOffset index at which to start writing * @param source array in Pd to read from * @param srcOffset index at which to start reading * @param n number of values to read * @return 0 on success, or a negative error code on failure */ public synchronized static int readArray(float[] destination, int destOffset, String source, int srcOffset, int n) { if (destOffset < 0 || destOffset + n > destination.length) { return -2; } return readArrayNative(destination, destOffset, source, srcOffset, n); } /** * write values to an array in Pd * * @param destination name of the array in Pd to write to * @param destOffset index at which to start writing * @param source float array to read from * @param srcOffset index at which to start reading * @param n number of values to write * @return 0 on success, or a negative error code on failure */ public synchronized static int writeArray(String destination, int destOffset, float[] source, int srcOffset, int n) { if (srcOffset < 0 || srcOffset + n > source.length) { return -2; } return writeArrayNative(destination, destOffset, source, srcOffset, n); } /** * checks whether a symbol represents a pd object * * @param s * String representing pd symbol * @return true if and only if the symbol given by s is associated with * something in pd */ public synchronized native static boolean exists(String s); /** * subscribes to pd messages sent to the given symbol * * @param symbol * @return error code, 0 on success */ public synchronized static int subscribe(String symbol) { if (bindings.get(symbol) != null) { return 0; } long ptr = bindSymbol(symbol); if (ptr == 0) { return -1; } bindings.put(symbol, ptr); return 0; } /** * unsubscribes from pd messages sent to the given symbol; will do nothing * if there is no subscription to this symbol * * @param symbol */ public synchronized static void unsubscribe(String symbol) { Long ptr = bindings.remove(symbol); if (ptr != null) { unbindSymbol(ptr); } } /** * reads a patch from a file * * @param file * @return an integer handle that identifies this patch; this handle is the * $0 value of the patch * @throws IOException * thrown if the file doesn't exist or can't be opened */ public synchronized static int openPatch(File file) throws IOException { if (!file.exists()) { throw new FileNotFoundException(file.getPath()); } String name = file.getName(); File dir = file.getParentFile(); long ptr = openFile(name, (dir != null) ? dir.getAbsolutePath() : "."); if (ptr == 0) { throw new IOException("unable to open patch " + file.getPath()); } int handle = getDollarZero(ptr); patches.put(handle, ptr); return handle; } /** * reads a patch from a file * * @param path * to the file * @return an integer handle that identifies this patch; this handle is the * $0 value of the patch * @throws IOException * thrown if the file doesn't exist or can't be opened */ public synchronized static int openPatch(String path) throws IOException { return openPatch(new File(path)); } /** * closes a patch; will do nothing if the handle is invalid * * @param handle * representing the patch, as returned by openPatch */ public synchronized static void closePatch(int handle) { Long ptr = patches.remove(handle); if (ptr != null) { closeFile(ptr); } } /** * sends a bang to the object associated with the given symbol * * @param recv * symbol associated with receiver * @return error code, 0 on success */ public synchronized native static int sendBang(String recv); /** * sends a float to the object associated with the given symbol * * @param recv * symbol associated with receiver * @param x * @return error code, 0 on success */ public synchronized native static int sendFloat(String recv, float x); /** * sends a symbol to the object associated with the given symbol * * @param recv * symbol associated with receiver * @param sym * @return error code, 0 on success */ public synchronized native static int sendSymbol(String recv, String sym); /** * sends a list to an object in pd * * @param receiver * @param args * list of arguments of type Integer, Float, or String; no more * than 32 arguments * @return error code, 0 on success */ public synchronized static int sendList(String receiver, Object... args) { int err = processArgs(args); return (err == 0) ? finishList(receiver) : err; } /** * sends a message to an object in pd * * @param receiver * @param message * @param args * list of arguments of type Integer, Float, or String; no more * than 32 arguments * @return error code, 0 on success */ public synchronized static int sendMessage(String receiver, String message, Object... args) { int err = processArgs(args); return (err == 0) ? finishMessage(receiver, message) : err; } /** * @return default pd block size, DEFDACBLKSIZE (currently 64) (aka number * of samples per tick per channel) */ public synchronized native static int blockSize(); /** * sets the handler for receiving MIDI events from pd * * @param receiver */ public synchronized native static void setMidiReceiver( PdMidiReceiver receiver); /** * sends a note on event to pd * * @param channel * starting at 0 * @param pitch * 0..0x7f * @param velocity * 0..0x7f * @return error code, 0 on success */ public synchronized native static int sendNoteOn(int channel, int pitch, int velocity); /** * sends a control change event to pd * * @param channel * starting at 0 * @param controller * 0..0x7f * @param value * 0..0x7f * @return error code, 0 on success */ public synchronized native static int sendControlChange(int channel, int controller, int value); /** * sends a program change event to Pd * * @param channel * starting at 0 * @param value * 0..0x7f * @return error code, 0 on success */ public synchronized native static int sendProgramChange(int channel, int value); /** * sends a pitch bend event to pd * * @param channel * starting at 0 * @param value * -8192..8191 (note that Pd has some offset bug in its pitch * bend objects, but libpd corrects for this) * @return error code, 0 on success */ public synchronized native static int sendPitchBend(int channel, int value); /** * sends an aftertouch event to pd * * @param channel * starting at 0 * @param value * 0..0x7f * @return error code, 0 on success */ public synchronized native static int sendAftertouch(int channel, int value); /** * sends a polyphonic aftertouch event to pd * * @param channel * starting at 0 * @param pitch * 0..0x7f * @param value * 0..0x7f * @return error code, 0 on success */ public synchronized native static int sendPolyAftertouch(int channel, int pitch, int value); /** * sends one raw MIDI byte to pd * * @param port * 0..0x0fff * @param value * 0..0xff * @return error code, 0 on success */ public synchronized native static int sendMidiByte(int port, int value); /** * sends one byte of a sysex message to pd * * @param port * 0..0x0fff * @param value * 0..0x7f * @return error code, 0 on success */ public synchronized native static int sendSysex(int port, int value); /** * sends one byte to the realtimein object of pd * * @param port * 0..0x0fff * @param value * 0..0xff * @return error code, 0 on success */ public synchronized native static int sendSysRealTime(int port, int value); private static int processArgs(Object[] args) { if (startMessage(args.length) != 0) { return -100; } for (Object arg : args) { if (arg instanceof Integer) { addFloat(((Integer) arg).intValue()); } else if (arg instanceof Float) { addFloat(((Float) arg).floatValue()); } else if (arg instanceof Double) { addFloat(((Double) arg).floatValue()); } else if (arg instanceof String) { addSymbol((String) arg); } else { return -101; // illegal argument } } return 0; } // the remaining methods do not need to be synchronized because they are // protected by the public methods that call them private native static void initialize(); private native static int startMessage(int length); private native static void addFloat(float x); private native static void addSymbol(String s); private native static int finishList(String receive); private native static int finishMessage(String receive, String message); private native static long bindSymbol(String s); private native static void unbindSymbol(long p); private native static long openFile(String patch, String dir); private native static void closeFile(long p); private native static int getDollarZero(long p); private native static int readArrayNative(float[] destination, int destOffset, String source, int srcOffset, int n); private native static int writeArrayNative(String destination, int destOffset, float[] source, int srcOffset, int n); }