This is a chaotic set of bindings for libpd, the data-processing interface for PureData. This library does not emit any sound or picture – you have to create images and sounds yourself.
Clone the repository:
git clone https://github.com/aartaka/cl-pure-data
Load the library into your Lisp image:
(asdf:load-asd #p"/path/to/cl-pure-data/cl-pure-data.asd")
(asdf:load-asd :cl-pure-data)
The dependencies are CFFI and Alexandria, they are easy to reach in almost any implementation. Additionally, make sure libpd is in your cffi:*foreign-library-directories*
.
After it loads fine, you can start hacking:
;; Initialize hooks to print everything out.
(pd:init-default-hooks)
;; Initialize audio to two input, two outputs and sample rate of 44100.
(pd:init-audio 2 2 44100)
;; Load a patch from somewhere in the filesystem.
(pd:open-patch #p"/nested/directories/and/whatever.pd")
;; Enable Direct Sound Processing (e.g. playback of the data you feed into libpd).
(pd:message "pd" "dsp" 1)
;; Feed the data for three ticks into the image.
;; DOES NOT PLAY ANY AUDIO :(
(pd:process #(3.0 4.3 1.0 ...) 3)
And then do whatever you want with the data you get!
cl-pure-data exposes the defproxy
macro to define proxies – patch-backed Lisp code seamlessly translating into PD code. It’s somewhat restricted at the moment, but is also working for dynamic patching (pun intended) of PD files and on-the-fly processing.
To see how that works, try (taken from the defproxy
documentation)
;; Assuming you initialized cl-pure-data as in previous snippets already.
(pd:init-default-hooks)
(pd:init-audio 0 1 44100)
(pd:message "pd" "dsp" 1)
;;
;; This creates two functions, bloopy and bloopy-off, loading and
;; unloading the generated patch respectively. The contents of the
;; patch are the PD commands generated from the Lisp code in the body.
(pd:defproxy bloopy ()
(dac~ (osc~ (+~ (*~ (samphold~ (noise~) (phasor~ 6)) 200) 440))))
;; Start bloopy. You won't hear anything, but `ps:process' will start returning different values
(bloopy)
;; Make it emit sound. Ensure you have cl-pure-data/asla or an equivalent loaded!
(loop repeat 1000 do (pd:process #()))
;; Disable bloopy.
(bloopy-off)
;; Create an even simpler proxy, yet having an argument.
(pd:defproxy osc (&optional (freq 440))
(dac~ (osc~ freq)))
;; Initialize it and make it play sound. Use the default frequency.
(osc)
(loop repeat 500 do (pd:process #()))
;; Now change the frequency to something audibly higher.
(osc 600)
(loop repeat 500 do (pd:process #()))
;; Don't run this -- for the safety of your ears!
(osc 4000)
(loop repeat 1000 do (pd:process #()))
- Low-level API:
- libpd.lisp
- low-level bindings to libpd. C functions with lispy names, beware of malloc&free business!
- High-level API:
- hooks.lisp
- high-level bindings to PD event hooks. Simply
push
your function into the hook you like, and you’ll see the effect the next time the event fires! - arr.lisp
- high-level-ish bindings for PD arrays. Exposes
arr
to reference arrays by name,contents
to get subsequences of those (literally the same set of arguments assubseq
), andelem
to reference just one element (thinkelt
). Bothcontents
andelem
aresetf
-able. - audio.lisp
- high-level API to PD audio processing capabilities.
init-audio
sets things up for processing, whileprocess
takes an input and produces and output, threading it all through PureData image. - pure-data.lisp
- generic high-level macros (
with-pd
,defpdfun
), patch handling (open-patch
,close-patch
,with-patch
), message-sending (message
), and cleanup (release
). - proxy.lisp
- Proxies (terminology taken from cl-collider) to express PD patches in Lisp code and dynamically modifying those. All with the help of
defproxy
macro.
Most of the cl-pure-data
functions return
t
(or other meaningful truthy value) on success- and
nil
on failure, - but some also signal conditions, so watch out!
Someday this will be more structured. Someday…
- Directly interfacing with
libpd.
libpd
package with the function names mirroring the ones from libpd.
- Initializing PureData instance.
- happens automatically when you call any function from
cl-pure-data
package or vialibpd:libpd-init
.
- happens automatically when you call any function from
- Loading and unloading PureData patches (.pd) into the instance.
- Binding hooks to PureData events.
- Binding sensible default hooks via
init-default-hooks
. - Binding to individual hooks (from hooks.lisp) via simple
(push #'handler hook)
.
- Binding sensible default hooks via
- Sending messages to the current instance via
message
.
- [X] Subscribing to messages sent by PD.
- [ ] MIDI interfacing (I’ve been too lazy and MIDI-ignorant to do it…)
- [ ] Wrapping messages the right way.
- [ ] Concatenating print messages (although simply printing them to standard output works fine too…)
- [ ] Conveniently managing instances. Right now instances are only swap-able when using
libpd
package. Ideally, this package should never be accessed by the end-user. Even though it’s exported. - [ ] Binding queued API for thread-safety.
- [X] Producing sound (although using also-alsa or cl-portaudio should be pretty straightforward in this context).
- [ ] There’s also-alsa-based backend now, but it only plays well with one output channel :D
- [ ] Producing visuals is not an option either, until you leverage some image/video library.
- [X] Making new PureData patches out of Lisp code.
- [X] Should be relatively easy—take Lisp code, process it into .pd code format, write it into a file, and load it into the current instance.
- [ ] Now, how do we express self-referential and multiple-output nodes in essentially non-cyclical code…
- [X] Should be relatively easy—take Lisp code, process it into .pd code format, write it into a file, and load it into the current instance.
- [ ] Signaling recoverable conditions for some exceptional case, like broken messages.