diff --git a/pdlua/tutorial/examples/dial.pd b/pdlua/tutorial/examples/dial.pd index 8612693..4052b01 100644 --- a/pdlua/tutorial/examples/dial.pd +++ b/pdlua/tutorial/examples/dial.pd @@ -1,4 +1,4 @@ -#N canvas 433 315 876 306 12; +#N canvas 542 350 734 300 12; #X obj 40 50 dial; #X floatatom 40 10 5 0 0 0 - phase -, f 5; #X obj 200 70 tgl 15 0 empty empty empty 17 7 0 10 #fcfcfc #000000 @@ -6,35 +6,31 @@ #X text 200 40 clock; #X obj 200 94 metro 1000; #X floatatom 200 152 5 0 0 0 - - -, f 5; -#X msg 200 210 0; -#X obj 200 180 s phase; -#X text 400 40 random speedometer; -#X obj 400 70 tgl 15 0 empty empty empty 17 7 0 10 #fcfcfc #000000 +#X msg 150 210 0; +#X obj 200 260 s phase; +#X text 350 40 random speedometer; +#X obj 350 70 tgl 15 0 empty empty empty 17 7 0 10 #fcfcfc #000000 #000000 0 1; -#X floatatom 400 152 5 0 0 0 - - -, f 5; -#X obj 400 239 line; -#X obj 400 274 s phase; -#X obj 400 123 expr phase = random(-100 \, 100)/300.; -#X msg 400 210 \$1 100; -#X obj 400 94 metro 100; -#X obj 200 123 expr phase = phase + 1/60.; -#X obj 200 240 t f f; +#X floatatom 350 152 5 0 0 0 - - -, f 5; +#X obj 350 219 line; +#X obj 350 264 s phase; +#X msg 350 190 \$1 100; +#X obj 350 94 metro 100; #X obj 20 12 bng 15 250 50 0 empty empty empty 17 7 0 10 #fcfcfc #000000 #000000; #X floatatom 40 210 5 0 0 0 - - -, f 5; -#X obj 40 274 v phase; -#X obj 40 238 change; -#X obj 680 70 adc~; -#X obj 740 70 tgl 15 0 empty empty empty 17 7 0 10 #fcfcfc #000000 +#X obj 40 264 v phase; +#X obj 570 70 adc~; +#X obj 630 70 tgl 15 0 empty empty empty 17 7 0 10 #fcfcfc #000000 #000000 0 1; -#X obj 680 274 s phase; -#X text 680 40 dB meter; -#X obj 680 123 env~; -#X obj 680 99 *~ 0.5; -#X obj 680 186 expr ($f1-50)/100; -#X floatatom 680 152 5 0 0 0 - - -, f 5; -#X text 700 206 0..100 dB -> -0.5..+0.5 phase, f 16; -#X msg 740 94 \; pd dsp \$1; +#X obj 570 264 s phase; +#X text 570 40 dB meter; +#X obj 570 123 env~; +#X obj 570 99 *~ 0.5; +#X obj 570 186 expr ($f1-50)/100; +#X floatatom 570 152 5 0 0 0 - - -, f 5; +#X text 590 206 0..100 dB -> -0.5..+0.5 phase, f 16; +#X msg 630 94 \; pd dsp \$1; #N canvas 767 395 479 405 colors-and-size 0; #X text 120 70 black on white; #X text 120 100 white on black; @@ -74,29 +70,28 @@ color (face), f 40; #X connect 22 0 9 0; #X restore 90 10 pd colors-and-size; #X text 230 10 <--- click to open; -#X connect 0 0 19 0; +#X obj 200 123 expr phase + 1/60.; +#X obj 350 123 expr random(-100 \, 100)/300.; +#X connect 0 0 16 0; #X connect 1 0 0 0; #X connect 2 0 4 0; -#X connect 4 0 16 0; +#X connect 4 0 30 0; #X connect 5 0 7 0; -#X connect 6 0 17 0; -#X connect 9 0 15 0; -#X connect 10 0 14 0; +#X connect 6 0 7 0; +#X connect 9 0 14 0; +#X connect 10 0 13 0; #X connect 11 0 12 0; -#X connect 13 0 10 0; -#X connect 14 0 11 0; -#X connect 15 0 13 0; -#X connect 16 0 5 0; -#X connect 17 0 20 0; -#X connect 17 1 12 0; -#X connect 18 0 0 0; -#X connect 19 0 21 0; -#X connect 21 0 20 0; -#X connect 22 0 27 0; -#X connect 22 1 27 0; -#X connect 23 0 31 0; -#X connect 26 0 29 0; -#X connect 27 0 26 0; -#X connect 28 0 24 0; -#X connect 29 0 28 0; -#X connect 32 0 0 0; +#X connect 13 0 11 0; +#X connect 14 0 31 0; +#X connect 15 0 0 0; +#X connect 16 0 17 0; +#X connect 18 0 23 0; +#X connect 18 1 23 0; +#X connect 19 0 27 0; +#X connect 22 0 25 0; +#X connect 23 0 22 0; +#X connect 24 0 20 0; +#X connect 25 0 24 0; +#X connect 28 0 0 0; +#X connect 30 0 5 0; +#X connect 31 0 10 0; diff --git a/pdlua/tutorial/pd-lua-intro.pdf b/pdlua/tutorial/pd-lua-intro.pdf index ad36215..200b0f2 100644 Binary files a/pdlua/tutorial/pd-lua-intro.pdf and b/pdlua/tutorial/pd-lua-intro.pdf differ diff --git a/tutorial/18-graphics1.png b/tutorial/18-graphics1.png index 6e894dc..748711f 100644 Binary files a/tutorial/18-graphics1.png and b/tutorial/18-graphics1.png differ diff --git a/tutorial/18-graphics2.png b/tutorial/18-graphics2.png index fb29a10..234332c 100644 Binary files a/tutorial/18-graphics2.png and b/tutorial/18-graphics2.png differ diff --git a/tutorial/18-graphics3.png b/tutorial/18-graphics3.png index 2501fc6..b36aaf8 100644 Binary files a/tutorial/18-graphics3.png and b/tutorial/18-graphics3.png differ diff --git a/tutorial/18-graphics4.png b/tutorial/18-graphics4.png index b9aa326..e97027a 100644 Binary files a/tutorial/18-graphics4.png and b/tutorial/18-graphics4.png differ diff --git a/tutorial/18-graphics5.png b/tutorial/18-graphics5.png index f45d87c..d679f81 100644 Binary files a/tutorial/18-graphics5.png and b/tutorial/18-graphics5.png differ diff --git a/tutorial/18-graphics6.png b/tutorial/18-graphics6.png index 0297434..3ff5462 100644 Binary files a/tutorial/18-graphics6.png and b/tutorial/18-graphics6.png differ diff --git a/tutorial/pd-lua-intro.html b/tutorial/pd-lua-intro.html index dc83757..6974f8f 100644 --- a/tutorial/pd-lua-intro.html +++ b/tutorial/pd-lua-intro.html @@ -704,7 +704,7 @@
Albert Gräf <aggraef@gmail.com>
Computer Music Dept., Institute of Art History and Musicology
Johannes Gutenberg University (JGU) Mainz, Germany
August 2024
This document is licensed under CC BY-SA 4.0. Other formats: Markdown source, PDF
Permanent link: https://agraef.github.io/pd-lua/tutorial/pd-lua-intro.html
Pd's facilities for data structures, iteration, and recursion are somewhat limited, thus sooner or later you'll probably run into a problem that can't be easily solved by a Pd abstraction any more. At this point you'll have to consider writing an external object (or just external, for short) in a "real" programming language instead. Pd externals are usually programmed using C, the same programming language that Pd itself is written in. But novices may find C difficult to learn, and the arcana of Pd's C interface may also be hard to master.
Enter Pd-Lua, the Pd programmer's secret weapon, which lets you develop your externals in the Lua scripting language. Pd-Lua was originally written by Claude Heiland-Allen and has since been maintained by a number of other people in the Pd community. Lua, from PUC Rio, is open-source (under the MIT license), mature, very popular, and supported by a large developer community. It is a small programming language, but very capable, and is generally considered to be relatively easy to learn. For programming Pd externals, you'll also need to learn a few bits and pieces which let you interface your Lua functions to Pd, as explained in this tutorial, but programming externals in Lua is still quite easy and a lot of fun. Using Pd-Lua, you can program your own externals ranging from little helper objects to full-blown synthesizers, sequencers, and algorithmic composition tools. It gives you access to Pd arrays and tables, as well as a number of other useful facilities such as clocks and receivers, which we'll explain in some detail. Pd-Lua also ships with a large collection of instructive examples which you'll find helpful when exploring its possibilities.
Pd-Lua was originally designed for control processing, so we used to recommend Faust for doing dsp instead. We still do, but Faust isn't for everyone; being a purely functional language, Faust follows a paradigm which most programmers aren't very familiar with. Fortunately, thanks to the work of Timothy Schoen, the most recent Pd-Lua versions now also provide support for signal processing and even graphics. So it is now possible to create pretty much any kind of Pd object in Lua, including dsp objects. (However, Faust will almost certainly execute dsp code much more efficiently than Pd-Lua, as it generates highly optimized native code for just this purpose.)
Note that we can't possibly cover Pd or the Lua language themselves here, so you'll have to refer to other online resources to learn about those. In particular, check out the Lua website, which has extensive documentation available, and maybe have a look at Derek Banas' video tutorial for a quick overview of Lua. For Pd, we recommend the Pd FLOSS Manual at https://flossmanuals.net/ to get started.
Pd-Lua works inside any reasonably modern Pd flavor. This encompasses vanilla Pd, of course, but also Purr Data which includes an up-to-date version of Pd-Lua for Lua 5.4 and has it enabled by default, so you should be ready to go immediately; no need to install anything else. The same is true for plugdata (version 0.6.3 or later), a Pd flavor which can also run as a plug-in inside a DAW.
With vanilla Pd, you can install the pdlua package from Deken. There's also an official Debian package, maintained by IOhannes Zmölnig. You can also compile Pd-Lua from source, using the author's Github repository. Compilation instructions are in the README, and you'll also find some Mac and Windows binaries there. In either case, after installing Pd-Lua you also have to add pdlua
to Pd's startup libraries.
If all is well, you should see a message like the following in the Pd console (note that for vanilla Pd you'll have to switch the log level to 2 or more to see that message):
xxxxxxxxxx
pdlua 0.12.4 (GPL) 2008 Claude Heiland-Allen, 2014 Martin Peach et al.
pdlua: compiled for pd-0.55 on Aug 24 2024 00:51:01
Using lua version 5.4
This will also tell you the Lua version that Pd-Lua is using, so that you can install a matching version of the stand-alone Lua interpreter if needed. Lua should be readily available from your package repositories on Linux, and for Mac and Windows you can find binaries on the Lua website. In the following we generally assume that you're using Lua 5.3 or later (using Lua versions older than 5.3 is not recommended).
If all is not well and you do not see that message, then most likely Pd-Lua refused to load because the Lua library is missing. This shouldn't happen if you installed Pd-Lua from a binary package, but if it does then you may have to manually install the right version of the Lua library to make Pd-Lua work. Make sure that you install the package with the Lua library in it; on Debian, Ubuntu and their derivatives this will be something like liblua5.4-0.
With that out of the way, let's have a look at the most essential parts of a Lua external. To make an external, say foo
, loadable by Pd-Lua, you need to put it into a Lua script, which is simply a text file with the right name (which must be the same as the object name, foo
in this case) and extension (which needs to be .pd_lua
), so the file name will be foo.pd_lua
in this example.
Any implementation of an object must always include:
a call to the pd.Class:new():register
method which registers the object class with Pd (this should always be the first line of the script, other than comments)
a definition of the initialize
method for your object class
Here is a prototypical example (this is the contents of the foo.pd_lua file):
xxxxxxxxxx
local foo = pd.Class:new():register("foo")
+A Quick Introduction to Pd-Lua
Albert Gräf <aggraef@gmail.com>
Computer Music Dept., Institute of Art History and Musicology
Johannes Gutenberg University (JGU) Mainz, Germany
August 2024
This document is licensed under CC BY-SA 4.0. Other formats: Markdown source, PDF
Permanent link: https://agraef.github.io/pd-lua/tutorial/pd-lua-intro.html
Why Pd-Lua?
Pd's facilities for data structures, iteration, and recursion are somewhat limited, thus sooner or later you'll probably run into a problem that can't be easily solved by a Pd abstraction any more. At this point you'll have to consider writing an external object (or just external, for short) in a "real" programming language instead. Pd externals are usually programmed using C, the same programming language that Pd itself is written in. But novices may find C difficult to learn, and the arcana of Pd's C interface may also be hard to master.
Enter Pd-Lua, the Pd programmer's secret weapon, which lets you develop your externals in the Lua scripting language. Pd-Lua was originally written by Claude Heiland-Allen and has since been maintained by a number of other people in the Pd community. Lua, from PUC Rio, is open-source (under the MIT license), mature, very popular, and supported by a large developer community. It is a small programming language, but very capable, and is generally considered to be relatively easy to learn. For programming Pd externals, you'll also need to learn a few bits and pieces which let you interface your Lua functions to Pd, as explained in this tutorial, but programming externals in Lua is still quite easy and a lot of fun. Using Pd-Lua, you can program your own externals ranging from little helper objects to full-blown synthesizers, sequencers, and algorithmic composition tools. It gives you access to Pd arrays and tables, as well as a number of other useful facilities such as clocks and receivers, which we'll explain in some detail. Pd-Lua also ships with a large collection of instructive examples which you'll find helpful when exploring its possibilities.
Pd-Lua was originally designed for control processing, so we used to recommend Faust for doing dsp instead. We still do, but Faust isn't for everyone; being a purely functional language, Faust follows a paradigm which most programmers aren't very familiar with. Fortunately, thanks to the work of Timothy Schoen, the most recent Pd-Lua versions now also provide support for signal processing and even graphics. So it is now possible to create pretty much any kind of Pd object in Lua, including dsp objects. (However, Faust will almost certainly execute dsp code much more efficiently than Pd-Lua, as it generates highly optimized native code for just this purpose.)
Note that we can't possibly cover Pd or the Lua language themselves here, so you'll have to refer to other online resources to learn about those. In particular, check out the Lua website, which has extensive documentation available, and maybe have a look at Derek Banas' video tutorial for a quick overview of Lua. For Pd, we recommend the Pd FLOSS Manual at https://flossmanuals.net/ to get started.
Installation
Pd-Lua works inside any reasonably modern Pd flavor. This encompasses vanilla Pd, of course, but also Purr Data which includes an up-to-date version of Pd-Lua for Lua 5.4 and has it enabled by default, so you should be ready to go immediately; no need to install anything else. The same is true for plugdata (version 0.6.3 or later), a Pd flavor which can also run as a plug-in inside a DAW.
With vanilla Pd, you can install the pdlua package from Deken. There's also an official Debian package, maintained by IOhannes Zmölnig. You can also compile Pd-Lua from source, using the author's Github repository. Compilation instructions are in the README, and you'll also find some Mac and Windows binaries there. In either case, after installing Pd-Lua you also have to add pdlua
to Pd's startup libraries.
If all is well, you should see a message like the following in the Pd console (note that for vanilla Pd you'll have to switch the log level to 2 or more to see that message):
pdlua 0.12.4 (GPL) 2008 Claude Heiland-Allen, 2014 Martin Peach et al.
pdlua: compiled for pd-0.55 on Aug 24 2024 00:51:01
Using lua version 5.4
This will also tell you the Lua version that Pd-Lua is using, so that you can install a matching version of the stand-alone Lua interpreter if needed. Lua should be readily available from your package repositories on Linux, and for Mac and Windows you can find binaries on the Lua website. In the following we generally assume that you're using Lua 5.3 or later (using Lua versions older than 5.3 is not recommended).
If all is not well and you do not see that message, then most likely Pd-Lua refused to load because the Lua library is missing. This shouldn't happen if you installed Pd-Lua from a binary package, but if it does then you may have to manually install the right version of the Lua library to make Pd-Lua work. Make sure that you install the package with the Lua library in it; on Debian, Ubuntu and their derivatives this will be something like liblua5.4-0.
A basic example
With that out of the way, let's have a look at the most essential parts of a Lua external. To make an external, say foo
, loadable by Pd-Lua, you need to put it into a Lua script, which is simply a text file with the right name (which must be the same as the object name, foo
in this case) and extension (which needs to be .pd_lua
), so the file name will be foo.pd_lua
in this example.
Any implementation of an object must always include:
a call to the pd.Class:new():register
method which registers the object class with Pd (this should always be the first line of the script, other than comments)
a definition of the initialize
method for your object class
Here is a prototypical example (this is the contents of the foo.pd_lua file):
xxxxxxxxxx
local foo = pd.Class:new():register("foo")
function foo:initialize(sel, atoms)
return true
end
Note that in the first line we called pd.Class:new():register
with the name of the object class as a string, which must be the same as the basename of the script, otherwise Pd's loader will get very confused, create the wrong object class, print a (rather cryptic) error message, and won't be able to create the object.
We also assigned the created class (which is represented as a Lua table) to a variable foo
(which we made local to the script file here, as explained below). We need that variable as a qualifier for the methods of the object class, including initialize
. You can actually name that variable whatever you want, as long as you use that name consistently throughout the script. This can be useful at times, if the actual class name you chose, as it is known to Pd and set with pd.Class:new():register
(as well as being the basename of your .pd_lua script), is a jumble of special characters such as fo:o#?!
, which isn't a valid Lua identifier.
Next comes the initialize
method, which is implemented as a Lua function, prefixing the method name with the name of the class variable we created above and a colon, i.e., foo:initialize
. (This colon syntax is used for all functions that represent methods, which receive the called object as an implicit self
parameter; please check the section on function definitions in the Lua manual for details.) As a bare minimum, as is shown here, this method must return true
, otherwise the loader will assume that the object creation has failed, and will complain that it couldn't create the object with an error message.
We mention in passing here that Pd-Lua also provides a parameter-less postinitialize
method which can be used to execute code after the object has been created, but before the object starts processing messages. We'll see an example of this method later.
NOTE: Pd-Lua runs all Lua objects in the same instance of the Lua interpreter. Therefore, as a general guideline, we want to keep the global name space tidy and clean. That's why we made foo
a local variable, which means that its scope is confined to this single script. Note that this isn't needed for the member variables and methods, as these are securely stowed away inside the object and not accessible from the outside anyway, if the class variable is local
. But the same caveat applies to all variables and functions in the script file that might be needed to implement the object, so normally you want to mark these as local
, too (or turn them into member variables and methods, if that seems more appropriate).
We mention in passing that global variables and functions may also have their uses if you need to share a certain amount of global state between different Lua objects. But even then it's usually safer to have the objects communicate with each other behind the scenes using receivers, which we'll explain later.
To actually use the object class we just created, Pd needs be able to find our foo.pd_lua file. We'll discuss different approaches in the following section, but the easiest way to achieve this is to just drop foo.pd_lua into the directory that your patch is in (say, pd-lua
in your home directory). Now we can just create our first foo
object (hit Ctrl+1, then type the object name foo
), and we should see something like this:
Hooray, it works! :)) Well, this object doesn't do anything right now, so let's equip it with a single inlet/outlet pair. This is what the initialize
method is for, so we have to edit that method accordingly.
NB: If you closed the editor already and don't remember where the file is, you can just right-click the object and choose Open
, which will open the .pd_lua file in your favorite text editor, as configured in your desktop and/or shell environment.
xxxxxxxxxx
local foo = pd.Class:new():register("foo")
function foo:initialize(sel, atoms)
self.inlets = 1
self.outlets = 1
return true
end
Note that, as we already mentioned, the self
variable here is an implicit parameter of any Lua method, which refers to the object itself. Every Pd-Lua object has two member variables inlets
and outlets
which let us specify the number of inlets and outlets our object should have. This needs to be done when the object is initialized; afterwards, the number of inlets and outlets is set in stone and can't be changed any more.
Next, we have to make sure that Pd picks up our edited class definition. Since the Pd-Lua loader will never reload the .pd_lua file for any given object class during a Pd session, we will have to save the patch, quit Pd, relaunch it and reopen the patch:
So there, we got our single inlet/outlet pair now. To do anything with these, we finally have to add some message handlers to our object. Say, for instance, we want to handle a bang message by incrementing a counter and outputting its current value to the outlet. We first have to initialize the counter value in the initialize
method. As we want each foo
object to have its own local counter value, we create the counter as a member variable:
xxxxxxxxxx
function foo:initialize(sel, atoms)
self.inlets = 1
self.outlets = 1
self.counter = 0
return true
end
It's not necessary to declare the self.counter
variable in any way, just give it an initial value and be done with it. Finally, we have to add a method for the bang message, which looks as follows:
xxxxxxxxxx
function foo:in_1_bang()
self.counter = self.counter + 1
self:outlet(1, "float", {self.counter})
end
We'll dive into the naming conventions for message handlers later, but note that in_1
specifies the first (and only) inlet and bang
the kind of message we expect. In the body of the method we increment the self.counter
value and output its new value on the first (and only) outlet. This is done by the predefined self:outlet
method which takes three arguments: the outlet number, the (Pd) data type to output, and the output value itself. (In general, it's possible to have multiple values there, e.g., when outputting a list value. Therefore the output value is always specified as a Lua table, hence the curly braces around the float output value.)
Throwing everything together, our Lua external now looks as follows:
xxxxxxxxxx
local foo = pd.Class:new():register("foo")
function foo:initialize(sel, atoms)
self.inlets = 1
self.outlets = 1
self.counter = 0
return true
end
@@ -743,7 +743,7 @@
function luarecv:finalize()
self.recv:destruct()
end
function luarecv:receive(sel, atoms)
-- simply store the message, so that we can output it later
self.sel, self.atoms = sel, atoms
pd.post(string.format("luarecv: got '%s %s'", sel,
table.concat(atoms, " ")))
end
function luarecv:in_1_bang()
-- output the last message we received (if any)
if self.sel then
self:outlet(1, self.sel, self.atoms)
end
end
The obligatory test patch:
Signals and graphics
So far all of our examples only did control processing, which is what Pd-Lua was originally designed for. But thanks to the work of Timothy Schoen (the creator and main developer of plugdata), as of version 0.12.0 Pd-Lua also provides facilities for audio signal processing and graphics. It goes without saying that these capabilities vastly extend the scope of Pd-Lua applications, as you can now program pretty much any kind of Pd object in Lua, covering both signal and control processing, as well as custom GUI objects. We'll first discuss how to write a signal processing (a.k.a. dsp) object in Pd-Lua, and then go on to show the implementation of a simple GUI object using the graphics API.
NOTE: As these features are still fairly new, some details of the implementation may still be subject to change in future Pd-Lua versions. Also, we can't cover all the details here, so we recommend having a look at the examples included in the Pd-Lua distribution. You can find these under pdlua/examples in the source or in the help browser. Specifically, check the sig-example folder for another example illustrating the use of signal inlets and outlets, and Benjamin Wesch's osci3d~ which shows how to implement a 3d oscilloscope employing both signal processing and graphics.
Signals
Enabling signal processing in a Pd-Lua object involves three ingredients:
Adding signal inlets and outlets: As before, this is done by setting the inlets
and outlets
member variables in the initialize
method. But instead of setting these variables to just the numbers of inlets and outlets, you set them to a table which indicates the signal and control in- and outlets with the special SIGNAL
and DATA
values. The number of in- and outlets is then given by the size of these tables. Thus, e.g., you'd use self.inlets = { SIGNAL, SIGNAL, DATA }
if you need two signal and one control data inlet, in that order.
Adding a dsp method: This step is optional. The dsp
method gets invoked whenever signal processing is turned on in Pd, passing two parameters: samplerate
and blocksize
. The former tells you about the sample rate (number of audio samples per second) Pd runs at, which will be useful if your object needs to translate time and frequency values from physical units (i.e., seconds, milliseconds, and Hz) to sample-based time and frequency values, so usually you want to store the given value in a member variable of your object. The latter specifies the block size, i.e., the number of samples Pd expects to be processed during each call of the perform
method (see below). You only need to store that number if your object doesn't have any signal inlets, so that you know how many samples need to be generated. Otherwise the block size can also be inferred from the size of the in
tables passed to the perform
method. Adding the dsp
method is optional. You only have to define it if the signal and control data processing in your object requires the samplerate
and blocksize
values, or if you need to be notified when dsp processing gets turned on for some other reason.
Adding a perform method: This method is where the actual signal processing happens. If your object outputs any signal data, this method needs to be implemented, otherwise you'll get lots of errors in the Pd console as soon as you turn on dsp processing. The perform
method receives blocks of signal data from the inlets through its arguments, where each block is represented as a (numeric) Lua table. The method then needs to return a tuple of Lua tables with the blocks of signal data for each outlet. Note that the number of arguments of the method matches the number of signal inlets, while the number of return values corresponds to the number of signal outlets.
In addition to the dsp
and perform
methods, your object may contain any number of methods doing the usual control data processing on the DATA
inlets. It is also possible to receive control data on the SIGNAL
inlets; however, you won't be able to receive float
messages, because they will be interpreted as constant signals which get passed as blocks of signal data to the perform
method instead.
Example 1: Mixing signals
Let's take a look at a few simple examples illustrating the kind of processing the perform
method might do. First, let's mix two signals (stereo input) down to a single (mono) output by computing the average of corresponding samples. We need two signal inlets and one signal outlet, so our initialize
method looks like this:
xxxxxxxxxx
local foo = pd.Class:new():register("foo")
-
function foo:initialize(sel, atoms)
self.inlets = {SIGNAL,SIGNAL}
self.outlets = {SIGNAL}
return true
end
And here's the perform
method (in this simple example we don't need foo:dsp()
):
xxxxxxxxxx
function foo:perform(in1, in2)
for i = 1, #in1 do
in1[i] = (in1[i]+in2[i])/2
end
return in1
end
Note that here we replaced the signal data in the in1
table with the result, so we simply return the modified in1
signal; no need to create a third out
table. (This is safe because it won't actually modify any signal data outside the Lua method.)
Easy enough. And this is how this object works in a little test patch:
Example 2: Analyzing a signal
A dsp object can also have no signal outlets at all if you just want to process the incoming signal data in some way and output the result through a normal control outlet. E.g., here's one (rather simplistic) way to compute the rms (root mean square) envelope of a signal as control data:
xfunction foo:initialize(sel, atoms)
self.inlets = {SIGNAL}
self.outlets = {DATA}
return true
end
+
function foo:initialize(sel, atoms)
self.inlets = {SIGNAL,SIGNAL}
self.outlets = {SIGNAL}
return true
end
And here's the perform
method (in this simple example we don't need foo:dsp()
):
xxxxxxxxxx
function foo:perform(in1, in2)
for i = 1, #in1 do
in1[i] = (in1[i]+in2[i])/2
end
return in1
end
Note that here we replaced the signal data in the in1
table with the result, so we simply return the modified in1
signal; no need to create a third out
table. (This is safe because it won't actually modify any signal data outside the Lua method.)
Easy enough. And this is how this object works in a little test patch:
Example 2: Analyzing a signal
A dsp object can also have no signal outlets at all if you just want to process the incoming signal data in some way and output the result through a normal control outlet. E.g., here's one (rather simplistic) way to compute the rms (root mean square) envelope of a signal as control data:
xxxxxxxxxx
function foo:initialize(sel, atoms)
self.inlets = {SIGNAL}
self.outlets = {DATA}
return true
end
function foo:perform(in1)
local rms = 0
for i = 1, #in1 do
rms = rms + in1[i]*in1[i]
end
rms = math.sqrt(rms/#in1)
self:outlet(1, "float", {rms})
end
A little test patch:
Example 3: Generating a signal
Conversely, we can also have an object which converts control inputs into signal data, such as this little oscillator object which produces a sine wave:
xxxxxxxxxx
function foo:initialize(sel, atoms)
self.inlets = {DATA}
self.outlets = {SIGNAL}
self.phase = 0
self.freq = 220
self.amp = 0.5
return true
end
-- message to set frequency...
function foo:in_1_freq(atoms)
self.freq = atoms[1]
end
-- ... and amplitude.
function foo:in_1_amp(atoms)
self.amp = atoms[1]
end
@@ -761,18 +761,18 @@
function dial:initialize(sel, atoms)
self.inlets = 1
self.outlets = 0
self.phase = 0
self:set_size(127, 127)
return true
end
function dial:in_1_float(x)
self.phase = x
self:repaint()
end
The self:set_size()
call in the initialize
method sets the pixel size of the object rectangle on the canvas (in this case it's a square with a width and height of 127 pixels). Also note the call to self:repaint()
in the float handler for the inlet, which will redraw the graphical representation after updating the phase value.
We still have to add the dial:paint()
method to do all the actual drawing:
xxxxxxxxxx
function dial:paint(g)
local width, height = self:get_size()
local x, y = self:tip()
-- standard object border, fill with bg color
g:set_color(0)
g:fill_all()
-
-- dial face
g:fill_ellipse(2, 2, width - 4, height - 4)
g:set_color(1)
-- dial border
g:stroke_ellipse(2, 2, width - 4, height - 4, 4)
-- center point
g:fill_ellipse(width/2 - 3.5, height/2 - 3.5, 7, 7)
-- dial hand
g:draw_line(x, y, width/2, height/2, 2)
end
The existence of the paint
method tells Pd-Lua that this is a graphical object. As mentioned before, this method receives a graphics context g
as argument. The graphics context is an internal data structure keeping track of the graphics state of the object, which is used to invoke all the graphics operations. The set_color
method of the graphics context is used to set the color for all drawing operations; in the case of fill
operations it fills the graphical element with the color, while in the case of stroke
operations it draws its border. There's just one color value, so we need to set the desired fill color in case of fill
, and the desired stroke color in case of stroke
operations. The color values 0 and 1 we use in this example are predefined, and indicate the default background color (usually white) and default foreground color (usually black), respectively.
It is possible to choose other colors by calling g:set_color(r, g, b)
with rgb color values instead, with each r, g, b value ranging from 0 to 255 (i.e., a byte value). For instance, the color "teal" would be specified as 0, 128, 128, the color "orange" as 255, 165, 0, "black" as 0, 0, 0, "white" as 255, 255, 255, etc. It's also possible to add a fourth alpha a.k.a. opacity value a, which is a floating point number in the range 0-1. 0 means fully transparent, 1 fully opaque, any value in between will blend in whatever is behind the graphical element to varying degrees. The alpha values are only supported in both plugdata and purr-data (as of Pd-Lua 0.12.7), in vanilla Pd they are simply ignored at present. So in vanilla Pd all graphical objects will be opaque no matter what alpha value you specify.
Let's now take a closer look at the drawing operations themselves. We start out by filling the entire object rectangle, which is our drawing surface, with the default background color 0, using g:fill_all()
. This operation is special in that it not only fills the entire object rectangle, but also creates a standard border rectangle around it. If you skip this, you'll get an object without border, which may be useful at times.
We then go on to fill a circle with the background color, the dial's face. The graphics API has no operation to draw a circle, so we just draw an ellipse instead. The coordinates given to g:fill_ellipse()
are the coordinates of the rectangle surrounding the ellipse. In this case the width and height values are what we specified with self:set_size(127, 127)
in the initialize
method, so they are identical, and thus our ellipse is in fact a circle. Also note that we make the ellipse a little smaller and put it at a small offset from the upper left corner, so the actual width and height are reduced by 4 and the shape is centered in the object rectangle (or square, in this case).
Note that we could have skipped drawing the face entirely at this point, since it just draws a white circle on white background. But we could make the face a different color later, so it's good to include it anyway.
After the face we draw its border, drawing the same ellipse again, but this time in the default foreground color and with a stroke width of 4. We then go on to draw the remaining parts, a small disk in the center which mimics the shaft on which the single hand of the dial is mounted, and the hand itself, which is just a simple line pointing in a certain direction.
Which direction? I'm glad you asked. The line representing the hand goes from the center point width/2, height/2 to the point given by the x, y coordinates. Both width, height and x, y are calculated and assigned to local variables at the beginning of the paint
method:
xxxxxxxxxx
local width, height = self:get_size()
local x, y = self:tip()
The get_size()
call employs a built-in method which returns the current dimensions of the object rectangle; this is part of the graphics API. We could have used the constant 127 from the initialize
method there, but we could change the size of the object rectangle later, so it's better not to hard-code the size in the paint
method.
The tip()
method we have to define ourselves. It is supposed to calculate the coordinates of the tip of the hand. I have factored this out into its own routine right away, so that we can reuse it later when we add the mouse actions. Here it is:
xxxxxxxxxx
function dial:tip()
local width, height = self:get_size()
local x, y = math.sin(self.phase*math.pi), -math.cos(self.phase*math.pi)
x, y = (x/2*0.8+0.5)*width, (y/2*0.8+0.5)*height
return x, y
end
This basically just converts the position of the tip from polar coordinates (1, phase) to rectangular coordinates (x, y) and then translates and scales the normalized coordinates to pixel coordinates in the object rectangle which has its center at (width/2, height/2). We also put the tip at a normalized radius of 0.8 so that it is well within the face of the dial. Moreover, the formula computing the x, y pair accounts for the fact that the y coordinates of the object rectangle are upside-down (0 is at the top), and that we want the center-up (a.k.a. 12 o'clock) position to correspond to a zero phase angle. Hence the sin and cos terms have been swapped and the cos term adorned with a minus sign compared to the standard polar - rectangular conversion formula.
So now that we hopefully understand all the bits and pieces, here's the Lua code of the object in its entirety again:
xxxxxxxxxx
local dial = pd.Class:new():register("dial")
+
-- dial face
g:fill_ellipse(2, 2, width - 4, height - 4)
g:set_color(1)
-- dial border
g:stroke_ellipse(2, 2, width - 4, height - 4, 4)
-- center point
g:fill_ellipse(width/2 - 3.5, height/2 - 3.5, 7, 7)
-- dial hand
g:draw_line(x, y, width/2, height/2, 2)
end
The existence of the paint
method tells Pd-Lua that this is a graphical object. As mentioned before, this method receives a graphics context g
as argument. The graphics context is an internal data structure keeping track of the graphics state of the object, which is used to invoke all the graphics operations. The set_color
method of the graphics context is used to set the color for all drawing operations; in the case of fill
operations it fills the graphical element with the color, while in the case of stroke
operations it draws its border. There's just one color value, so we need to set the desired fill color in case of fill
, and the desired stroke color in case of stroke
operations. The color values 0 and 1 we use in this example are predefined, and indicate the default background color (usually white) and default foreground color (usually black), respectively.
It is possible to choose other colors by calling g:set_color(r, g, b)
with rgb color values instead, with each r, g, b value ranging from 0 to 255 (i.e., a byte value). For instance, the color "teal" would be specified as 0, 128, 128, the color "orange" as 255, 165, 0, "black" as 0, 0, 0, "white" as 255, 255, 255, etc. It's also possible to add a fourth alpha a.k.a. opacity value a, which is a floating point number in the range 0-1, where 0 means fully transparent, 1 fully opaque, and any value in between will blend in whatever is behind the graphical element to varying degrees. As of Pd-Lua 0.12.7, alpha values are fully supported in both plugdata and purr-data. In vanilla Pd they are simply ignored at present, so all graphical objects will be opaque no matter what alpha value you specify.
Let's now take a closer look at the drawing operations themselves. We start out by filling the entire object rectangle, which is our drawing surface, with the default background color 0, using g:fill_all()
. This operation is special in that it not only fills the entire object rectangle, but also creates a standard border rectangle around it. If you skip this, you'll get an object without border, which may be useful at times.
We then go on to fill a circle with the background color, the dial's face. The graphics API has no operation to draw a circle, so we just draw an ellipse instead. The coordinates given to g:fill_ellipse()
are the coordinates of the rectangle surrounding the ellipse. In this case the width and height values are what we specified with self:set_size(127, 127)
in the initialize
method, so they are identical, and thus our ellipse is in fact a circle. Also note that we make the ellipse a little smaller and put it at a small offset from the upper left corner, so the actual width and height are reduced by 4 and the shape is centered in the object rectangle (or square, in this case).
Note that we could have skipped drawing the face entirely at this point, since it just draws a white circle on white background. But we could make the face a different color later, so it's good to include it anyway.
After the face we draw its border, drawing the same ellipse again, but this time in the default foreground color and with a stroke width of 4. We then go on to draw the remaining parts, a small disk in the center which mimics the shaft on which the single hand of the dial is mounted, and the hand itself, which is just a simple line pointing in a certain direction.
Which direction? I'm glad you asked. The line representing the hand goes from the center point width/2, height/2 to the point given by the x, y coordinates. Both width, height and x, y are calculated and assigned to local variables at the beginning of the paint
method:
xxxxxxxxxx
local width, height = self:get_size()
local x, y = self:tip()
The get_size()
call employs a built-in method which returns the current dimensions of the object rectangle; this is part of the graphics API. We could have used the constant 127 from the initialize
method there, but we could change the size of the object rectangle later, so it's better not to hard-code the size in the paint
method.
The tip()
method we have to define ourselves. It is supposed to calculate the coordinates of the tip of the hand. I have factored this out into its own routine right away, so that we can reuse it later when we add the mouse actions. Here it is:
xxxxxxxxxx
function dial:tip()
local width, height = self:get_size()
local x, y = math.sin(self.phase*math.pi), -math.cos(self.phase*math.pi)
x, y = (x/2*0.8+0.5)*width, (y/2*0.8+0.5)*height
return x, y
end
This basically just converts the position of the tip from polar coordinates (1, phase) to rectangular coordinates (x, y) and then translates and scales the normalized coordinates to pixel coordinates in the object rectangle which has its center at (width/2, height/2). We also put the tip at a normalized radius of 0.8 so that it is well within the face of the dial. Moreover, the formula computing the x, y pair accounts for the fact that the y coordinates of the object rectangle are upside-down (0 is at the top), and that we want the center-up (a.k.a. 12 o'clock) position to correspond to a zero phase angle. Hence the sin and cos terms have been swapped and the cos term adorned with a minus sign compared to the standard polar - rectangular conversion formula.
So now that we hopefully understand all the bits and pieces, here's the Lua code of the object in its entirety again:
xxxxxxxxxx
local dial = pd.Class:new():register("dial")
function dial:initialize(sel, atoms)
self.inlets = 1
self.outlets = 0
self.phase = 0
self:set_size(127, 127)
return true
end
function dial:in_1_float(x)
self.phase = x
self:repaint()
end
-- calculate the x, y position of the tip of the hand
function dial:tip()
local width, height = self:get_size()
local x, y = math.sin(self.phase*math.pi), -math.cos(self.phase*math.pi)
x, y = (x/2*0.8+0.5)*width, (y/2*0.8+0.5)*height
return x, y
end
function dial:paint(g)
local width, height = self:get_size()
local x, y = self:tip()
-- standard object border, fill with bg color
g:set_color(0)
g:fill_all()
-
-- dial face
g:fill_ellipse(2, 2, width - 4, height - 4)
g:set_color(1)
-- dial border
g:stroke_ellipse(2, 2, width - 4, height - 4, 4)
-- center point
g:fill_ellipse(width/2 - 3.5, height/2 - 3.5, 7, 7)
-- dial hand
g:draw_line(x, y, width/2, height/2, 2)
end
Adding an outlet
We can already send phase values into our dial object, but there's no way to get them out again. So let's add an outlet which lets us do that. Now that the grunt work is already done, this is rather straightforward. First we need to add the outlet in the initialize
method:
xxxxxxxxxx
self.outlets = 1
And then we just add a message handler for bang
which outputs the value on the outlet:
xxxxxxxxxx
function dial:in_1_bang()
self:outlet(1, "float", {self.phase})
end
Easy as pie. Here's how our patch looks like now:
Mouse actions
Our dial now has all the basic ingredients, but it still lacks one important piece: Interacting with the graphical representation itself using the mouse. The graphics API makes this reasonably easy since it provides us with four callback methods that we can implement. Each of these gets invoked with the current mouse coordinates relative to the object rectangle:
mouse_down(x, y)
: called when the mouse is clicked
mouse_up(x, y)
: called when the mouse button is released
mouse_move(x, y)
: called when the mouse changes position while the mouse button is not pressed
mouse_drag(x, y)
: called when the mouse changes position while the mouse button is pressed
Here we only need the mouse_down
and mouse_drag
methods which let us keep track of mouse drags in the object rectangle in order to update the phase value and recalculate the tip of the hand (I told you that the tip()
method would come in handy again!). Here's the Lua code. Note that the mouse_down
callback is used to initialize the tip_x
and tip_y
member variables, which we keep track of during the drag operation, so that we can detect in mouse_drag
when it's time to output the phase value and repaint the object:
xxxxxxxxxx
function dial:mouse_down(x, y)
self.tip_x, self.tip_y = self:tip()
end
+
-- dial face
g:fill_ellipse(2, 2, width - 4, height - 4)
g:set_color(1)
-- dial border
g:stroke_ellipse(2, 2, width - 4, height - 4, 4)
-- center point
g:fill_ellipse(width/2 - 3.5, height/2 - 3.5, 7, 7)
-- dial hand
g:draw_line(x, y, width/2, height/2, 2)
end
Adding an outlet
We can already send phase values into our dial object, but there's no way to get them out again. So let's add an outlet which lets us do that. Now that the grunt work is already done, this is rather straightforward. First we need to add the outlet in the initialize
method:
xxxxxxxxxx
self.outlets = 1
And then we just add a message handler for bang
which outputs the value on the outlet:
xxxxxxxxxx
function dial:in_1_bang()
self:outlet(1, "float", {self.phase})
end
Easy as pie. Here's how our patch looks like now:
Mouse actions
Our dial now has all the basic ingredients, but it still lacks one important piece: Interacting with the graphical representation itself using the mouse. The graphics API makes this reasonably easy since it provides us with four callback methods that we can implement. Each of these gets invoked with the current mouse coordinates relative to the object rectangle:
mouse_down(x, y)
: called when the mouse is clicked
mouse_up(x, y)
: called when the mouse button is released
mouse_move(x, y)
: called when the mouse changes position while the mouse button is not pressed
mouse_drag(x, y)
: called when the mouse changes position while the mouse button is pressed
Here we only need the mouse_down
and mouse_drag
methods which let us keep track of mouse drags in the object rectangle in order to update the phase value and recalculate the tip of the hand (I told you that the tip()
method would come in handy again!). Here's the Lua code. Note that the mouse_down
callback is used to initialize the tip_x
and tip_y
member variables, which we keep track of during the drag operation, so that we can detect in mouse_drag
when it's time to output the phase value and repaint the object:
xfunction dial:mouse_down(x, y)
self.tip_x, self.tip_y = self:tip()
end
function dial:mouse_drag(x, y)
local width, height = self:get_size()
local x1, y1 = x/width-0.5, y/height-0.5
-- calculate the normalized phase, shifted by 0.5, since we want zero to be
-- the center up position
local phase = math.atan(y1, x1)/math.pi + 0.5
-- renormalize if we get an angle > 1, to account for the phase shift
if phase > 1 then
phase = phase - 2
end
self.phase = phase
local tip_x, tip_y = self:tip();
-
if tip_x ~= self.tip_x or tip_y ~= self.tip_y then
self.tip_x = tip_x
self.tip_y = tip_y
self:in_1_bang()
self:repaint()
end
end
And here's the same patch again, which now lets us drag the hand to change the phase value:
More dial action: clocks and speedometers
Now that our dial object is basically finished, let's do something interesting with it. The most obvious thing is to just turn it into a clock (albeit one with just a seconds hand) counting off the seconds. For that we just need to add a metro object which increments the phase angle and sends the value to the dial each second:
In the patch, we store the current phase angle in the phase
variable, so that it can be updated easily using the expr
object by just assigning to the variable. Note that we also update the variable whenever the dial outputs a new value, so you can also drag around the hand to determine its starting phase.
Here's another little example, rather useless but fun, simulating a speedometer which just randomly moves the needle left and right:
I'm sure you can imagine many more creative uses for this simple but surprisingly versatile little GUI object, which we did in just a few lines of fairly simple Lua code. Have fun with it! An extended version of this object, which covers some more features of the graphics API that we didn't discuss here to keep things simple, can be found as dial.pd and dial.pd_lua in the tutorial examples:
The extended example adds messages for resizing the object and setting colors, and also shows how to save and restore object state in the creation arguments using the set_args()
method mentioned at the beginning of this section. The accompanying patch covers all the examples we discussed here, and adds a third example showing how to utilize our dial object as a dB meter.
Live coding
I've been telling you all along that in order to make Pd-Lua pick up changes you made to your .pd_lua files, you have to relaunch Pd and reload your patches. Well, in this section we are going to discuss Pd-Lua's live coding features, which let you modify your sources and have Pd-Lua reload them on the fly, without ever exiting the Pd environment. This rapid incremental style of development is one of the hallmark features of dynamic programming environments like Pd and Lua. Musicians also like to employ it to modify their algorithmic composition programs live on stage, which is where the term "live coding" comes from. You'll probably be using live coding a lot while developing your Pd-Lua externals, but I've kept this topic for the final section of this guide, because it requires a good understanding of Pd-Lua's basic features. So without any further ado, let's dive right into it now.
First, we need to describe the predefined Pd-Lua object classes pdlua
and pdluax
, so that you know what's readily available. We'll also discuss how to add a reload
message to your existing object definitions. This is quite easy to do by directly employing Pd-Lua's dofile
method, which is also what both pdlua
and pdluax
use internally. These methods all work with older Pd-Lua versions, but they require a lot of manual fiddling with the Lua source and are are thus arduous and error-prone. That's why we also provide the special pdx.lua module which automatizes the entire process and is much less work than all the other approaches (basically, you just add two lines to your script and be done with it). Hence this is the method we recommend for all modern Pd-Lua versions. We describe it last so that you can also gather a good understanding of the "legacy" live coding methods on offer. But if you want something that just works with minimal effort, feel free to skip ahead to the pdx.lua section below which should be readable without any prior knowledge of the other approaches.
pdlua
The pdlua
object accepts a single kind of message of the form load filename
on its single inlet, which causes the given Lua file to be loaded and executed. Since pdlua
has no outlets, its uses are rather limited. However, it does enable you to load global Lua definitions and execute an arbitrary number of statements, e.g., to post some text to the console or transmit messages to Pd receivers using the corresponding Pd-Lua functions. For instance, here's a little Lua script loadtest.lua which simply increments a global counter
variable (first initializing it to zero if the variable doesn't exist yet) and posts its current value in the console:
xxxxxxxxxx
counter = counter and counter + 1 or 0
pd.post(string.format("loadtest: counter = %d", counter))
To run this Lua code in Pd, you just need to connect the message load loadtest.lua
to pdlua
's inlet (note that you really need to specify the full filename here, there's no default suffix):
Now, each time you click on the load loadtest.lua
message, the file is reloaded and executed, resulting in some output in the console, e.g.:
xxxxxxxxxx
loadtest: counter = 0
loadtest: counter = 1
Also, you can edit the script between invocations and the new code will be loaded and used immediately. E.g., if you change counter + 1
to counter - 1
, you'll get:
xxxxxxxxxx
loadtest: counter = 0
loadtest: counter = -1
That's about all there is to say about pdlua
; it's a very simple object.
pdluax
pdluax
is a bit more elaborate and allows you to create real Pd-Lua objects with an arbitrary number of inlets, outlets, and methods. To these ends, it takes a single creation argument, the basename of a .pd_luax file. This file is a Lua script returning a function to be executed in pdluax
's own initialize
method, which contains all the usual definitions, including the object's method definitions, in its body. This function receives the object's self
as well as all the extra arguments pdluax
was invoked with, and should return true
if creation of the object succeeded.
For instance, here's a simplified version of our foo
counter object, rewritten as a .pd_luax file, to be named foo.pd_luax:
xxxxxxxxxx
return function (self, sel, atoms)
self.inlets = 1
self.outlets = 1
self.counter = type(atoms[1]) == "number" and atoms[1] or 0
self.step = type(atoms[2]) == "number" and atoms[2] or 1
function self:in_1_bang()
self:outlet(1, "float", {self.counter})
self.counter = self.counter + self.step
end
return true
end
Note the colon syntax self:in_1_bang()
. This adds the bang method directly to the self
object rather than its class, which is pdluax
. (We obviously don't want to modify the class itself, which may be used to create any number of different kinds of objects, each with their own collection of methods.) Also note that the outer function is "anonymous" (nameless) here; you can name it, but there's usually no need to do that, because this function will be executed just once, when the corresponding pdluax
object is created. Another interesting point to mention here is that this approach of including all the object's method definitions in its initialization method works with regular .pd_lua objects, too; try it!
In the patch, we invoke a .pd_luax object by specifying the basename of its script file as pdluax
's first argument, adding any additional creation arguments that the object itself might need:
These pdluax foo
objects work just the same as their regular foo
counterparts, but there's an important difference: The code in foo.pd_luax is loaded every time you create a new pdluax foo
object. Thus you can easily modify that file and just add a new pdluax foo
object to have it run the latest version of your code. For instance, in foo.pd_luax take the line that reads:
xxxxxxxxxx
self.counter = self.counter + self.step
Now change that +
operator to -
:
xxxxxxxxxx
self.counter = self.counter - self.step
Don't forget to save your edits, then go back to the patch and recreate the pdluax foo
object on the left. The quickest way to do that is to just delete the object, then use Pd's "Undo" operation, Ctrl+Z. Et voilà: the new object now decrements the counter rather than incrementing it. Also note that the other object on the right still runs the old code which increments the counter; thus you will have to give that object the same treatment if you want to update it, too.
While pdluax
was considered Pd-Lua's main workhorse for live coding, it has its quirks. Most notably, the syntax is different from regular object definitions, so you have to change the code if you want to turn it into a .pd_lua file. Also, having to recreate an object to reload the script file is quite disruptive (it resets the internal state of the object), and may leave objects in an inconsistent state (different objects may use various different versions of the same script file). Sometimes this may be what you want, but it makes pdluax
somewhat difficult to use. It's not really tailored for interactive development, but it shines if you need a specialized tool for changing your objects on a whim in a live situation.
Fortunately, if you're not content with Pd-Lua's built-in facilities for live coding, it's easy to roll your own using the internal dofile
method, which is discussed in the next subsection.
dofile and dofilex
So let's discuss how to use dofile
in a direct fashion. Actually, we're going to use its companion dofilex
here, which works the same as dofile
, but loads Lua code relative to the "externdir" of the class (the directory of the .pd_lua file) rather than the directory of the Pd canvas with the pdlua object, which is what dofile
does. Normally, you don't have to worry about these intricacies, but they will matter if Lua class names are specified using relative pathnames, such as ../foo
or bar/baz
. Since we're reloading class definitions here, it's better to use dofilex
so that our code doesn't break if we later change the directory structure.
The method we sketch out below is really simple and doesn't have any of the drawbacks of the pdluax
object, but you still have to add a small amount of boilerplate code to your existing object definition. Here is how dofilex
is invoked:
self:dofilex(scriptname)
: This loads the given Lua script, like Lua's loadfile
, but also performs a search on Pd's path to locate the file, and finally executes the file if it was loaded successfully. Note that self
must be a valid Pd-Lua object, which is used solely to determine the externdir of the object's class, so that the script will be found if it is located there.
The return values of dofilex
are those of the Lua script, along with the path under which the script was found. If the script itself returns no value, then only the path will be returned. (We don't use any of this information in what follows, but it may be useful in more elaborate schemes. For instance, pdluax
uses the returned function to initialize the object, and the path in setting up the object's actual script name.)
Of course, dofilex
needs the name of the script file to be loaded. We could hardcode this as a string literal, but it's easier to just ask the object itself for this information. Each Pd-Lua object has a number of private member variables, among them _name
(which is the name under which the object class was registered) and _scriptname
(which is the name of the corresponding script file, usually this is just _name
with the .pd_lua
extension tacked onto it). The latter is what we need. Pd-Lua also offers a whoami()
method for this purpose, but that method just returns _scriptname
if it is set, or _name
otherwise. Regular Pd-Lua objects always have _scriptname
set, so it will do for our purposes.
Finally, we need to decide how to invoke dofilex
in our object. The easiest way to do this is to just add a message handler (i.e., an inlet method) to the object. For instance, say that the object is named foo
which is defined in the foo.pd_lua script. Then all you have to do is add something like the following definition to the script:
xxxxxxxxxx
function foo:in_1_reload()
self:dofilex(self._scriptname)
end
As we already discussed, this code uses the object's internal _scriptname
variable, and so is completely generic. You can just copy this over to any .pd_lua file, if you replace the foo
prefix with whatever the name of your actual class variable is. With that definition added, you can now just send the object a reload
message whenever you want to have its script file reloaded.
NOTE: This works because the pd.Class:new():register("foo")
call of the object only registers a new class if that object class doesn't exist yet; otherwise it just returns the existing class.
By reloading the script file, all of the object's method definitions will be overwritten, not only for the object receiving the reload
message, but for all objects of the same class, so it's sufficient to send the message to any (rather than every) object of the class. Also, existing object state (as embodied by the internal member variables of each object) will be preserved.
In general all this works pretty well, but there are some caveats, too. Note that if you delete one of the object's methods, or change its name, the old method will still hang around in the runtime class definition until you relaunch Pd. That's because reloading the script does not erase any old method definitions, it merely replaces existing and adds new ones.
Finally, keep in mind that reloading the script file does not re-execute the initialize
method. This method is only invoked when an object is instantiated. Thus, in particular, reloading the file won't change the number of inlets and outlets of an existing object. Newly created objects will pick up the changes in initialize
, though, and have the proper number of inlets and outlets if those member variables were changed.
Let's give this a try, using luatab.pd_lua from the "Using arrays and tables" section as an example. In fact, that's a perfect showcase for live coding, since we want to be able to change the definition of the waveform function f
in luatab:in_1_float
on the fly. Just add the following code to the end of luatab.pd_lua:
xxxxxxxxxx
function luatab:in_1_reload()
self:dofilex(self._scriptname)
end
Now launch the luatab.pd patch and connect a reload
message to the luatab wave
object, like so:
Next change the wavetable function to whatever you want, e.g.:
xxxxxxxxxx
local function f(x)
return math.sin(2*math.pi*freq*x)+1/3*math.sin(2*math.pi*3*freq*x)
end
Return to the patch, click the reload
message, and finally reenter the frequency value, so that the waveform gets updated:
pdx.lua
The method sketched out in the preceding subsection works well enough for simple patches. However, having to manually wire up the reload
message to one object of each class that you're editing is still quite cumbersome. In a big patch, which is being changed all the time, this quickly becomes unwieldy. Wouldn't it be nice if we could equip each object with a special receiver, so that we can just click a message somewhere in the patch to reload a given class, or even all Pd-Lua objects at once? And maybe even do that remotely from the editor, using the pdsend
program?
Well, all this is in fact possible, but the implementation is a bit too involved to fully present it here. So we have provided this in a separate pdx.lua module, which you can find in the sources accompanying this tutorial. Setting up an object for this kind of remote control is easy, though. First, you need to import the pdx
module into your script, using Lua's require
:
xxxxxxxxxx
local pdx = require 'pdx'
Then just call pdx.reload(self)
somewhere in the initialize
method. This will set up the whole receiver/dofilex machinery in a fully automatic manner. Finally, add a message like this to your patch, which goes to the special pdluax
receiver (note that this is completely unrelated to the pdluax
object discussed previously, it just incidentally uses the same name):
xxxxxxxxxx
; pdluax reload
When clicked, this just reloads all Pd-Lua objects in the patch, provided they have been set up with pdx.reload
. You can also specify the class to be reloaded (the receiver matches this against each object's class name):
xxxxxxxxxx
; pdluax reload foo
Or maybe name several classes, like so:
xxxxxxxxxx
; pdluax reload foo, reload bar
You get the idea. Getting set up for remote control via pdsend
isn't much harder. E.g., let's say that we use UDP port 4711 on localhost for communicating with Pd, then you just need to connect netreceive 4711 1
to the ; pdluax reload
message in a suitable way, e.g.:
You can then use pdsend 4711 localhost udp
to transmit the reload
message to Pd when needed. You probably don't want to run those commands yourself, but a decent code editor will let you bind a keyboard command which does this for you. Myself, I'm a die-hard Emacs fan, so I've included a little elisp module pd-remote.el which shows how to do this. Once you've added this to your .emacs, you can just type Ctrl+C Ctrl+K in Emacs to make Pd reload your Lua script after saving it. It doesn't get much easier than that.
Moreover, for your convenience I've also added a little abstraction named pd-remote.pd which takes care of the netreceive
and messaging bits and will look much tidier in your patches. Using the abstraction is easy: Insert pd-remote
into the patch you're working on, and connect a pdluax reload
message (without the ;
prefix) to the inlet of the abstraction. Now you can just click on that message to reload your script files. In fact any of the variations of reload messages discussed above will work, if you remove the ;
prefix, and the abstraction will also respond to such messages on port 4711 by default (the port number can be changed in the abstraction if needed).
NOTE: As of Pd-Lua 0.12.5, using pdx.lua has become much easier, since pdx.lua, pd-remote.el, and pd-remote.pd get installed in your pdlua external directory, and this directory now also gets searched for Lua imports. Thus require 'pdx'
will just work in all your Pd-Lua scripts without any further ado. To make Pd find the pd-remote.pd abstraction, you'll still have to copy it to your project directory. Or you can add the pdlua directory to your Pd library path. (Depending on how Pd-Lua was installed, you may want to do that anyway so that the pdlua directory shows up in Pd's help browser.)
The pd-remote.el file can be installed in your Emacs site-lisp directory if needed. Please also check the pd-remote repository on GitHub for the latest pd-remote version and further details. This also includes a pointer to a Visual Studio Code extension written by Baris Altun which can be used as a replacement for pd-remote.el if you're not familiar with Emacs, or just prefer to use VS Code as an editor.
So here's the full source code of our reworked luatab
example (now with the in_1_reload
handler removed and the pdx.reload
call added to the initialize
method):
xxxxxxxxxx
local luatab = pd.Class:new():register("luatab")
+
if tip_x ~= self.tip_x or tip_y ~= self.tip_y then
self.tip_x = tip_x
self.tip_y = tip_y
self:in_1_bang()
self:repaint()
end
end
And here's the same patch again, which now lets us drag the hand to change the phase value:
More dial action: clocks and speedometers
Now that our dial object is basically finished, let's do something interesting with it. The most obvious thing is to just turn it into a clock (albeit one with just a seconds hand) counting off the seconds. For that we just need to add a metro object which increments the phase angle and sends the value to the dial each second:
Pd lets us store the phase angle in a named variable (v phase
) which can be recalled in an expr
object doing the necessary calculations. The expr
object sends the computed value to the phase
receiver, which updates both the variable and the upper numbox, and the numbox then updates the dial. Note that we also set the variable whenever the dial outputs a new value, so you can also drag around the hand to determine the starting phase. And we added a 0
message to reset the hand to the 12 o'clock home position when needed.
Here's another little example, rather useless but fun, simulating a speedometer which just randomly moves the needle left and right:
I'm sure you can imagine many more creative uses for this simple but surprisingly versatile little GUI object, which we did in just a few lines of fairly simple Lua code. Have fun with it! An extended version of this object, which covers some more features of the graphics API that we didn't discuss here to keep things simple, can be found as dial.pd and dial.pd_lua in the tutorial examples:
The extended example adds messages for resizing the object and setting colors, and also shows how to save and restore object state in the creation arguments using the set_args()
method mentioned at the beginning of this section. The accompanying patch covers all the examples we discussed here, and adds a third example showing how to utilize our dial object as a dB meter.
Live coding
I've been telling you all along that in order to make Pd-Lua pick up changes you made to your .pd_lua files, you have to relaunch Pd and reload your patches. Well, in this section we are going to discuss Pd-Lua's live coding features, which let you modify your sources and have Pd-Lua reload them on the fly, without ever exiting the Pd environment. This rapid incremental style of development is one of the hallmark features of dynamic programming environments like Pd and Lua. Musicians also like to employ it to modify their algorithmic composition programs live on stage, which is where the term "live coding" comes from. You'll probably be using live coding a lot while developing your Pd-Lua externals, but I've kept this topic for the final section of this guide, because it requires a good understanding of Pd-Lua's basic features. So without any further ado, let's dive right into it now.
First, we need to describe the predefined Pd-Lua object classes pdlua
and pdluax
, so that you know what's readily available. We'll also discuss how to add a reload
message to your existing object definitions. This is quite easy to do by directly employing Pd-Lua's dofile
method, which is also what both pdlua
and pdluax
use internally. These methods all work with older Pd-Lua versions, but they require a lot of manual fiddling with the Lua source and are are thus arduous and error-prone. That's why we also provide the special pdx.lua module which automatizes the entire process and is much less work than all the other approaches (basically, you just add two lines to your script and be done with it). Hence this is the method we recommend for all modern Pd-Lua versions. We describe it last so that you can also gather a good understanding of the "legacy" live coding methods on offer. But if you want something that just works with minimal effort, feel free to skip ahead to the pdx.lua section below which should be readable without any prior knowledge of the other approaches.
pdlua
The pdlua
object accepts a single kind of message of the form load filename
on its single inlet, which causes the given Lua file to be loaded and executed. Since pdlua
has no outlets, its uses are rather limited. However, it does enable you to load global Lua definitions and execute an arbitrary number of statements, e.g., to post some text to the console or transmit messages to Pd receivers using the corresponding Pd-Lua functions. For instance, here's a little Lua script loadtest.lua which simply increments a global counter
variable (first initializing it to zero if the variable doesn't exist yet) and posts its current value in the console:
xxxxxxxxxx
counter = counter and counter + 1 or 0
pd.post(string.format("loadtest: counter = %d", counter))
To run this Lua code in Pd, you just need to connect the message load loadtest.lua
to pdlua
's inlet (note that you really need to specify the full filename here, there's no default suffix):
Now, each time you click on the load loadtest.lua
message, the file is reloaded and executed, resulting in some output in the console, e.g.:
xxxxxxxxxx
loadtest: counter = 0
loadtest: counter = 1
Also, you can edit the script between invocations and the new code will be loaded and used immediately. E.g., if you change counter + 1
to counter - 1
, you'll get:
xxxxxxxxxx
loadtest: counter = 0
loadtest: counter = -1
That's about all there is to say about pdlua
; it's a very simple object.
pdluax
pdluax
is a bit more elaborate and allows you to create real Pd-Lua objects with an arbitrary number of inlets, outlets, and methods. To these ends, it takes a single creation argument, the basename of a .pd_luax file. This file is a Lua script returning a function to be executed in pdluax
's own initialize
method, which contains all the usual definitions, including the object's method definitions, in its body. This function receives the object's self
as well as all the extra arguments pdluax
was invoked with, and should return true
if creation of the object succeeded.
For instance, here's a simplified version of our foo
counter object, rewritten as a .pd_luax file, to be named foo.pd_luax:
xxxxxxxxxx
return function (self, sel, atoms)
self.inlets = 1
self.outlets = 1
self.counter = type(atoms[1]) == "number" and atoms[1] or 0
self.step = type(atoms[2]) == "number" and atoms[2] or 1
function self:in_1_bang()
self:outlet(1, "float", {self.counter})
self.counter = self.counter + self.step
end
return true
end
Note the colon syntax self:in_1_bang()
. This adds the bang method directly to the self
object rather than its class, which is pdluax
. (We obviously don't want to modify the class itself, which may be used to create any number of different kinds of objects, each with their own collection of methods.) Also note that the outer function is "anonymous" (nameless) here; you can name it, but there's usually no need to do that, because this function will be executed just once, when the corresponding pdluax
object is created. Another interesting point to mention here is that this approach of including all the object's method definitions in its initialization method works with regular .pd_lua objects, too; try it!
In the patch, we invoke a .pd_luax object by specifying the basename of its script file as pdluax
's first argument, adding any additional creation arguments that the object itself might need:
These pdluax foo
objects work just the same as their regular foo
counterparts, but there's an important difference: The code in foo.pd_luax is loaded every time you create a new pdluax foo
object. Thus you can easily modify that file and just add a new pdluax foo
object to have it run the latest version of your code. For instance, in foo.pd_luax take the line that reads:
xxxxxxxxxx
self.counter = self.counter + self.step
Now change that +
operator to -
:
xxxxxxxxxx
self.counter = self.counter - self.step
Don't forget to save your edits, then go back to the patch and recreate the pdluax foo
object on the left. The quickest way to do that is to just delete the object, then use Pd's "Undo" operation, Ctrl+Z. Et voilà: the new object now decrements the counter rather than incrementing it. Also note that the other object on the right still runs the old code which increments the counter; thus you will have to give that object the same treatment if you want to update it, too.
While pdluax
was considered Pd-Lua's main workhorse for live coding, it has its quirks. Most notably, the syntax is different from regular object definitions, so you have to change the code if you want to turn it into a .pd_lua file. Also, having to recreate an object to reload the script file is quite disruptive (it resets the internal state of the object), and may leave objects in an inconsistent state (different objects may use various different versions of the same script file). Sometimes this may be what you want, but it makes pdluax
somewhat difficult to use. It's not really tailored for interactive development, but it shines if you need a specialized tool for changing your objects on a whim in a live situation.
Fortunately, if you're not content with Pd-Lua's built-in facilities for live coding, it's easy to roll your own using the internal dofile
method, which is discussed in the next subsection.
dofile and dofilex
So let's discuss how to use dofile
in a direct fashion. Actually, we're going to use its companion dofilex
here, which works the same as dofile
, but loads Lua code relative to the "externdir" of the class (the directory of the .pd_lua file) rather than the directory of the Pd canvas with the pdlua object, which is what dofile
does. Normally, you don't have to worry about these intricacies, but they will matter if Lua class names are specified using relative pathnames, such as ../foo
or bar/baz
. Since we're reloading class definitions here, it's better to use dofilex
so that our code doesn't break if we later change the directory structure.
The method we sketch out below is really simple and doesn't have any of the drawbacks of the pdluax
object, but you still have to add a small amount of boilerplate code to your existing object definition. Here is how dofilex
is invoked:
self:dofilex(scriptname)
: This loads the given Lua script, like Lua's loadfile
, but also performs a search on Pd's path to locate the file, and finally executes the file if it was loaded successfully. Note that self
must be a valid Pd-Lua object, which is used solely to determine the externdir of the object's class, so that the script will be found if it is located there.
The return values of dofilex
are those of the Lua script, along with the path under which the script was found. If the script itself returns no value, then only the path will be returned. (We don't use any of this information in what follows, but it may be useful in more elaborate schemes. For instance, pdluax
uses the returned function to initialize the object, and the path in setting up the object's actual script name.)
Of course, dofilex
needs the name of the script file to be loaded. We could hardcode this as a string literal, but it's easier to just ask the object itself for this information. Each Pd-Lua object has a number of private member variables, among them _name
(which is the name under which the object class was registered) and _scriptname
(which is the name of the corresponding script file, usually this is just _name
with the .pd_lua
extension tacked onto it). The latter is what we need. Pd-Lua also offers a whoami()
method for this purpose, but that method just returns _scriptname
if it is set, or _name
otherwise. Regular Pd-Lua objects always have _scriptname
set, so it will do for our purposes.
Finally, we need to decide how to invoke dofilex
in our object. The easiest way to do this is to just add a message handler (i.e., an inlet method) to the object. For instance, say that the object is named foo
which is defined in the foo.pd_lua script. Then all you have to do is add something like the following definition to the script:
xxxxxxxxxx
function foo:in_1_reload()
self:dofilex(self._scriptname)
end
As we already discussed, this code uses the object's internal _scriptname
variable, and so is completely generic. You can just copy this over to any .pd_lua file, if you replace the foo
prefix with whatever the name of your actual class variable is. With that definition added, you can now just send the object a reload
message whenever you want to have its script file reloaded.
NOTE: This works because the pd.Class:new():register("foo")
call of the object only registers a new class if that object class doesn't exist yet; otherwise it just returns the existing class.
By reloading the script file, all of the object's method definitions will be overwritten, not only for the object receiving the reload
message, but for all objects of the same class, so it's sufficient to send the message to any (rather than every) object of the class. Also, existing object state (as embodied by the internal member variables of each object) will be preserved.
In general all this works pretty well, but there are some caveats, too. Note that if you delete one of the object's methods, or change its name, the old method will still hang around in the runtime class definition until you relaunch Pd. That's because reloading the script does not erase any old method definitions, it merely replaces existing and adds new ones.
Finally, keep in mind that reloading the script file does not re-execute the initialize
method. This method is only invoked when an object is instantiated. Thus, in particular, reloading the file won't change the number of inlets and outlets of an existing object. Newly created objects will pick up the changes in initialize
, though, and have the proper number of inlets and outlets if those member variables were changed.
Let's give this a try, using luatab.pd_lua from the "Using arrays and tables" section as an example. In fact, that's a perfect showcase for live coding, since we want to be able to change the definition of the waveform function f
in luatab:in_1_float
on the fly. Just add the following code to the end of luatab.pd_lua:
xxxxxxxxxx
function luatab:in_1_reload()
self:dofilex(self._scriptname)
end
Now launch the luatab.pd patch and connect a reload
message to the luatab wave
object, like so:
Next change the wavetable function to whatever you want, e.g.:
xxxxxxxxxx
local function f(x)
return math.sin(2*math.pi*freq*x)+1/3*math.sin(2*math.pi*3*freq*x)
end
Return to the patch, click the reload
message, and finally reenter the frequency value, so that the waveform gets updated:
pdx.lua
The method sketched out in the preceding subsection works well enough for simple patches. However, having to manually wire up the reload
message to one object of each class that you're editing is still quite cumbersome. In a big patch, which is being changed all the time, this quickly becomes unwieldy. Wouldn't it be nice if we could equip each object with a special receiver, so that we can just click a message somewhere in the patch to reload a given class, or even all Pd-Lua objects at once? And maybe even do that remotely from the editor, using the pdsend
program?
Well, all this is in fact possible, but the implementation is a bit too involved to fully present it here. So we have provided this in a separate pdx.lua module, which you can find in the sources accompanying this tutorial. Setting up an object for this kind of remote control is easy, though. First, you need to import the pdx
module into your script, using Lua's require
:
xxxxxxxxxx
local pdx = require 'pdx'
Then just call pdx.reload(self)
somewhere in the initialize
method. This will set up the whole receiver/dofilex machinery in a fully automatic manner. Finally, add a message like this to your patch, which goes to the special pdluax
receiver (note that this is completely unrelated to the pdluax
object discussed previously, it just incidentally uses the same name):
xxxxxxxxxx
; pdluax reload
When clicked, this just reloads all Pd-Lua objects in the patch, provided they have been set up with pdx.reload
. You can also specify the class to be reloaded (the receiver matches this against each object's class name):
xxxxxxxxxx
; pdluax reload foo
Or maybe name several classes, like so:
xxxxxxxxxx
; pdluax reload foo, reload bar
You get the idea. Getting set up for remote control via pdsend
isn't much harder. E.g., let's say that we use UDP port 4711 on localhost for communicating with Pd, then you just need to connect netreceive 4711 1
to the ; pdluax reload
message in a suitable way, e.g.:
You can then use pdsend 4711 localhost udp
to transmit the reload
message to Pd when needed. You probably don't want to run those commands yourself, but a decent code editor will let you bind a keyboard command which does this for you. Myself, I'm a die-hard Emacs fan, so I've included a little elisp module pd-remote.el which shows how to do this. Once you've added this to your .emacs, you can just type Ctrl+C Ctrl+K in Emacs to make Pd reload your Lua script after saving it. It doesn't get much easier than that.
Moreover, for your convenience I've also added a little abstraction named pd-remote.pd which takes care of the netreceive
and messaging bits and will look much tidier in your patches. Using the abstraction is easy: Insert pd-remote
into the patch you're working on, and connect a pdluax reload
message (without the ;
prefix) to the inlet of the abstraction. Now you can just click on that message to reload your script files. In fact any of the variations of reload messages discussed above will work, if you remove the ;
prefix, and the abstraction will also respond to such messages on port 4711 by default (the port number can be changed in the abstraction if needed).
NOTE: As of Pd-Lua 0.12.5, using pdx.lua has become much easier, since pdx.lua, pd-remote.el, and pd-remote.pd get installed in your pdlua external directory, and this directory now also gets searched for Lua imports. Thus require 'pdx'
will just work in all your Pd-Lua scripts without any further ado. To make Pd find the pd-remote.pd abstraction, you'll still have to copy it to your project directory. Or you can add the pdlua directory to your Pd library path. (Depending on how Pd-Lua was installed, you may want to do that anyway so that the pdlua directory shows up in Pd's help browser.)
The pd-remote.el file can be installed in your Emacs site-lisp directory if needed. Please also check the pd-remote repository on GitHub for the latest pd-remote version and further details. This also includes a pointer to a Visual Studio Code extension written by Baris Altun which can be used as a replacement for pd-remote.el if you're not familiar with Emacs, or just prefer to use VS Code as an editor.
So here's the full source code of our reworked luatab
example (now with the in_1_reload
handler removed and the pdx.reload
call added to the initialize
method):
xxxxxxxxxx
local luatab = pd.Class:new():register("luatab")
-- our own pdlua extension, needed for the reload functionality
local pdx = require 'pdx'
function luatab:initialize(sel, atoms)
-- single inlet for the frequency, bang goes to the single outlet when we
-- finished generating a new waveform
self.inlets = 1
self.outlets = 1
-- enable the reload callback
pdx.reload(self)
-- the name of the array/table should be in the 1st creation argument
if type(atoms[1]) == "string" then
self.tabname = atoms[1]
return true
else
self:error(string.format("luatab: expected array name, got %s",
tostring(atoms[1])))
return false
end
end
function luatab:in_1_float(freq)
if type(freq) == "number" then
-- the waveform we want to compute, adjust this as needed
local function f(x)
return math.sin(2*math.pi*freq*(x+1))/(x+1)
end
-- get the Pd array and its length
local t = pd.Table:new():sync(self.tabname)
if t == nil then
self:error(string.format("luatab: array or table %s not found",
self.tabname))
return
end
local l = t:length()
-- Pd array indices are zero-based
for i = 0, l-1 do
-- normalize arguments to the 0-1 range
t:set(i, f(i/l))
end
-- this is needed to update the graph display
t:redraw()
-- output a bang to indicate that we've generated a new waveform
self:outlet(1, "bang", {})
else
self:error(string.format("luatab: expected frequency, got %s",
tostring(freq)))
end
end
And here's a little gif showing the above patch in action. You may want to watch this in Typora or your favorite web browser to make the animation work.
So there you have it: four different ways to live-code with Pd-Lua. Choose whatever best fits your purpose and is the most convenient for you.
Conclusion
Congratulations! If you made it this far, you should have learned more than enough to start using Pd-Lua successfully for your own projects. You should also be able to read and understand the many examples in the Pd-Lua distribution, which illustrate all the various features in much more detail than we could muster in this introduction. You can find these in the examples folder, both in the Pd-Lua sources and the pdlua folder of your Pd installation.
The examples accompanying this tutorial (including the pdx.lua, pdlua-remote.el and pdlua-remote.pd files mentioned at the end of the previous section) are also available for your perusal in the examples subdirectory of the folder where you found this document.
Finally, I'd like to thank Claude Heiland-Allen for creating such an amazing tool, it makes programming Pd externals really easy and fun. Kudos also to Roberto Ierusalimschy for Lua, which for me is one of the best-designed, easiest to learn, and most capable multi-paradigm scripting languages there are today, while also being fast, simple, and light-weight.
diff --git a/tutorial/pd-lua-intro.md b/tutorial/pd-lua-intro.md
index 195ffc5..83682ff 100644
--- a/tutorial/pd-lua-intro.md
+++ b/tutorial/pd-lua-intro.md
@@ -1038,7 +1038,7 @@ end
The existence of the `paint` method tells Pd-Lua that this is a graphical object. As mentioned before, this method receives a graphics context `g` as argument. The graphics context is an internal data structure keeping track of the graphics state of the object, which is used to invoke all the graphics operations. The `set_color` method of the graphics context is used to set the color for all drawing operations; in the case of `fill` operations it fills the graphical element with the color, while in the case of `stroke` operations it draws its border. There's just one color value, so we need to set the desired fill color in case of `fill`, and the desired stroke color in case of `stroke` operations. The color values 0 and 1 we use in this example are predefined, and indicate the default background color (usually white) and default foreground color (usually black), respectively.
-It is possible to choose other colors by calling `g:set_color(r, g, b)` with rgb color values instead, with each r, g, b value ranging from 0 to 255 (i.e., a byte value). For instance, the color "teal" would be specified as 0, 128, 128, the color "orange" as 255, 165, 0, "black" as 0, 0, 0, "white" as 255, 255, 255, etc. It's also possible to add a fourth *alpha* a.k.a. opacity value a, which is a floating point number in the range 0-1. 0 means fully transparent, 1 fully opaque, any value in between will blend in whatever is behind the graphical element to varying degrees. The alpha values are only supported in both plugdata and purr-data (as of Pd-Lua 0.12.7), in vanilla Pd they are simply ignored at present. So in vanilla Pd all graphical objects will be opaque no matter what alpha value you specify.
+It is possible to choose other colors by calling `g:set_color(r, g, b)` with rgb color values instead, with each r, g, b value ranging from 0 to 255 (i.e., a byte value). For instance, the color "teal" would be specified as 0, 128, 128, the color "orange" as 255, 165, 0, "black" as 0, 0, 0, "white" as 255, 255, 255, etc. It's also possible to add a fourth *alpha* a.k.a. opacity value a, which is a floating point number in the range 0-1, where 0 means fully transparent, 1 fully opaque, and any value in between will blend in whatever is behind the graphical element to varying degrees. As of Pd-Lua 0.12.7, alpha values are fully supported in both plugdata and purr-data. In vanilla Pd they are simply ignored at present, so all graphical objects will be opaque no matter what alpha value you specify.
Let's now take a closer look at the drawing operations themselves. We start out by filling the entire object rectangle, which is our drawing surface, with the default background color 0, using `g:fill_all()`. This operation is special in that it not only fills the entire object rectangle, but also creates a standard border rectangle around it. If you skip this, you'll get an object without border, which may be useful at times.
@@ -1187,7 +1187,7 @@ Now that our dial object is basically finished, let's do something interesting w
![18-graphics4](18-graphics4.png)
-In the patch, we store the current phase angle in the `phase` variable, so that it can be updated easily using the `expr` object by just assigning to the variable. Note that we also update the variable whenever the dial outputs a new value, so you can also drag around the hand to determine its starting phase.
+Pd lets us store the phase angle in a named variable (`v phase`) which can be recalled in an `expr` object doing the necessary calculations. The `expr` object sends the computed value to the `phase` receiver, which updates both the variable and the upper numbox, and the numbox then updates the dial. Note that we also set the variable whenever the dial outputs a new value, so you can also drag around the hand to determine the starting phase. And we added a `0` message to reset the hand to the 12 o'clock home position when needed.
Here's another little example, rather useless but fun, simulating a speedometer which just randomly moves the needle left and right: