A cython-based Python wrapper for libpd (Pure Data as an embeddable audio library) with built-in audio support via miniaudio.
- Full libpd API: Comprehensive access to libpd functionality including patches, messaging, arrays, MIDI, and callbacks
- Built-in audio: Integrated miniaudio backend for easy audio playback
- Flexible architecture: Use the built-in audio or integrate your own audio system
- Thread-safe audio: Audio processing runs in a separate thread with proper
nogilhandling - Modern build system: using scikit-build-core with CMake
pip install cypdPrerequisites
- Python 3.10+
- uv (recommended) or pip
- CMake 3.15+
- C compiler (clang/gcc)
# Clone the repository
git clone https://github.com/shakfu/cypd.git
cd cypd
# Install with uv
uv sync
# Or install with pip
pip install -e .from cypd import audio
# Play a patch for 4 seconds
audio.play("my_patch.pd", "/path/to/patches", duration_ms=4000)import cypd
from cypd import audio
# Initialize libpd
cypd.init()
cypd.init_audio(1, 2, 44100) # 1 input, 2 outputs, 44100 Hz
# Initialize audio backend
audio.init_audio(44100, 1, 2)
# Open a patch
patch_id = cypd.open_patch("my_patch.pd", "/path/to/patches")
# Start audio and enable DSP
audio.start()
cypd.dsp(True)
# Let it play
audio.sleep(4000) # 4 seconds
# Cleanup
cypd.dsp(False)
audio.stop()
audio.terminate()
cypd.close_patch(patch_id)import cypd
cypd.init()
patch_id = cypd.open_patch("my_patch.pd", ".")
# Send a bang
cypd.send_bang("my_receiver")
# Send a float
cypd.send_float("frequency", 440.0)
# Send a symbol
cypd.send_symbol("my_receiver", "hello")
# Send a list
cypd.send_list("my_receiver", 1, 2, 3, "foo", "bar")
# Send a typed message
cypd.send_message("my_receiver", "set", 1, 2, 3)
cypd.close_patch(patch_id)import cypd
def my_float_callback(receiver, value):
print(f"Received float {value} from {receiver}")
def my_bang_callback(receiver):
print(f"Received bang from {receiver}")
cypd.init()
cypd.set_float_callback(my_float_callback)
cypd.set_bang_callback(my_bang_callback)
# Subscribe to a sender
cypd.subscribe("my_sender")
# ... run your patch ...
cypd.unsubscribe("my_sender")import cypd
cypd.init()
patch_id = cypd.open_patch("my_synth.pd", ".")
cypd.init_audio(1, 2, 44100)
# Send MIDI note on (channel, pitch, velocity)
cypd.noteon(0, 60, 100) # Middle C, velocity 100
# Send MIDI note off
cypd.noteon(0, 60, 0) # velocity 0 = note off
# Control change
cypd.controlchange(0, 1, 64) # Modulation wheel
# Pitch bend
cypd.pitchbend(0, 0) # Center position
cypd.close_patch(patch_id)import cypd
cypd.init()
patch_id = cypd.open_patch("with_array.pd", ".")
# Get array size
size = cypd.array_size("my_array")
print(f"Array size: {size}")
# Resize array
cypd.resize_array("my_array", 1024)
cypd.close_patch(patch_id)init()- Initialize libpdinit_audio(in_channels, out_channels, sample_rate)- Initialize audio renderingrelease()- Shutdown libpd and release resources
open_patch(name, dir)- Open a patch, returns patch IDclose_patch(patch_id)- Close a patchadd_to_search_path(path)- Add to abstraction search pathclear_search_path()- Clear the search path
get_blocksize()- Get pd's block size (always 64)dsp(on)- Enable/disable DSP processing
send_bang(receiver)- Send a bangsend_float(receiver, value)- Send a floatsend_symbol(receiver, symbol)- Send a symbolsend_list(receiver, *args)- Send a listsend_message(receiver, msg, *args)- Send a typed messagesubscribe(source)- Subscribe to messages from a senderunsubscribe(source)- Unsubscribe from a senderexists(receiver)- Check if a receiver exists
set_print_callback(func)- Set print hookset_bang_callback(func)- Set bang receive hookset_float_callback(func)- Set float receive hookset_symbol_callback(func)- Set symbol receive hookset_list_callback(func)- Set list receive hookset_message_callback(func)- Set typed message receive hook
noteon(channel, pitch, velocity)- Send note oncontrolchange(channel, controller, value)- Send control changeprogramchange(channel, value)- Send program changepitchbend(channel, value)- Send pitch bendaftertouch(channel, value)- Send aftertouchpolyaftertouch(channel, pitch, value)- Send poly aftertouch
array_size(name)- Get array sizeresize_array(name, size)- Resize an array
init_audio(sample_rate, channels_in, channels_out)- Initialize miniaudiostart()- Start audio playbackstop()- Stop audio playbackterminate()- Shutdown audiois_running()- Check if audio is runningsleep(milliseconds)- Sleep for durationplay(patch, dir, duration_ms, ...)- Convenience function to play a patchget_version()- Get miniaudio version string
# Run tests
make test
# Build
make build
# Clean
make cleanThe project consists of two Cython extension modules:
_libpd: Core libpd wrapper providing the full API_audio: Miniaudio-based audio backend
The _audio module obtains a function pointer to libpd_process_float from _libpd at runtime, ensuring both modules share the same libpd instance (avoiding issues with static library symbol duplication).
See LICENSE file.