ZenGarden
ZenGarden is a stand-alone library for running Pure Data patches.
Install / Use
/learn @mhroth/ZenGardenREADME
ZenGarden
ZenGarden (ZG) is a runtime for the Pure Data (Pd) audio programming language. It is implemented as an extensible audio library allowing full control over signal processing, message passing, and graph manipulation. ZenGarden does not have a GUI, but easily allows one to be built on top of it.
The library is written in C++ and exposes a pure C interface described exclusively in ZenGarden.h. Many audio objects are accelerated with vector operations on ARM (NEON) and x86 (SSE) platforms, and works especially well on Apple platforms (both OS X and iOS). ZenGarden allows externals to be built, also ones that override default object functionality. A language wrapper exists for Java.
ZenGarden is licensed under the LGPL. Among other things, this means that if you are going to use the libary in a public application you must:
- Indicate that you are using the ZenGarden library.
- If you extend the library (not including externals), you must make that code public.
- You may use this library for any application, including commerical ones.
Communication
- Most discussion takes place on the Google Groups mailing list.
- Twitter user ZenGardenPd will post commits and other news.
Acknowledgements
- ZenGarden makes use of an implementation of the Mersenne Twister in order to reliably produce random numbers.
- ZenGarden uses JUnit in order to test the behaviour of objects.
junit-4.8.2.jaris included in the repository in order to make it quick and easy to test the library after building it. See the JUnit repository for more details.
Semantics
ZenGarden consists of four basic object types. These are the context (ZGContext), graph (ZGGraph), object (ZGObject), and message (ZGMessage). The first three have to do with how the signal graph is organised. The latter represents discrete messages which are sent into, processed by, and out of the graph.
A context represents a unique and independent instance of Pure Data. Think of it as Pure Data's console window. A context is defined by its block size, sample rate, and the number of input and output channels. Contexts are entirely independent and messages and objects cannot be exchanged between them.
A graph is a collection of objects and the connections between them. A ZGGraph is a subclass of ZGObject, and thus ZGGraphs can contain other ZGGraphs (such as abstraction or subgraphs). However, this does not mean that ZGGraphs and ZGObjects are interchangeable in the API. Specific functions are made available for each.
Messages represent any Pd message, be it a bang or a list of assorted float, symbols, or bangs. Messages are timestamped and contain at least one element, and may otherwise contain any number and any combination of primitives. ZenGarden messages do not distinguish between lists or arrays or singleton elementary types as in Pd. ZG messages are always lists of typed elementary types.
Graph Attachement
ZenGarden introduces the concept of graph attachement. Whenever any change in the signal graph takes place in Pd, the audio thread must wait until the reconfiguration is finished. For minor changes such as removing a connection this can be very fast and no audio underrun will occur. For larger changes, such as adding an object requiring significant initialisation, or many changes at once, such as adding a complex abstraction, audio underruns are almost guaranteed. ZenGarden solves this problem by allowing an new object or graph to be created on another thread, and then attached to a context at a convenient time. As the graph has already been instantiated, the attachement process is a relatively quick one and can thus be accomplished without causing any audio dropouts. Graph attachement generally involves registering global senders and receivers and ensuring that existing objects are aware of the new ones. Similarly, a graph can be unattached from a context, leaving it in memory yet inert.
How to Get Started
-
Download the ZenGarden repository at https://github.com/mhroth/ZenGarden
-
The repository contains an Xcode project if you are using a Mac, and also a
makefile for other platforms. It is strongly recommended to use the Xcode project to compile ZG for either iOS or OS X. Targets exist for both cases. -
ZenGarden is dependent on libsndfile for reading and writing audio files in a platform independent way. Download it, install it.
-
Once you have built
libzengarden.a, it may be included in any of your projects also withZenGarden.h
Running the Tests
ZenGarden includes many tests meant to estabilsh the correct operation of the system. The test may be run by compiling the library via the included make file in the /src directory, and then running the runme-test.sh script from the base directory.
API Usage
ZenGarden implements a core C API and a Java wrapper. The C API is the official interface to ZenGarden. The Java wrapper is fully functional and is used by the project for testing with JUnit. Other language wrappers may be added.
A few examples showing the basic usage of the ZenGarden C API are detailed below.
- Create a context, add a preexisting graph, and then process it.
- Programmatically create a graph is also described.
- Send a message with a known structure into a context.
- Send a message with an unknown structure into a context.
Creating and Processing a Context
A basic example is shown below, describing how to set up a context and graph, and then process the audio blocks. The C API as described in ZenGarden.h is used, though wrappers may exist for other languages as well.
// include the ZenGarden API definition
#include "ZenGarden.h"
// A ZenGarden context (see below) communicates via a single callback funtion as show here.
// A number of ZGCallbackFunction types are available with the most common being print commands.
void *callbackFunction(ZGCallbackFunction function, void *userData, void *ptr) {
switch (function) {
case ZG_PRINT_STD: {
// A standard print command.
printf("%s\n", (const char *) ptr);
break;
}
case ZG_PRINT_ERR: {
// A error print command.
printf("ERROR: %s\n", (const char *) ptr);
break;
}
default: break;
}
return NULL;
}
int main(int argc, char * const argv[]) {
// The number of samples to be processed per block. This can be any positive integer,
// though a power of two is strongly suggested. Typical values range between 64 and 1024.
const int blockSize = 64;
// The number of input channels. This can be any non-negative integer. Typical values are 0 (no input),
// 1 (mono input), or 2 (stereo input).
const int numInputChannels = 2;
// The number of output channels. This can be any non-negative integer. Typical values are 0 (no output),
// 1 (mono output), or 2 (stereo output).
const int numOutputChannels = 2;
// The sample rate. Any positive sample rate is supported. Typical values are i.e. 8000.0f, 22050.0f, or 44100.0f.
const float sampleRate = 22050.0f;
// Create a new context.
ZGContext *context = zg_context_new(numInputChannels, numOutputChannels, blockSize, sampleRate, callbackFunction, NULL);
// Create a new graph from the given file. The returned graph will be fully functional but it will not be attached
// to the context. Only attached graphs are processed by the context. Unattached graphs created in a context do not
// exist from the perspective of the context and add no overhead, but can be quickly added (and removed) as needed.
// Graphs are not created in the audio thread and will not cause audio underruns.
PdGraph *graph = zg_context_new_graph_from_file(context, "/path/to/", "file.pd");
if (graph == NULL) {
// if the returned graph is NULL then something has gone wrong.
// If a callback function was provided to the context, then an error message will likely be reported there.
return 1;
}
// Attach the graph to its context. Attaching a graph pauses audio playback, but it is typically a fast operation
// and is unlikely to cause an underrun.
zg_graph_attach(graph);
// dummy input and output audio buffers. Note their size.
float finputBuffers[blockSize * numInputChannels];
float foutputBuffers[blockSize * numOutputChannels];
// the audio loop
while (1) {
// Process the context. Messages are executed and audio buffers are consumed and produced.
// if input and output audio buffers are presented as non-interleaved floating point (32-bit) samples
// (ZenGarden's native format), they can be processed as shown here.
zg_context_process(context, finputBuffers, foutputBuffers);
// if input and output audio buffers are presented as interleaved signed integer (16-bit) samples,
// they can be processed as shown here. ZenGarden automatically takes care of the translation to and
// from the native non-interleaved format.
// zg_context_process_s(context, sinputBuffers, soutputBuffer);
}
// Free memory. Memory can be freed in many ways.
// The easiest is to delete the context. This not deletes the context and everything attached to it.
zg_context_delete(context);
// On the other hand, individual graphs can be deleted when they are no longer needed.
// If an attached graph is deleted, it is automatically removed from the context first.
// zg_graph_delete(graph);
// zg_c
