Creating Objects (Pure Data module)
This page documents the actual Python API exported by py4pd's C core (see Sources/py4pd.c).
If you want to create a new py4pd object, you write a Python file with the extension .pd_py, define a class that inherits from puredata.NewObject, and make sure the class name matches the filename.
Quick start
Create a file mymetro.pd_py:
import puredata as pd
class mymetro(pd.NewObject):
name: str = "mymetro" # must match the filename (mymetro.pd_py)
def __init__(self, args):
self.inlets = 2
self.outlets = 1
self.ms = float(args[0]) if len(args) else 250.0
self.running = False
self.clock = pd.new_clock(self, self._tick)
def in_1_float(self, f: float):
self.running = bool(f)
if self.running:
self._tick()
else:
self.clock.unset()
def in_2_float(self, f: float):
self.ms = float(f)
def _tick(self):
if self.running:
self.clock.delay(self.ms)
self.out(0, pd.SYMBOL, "tick")
How Pd messages map to Python methods
py4pd routes incoming messages to methods using this naming convention:
in_{INLET}_{SELECTOR}
Where:
INLETis 1-based (first inlet is1, second is2, ...)SELECTORis the Pd selector name (bang,float,symbol,list, or any custom selector)
Argument conversion
py4pd converts Pd atoms to Python values like this:
bang→ callsin_N_bang(self)(no arguments)float→ callsin_N_float(self, f: float)symbol→ callsin_N_symbol(self, s: str)list→ callsin_N_list(self, xs: list)- numbers become
intwhen possible, otherwisefloat - symbols become
str - Any other selector (example:
set,foo,bar) → callsin_N_set(self, args: list) - the single argument is a Python
listof the message atoms (same conversion rules)
Methods must return None
If your method returns anything other than None, py4pd will print an error in Pd.
Use self.out(...) to output values.
Inlets and outlets
In __init__, set self.inlets and self.outlets.
These attributes can be:
1) An integer (count)
self.inlets = 2
self.outlets = 3
2) A single string (one inlet/outlet)
Use the module constants:
pd.SIGNAL(string:"signal")pd.DATA(string:"anything")
Example:
self.inlets = pd.SIGNAL
self.outlets = pd.SIGNAL
3) A tuple of strings (typed configuration)
self.inlets = (pd.SIGNAL, pd.DATA, pd.DATA)
self.outlets = (pd.DATA, pd.SIGNAL)
Output from Python to Pd: self.out
Use:
self.out(outlet_index, type_constant, value)
Type constants exported by the module:
pd.FLOATpd.SYMBOLpd.LISTpd.PYOBJECT
Examples:
self.out(0, pd.FLOAT, 1.5)
self.out(0, pd.SYMBOL, "hello")
self.out(0, pd.LIST, [1, 2, 3, "a"])
Passing arbitrary Python objects (pd.PYOBJECT)
You can send arbitrary Python objects between py4pd objects using pd.PYOBJECT.
Sender:
self.out(0, pd.PYOBJECT, {"a": 1, "b": 2})
Receiver: implement in_N_pyobj:
def in_1_pyobj(self, obj):
# obj is the exact Python object that was sent
self.logpost(2, "Got:", obj)
Arrays (Pd tables): tabread / tabwrite
Read an array as a Python tuple of floats:
data = self.tabread("myarray")
Write a list/tuple of numbers into an array:
self.tabwrite("myarray", [0.0, 0.5, 1.0])
Options:
resize=Trueto resize the Pd array to fit the input lengthredraw=Falseto skip GUI redraw (useful for performance)
self.tabwrite("myarray", [0.0, 0.5, 1.0], resize=True, redraw=True)
Logging and errors
self.logpost(loglevel, *args, prefix=True)
Posts to the Pd console. loglevel is an integer (Pd uses levels like error/log/debug depending on your build and settings).
self.logpost(2, "processing", 123)
self.logpost(2, "no prefix", prefix=False)
self.error(*args)
Prints an error tagged with the object name:
self.error("Something went wrong")
Helpers
self.get_current_dir()→ returns the current canvas directory (string)self.reload()→ reloads the current.pd_pyfile and updates the running object
Clocks (timers)
Create a clock:
self.clock = pd.new_clock(self, self._tick)
Supported clock methods:
clock.delay(ms: float)schedule the callback aftermsmillisecondsclock.unset()cancel any pending callback
Note: the C core also defines a set(...) method on the clock type, but delay(...) and unset() are the stable, expected API.
Receivers (global symbol binding)
Bind to a Pd symbol so you can receive messages sent to it:
self.r = pd.new_receiver(self, "mysym", self.received)
def received(self, x):
self.logpost(2, "got", x)
The callback receives arguments using the same conversion rules as inlet methods (bang/float/symbol/list/other).
Implementation note: the current C core binds on creation and unbinds when the receiver object is destroyed; it does not currently expose explicit bind()/unbind() methods in Python.
Audio (tilde) objects
If your class defines a callable perform(...), py4pd registers it as a DSP object.
For DSP to actually run, your class must also define a callable dsp(sr, blocksize, inchans) method that returns a boolean.
dsp(sr, blocksize, inchans) -> bool
sr: sample rate (float)blocksize: block size (int)inchans: number of signal inlets (int)
Return True to enable DSP, False to disable (outputs will be zeroed).
perform(inputs) -> tuple
inputsis a tuple ofinchansPython lists, each list withblocksizefloats
Return:
- for 1 signal outlet: a tuple of
blocksizefloats - for N signal outlets: a tuple of N tuples, each
blocksizefloats
Example (1-in / 1-out):
import puredata as pd
import math
class pysine(pd.NewObject):
name = "pysine~"
def __init__(self, args):
self.inlets = pd.SIGNAL
self.outlets = pd.SIGNAL
self.phase = 0.0
def dsp(self, sr, blocksize, inchans):
self.sr = float(sr)
self.blocksize = int(blocksize)
return True
def perform(self, inputs):
freq = inputs[0]
out = []
for f in freq:
self.phase += (2.0 * math.pi * float(f)) / self.sr
if self.phase > 2.0 * math.pi:
self.phase -= 2.0 * math.pi
out.append(math.sin(self.phase))
return tuple(out)