Continuing along with our audio tech series – last week I talked about wrapping Pure Data, and this week I’ll take a look at exactly what a message is, so that I can move on to how I went about dispatching messages from/to the right Unity scripts and Pure Data sub-patches.
When we want to tell a patch in Pure Data to do something, we need to send it a message. Whenever FRACT needs to tell Pure Data to change a note, modulate a synth, move an audio source in 3D space, play a sample, or do pretty much anything else, it’s done by sending a collection of messages. Pure Data veterans might find this post a little basic, but it’s important to understand this stuff before what comes after. :)
When you’re editing a Pure Data patch, the simplest kind of message is sent by a message box connected directly to an object:
Here we have a cosine wave oscillator which starts at a frequency of 440hz. When we click on the message, the oscillator will switch immediately an octave down to 220hz.
In Pure Data, each message begins with a selector (which must be a symbol, usually a single word or dash-delimited-words), and then continues with zero or more entries (each of which is either a symbol or a number). The selector tells the receiving object what sort of thing to do, and the content gives more details. In object-oriented-programming terms, the selector is the method name, and the entries after it are the arguments. In the above example, the selector is “float” and it’s followed by an argument of 220. The osc~ object implements a method for “float” which sets its oscillation frequency.
Why is the method called “float,” rather than something more intuitive like “set-frequency”? “float” is actually one of a few special selectors, which signifies that we’re simply sending a single number to an object. In fact, there’s a shorthand for sending this kind of message, we can simply write:
While it looks like we’re just sending a message containing 220, when it sees a message box starting with a number Pure Data implicitly inserts the selector “float,” making the full message [float 220(. While you can mostly get by in Pure Data without knowing this detail, it comes up in corner cases (i.e. differentiating between [float 1( and [list 1(, which is a list containing one float) and is very important to be aware of if you’re sending messages from code. So remember, a message always has a selector, even if you can’t necessarily see it.
If you’re curious, there are a few other important selectors: “symbol,” which is usually used to send a single piece of text, and “list,” which is used in a lot of built-in objects for manipulating lists of things.
From code we actually need to do something a bit more complicated, though. Manually connecting objects is fine when authoring patches in the Pure Data editor, but we can’t easily do that from Unity. Instead we’re going to use a special object called “receive” to receive the message. We give it a name, then use the “send” object to send the message.
We can also include the destination in the message itself, prefixing a semicolon to tell Pure Data we’re sending an indirect message:
So in all, a message in Pure Data consists of a symbol identifying the receiver, a symbol identifying the selector, and then any number of symbols or numbers giving the arguments.
There’s a problem, though, in that all the names of our receivers are hard-coded. The examples given are very simple, but FRACT’s subtractive synth takes dozens of different messages to configure different elements. On top of that, if we have two copies of the same patch loaded, how do we send the message to one without sending it to the other? We need some convention for easily assigning different names to different patches and the messages they receive, which is what I’ll dig into next week!