Skip to content

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:

  • INLET is 1-based (first inlet is 1, second is 2, ...)
  • SELECTOR is the Pd selector name (bang, float, symbol, list, or any custom selector)

Argument conversion

py4pd converts Pd atoms to Python values like this:

  • bang → calls in_N_bang(self) (no arguments)
  • float → calls in_N_float(self, f: float)
  • symbol → calls in_N_symbol(self, s: str)
  • list → calls in_N_list(self, xs: list)
  • numbers become int when possible, otherwise float
  • symbols become str
  • Any other selector (example: set, foo, bar) → calls in_N_set(self, args: list)
  • the single argument is a Python list of the message atoms (same conversion rules)

`` warning "Methods must **always** returnNone`"

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.FLOAT
  • pd.SYMBOL
  • pd.LIST
  • pd.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=True to resize the Pd array to fit the input length
  • redraw=False to 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_py file 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 after ms milliseconds
  • clock.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

  • inputs is a tuple of inchans Python lists, each list with blocksize floats

Return:

  • for 1 signal outlet: a tuple of blocksize floats
  • for N signal outlets: a tuple of N tuples, each blocksize floats

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)