To start using the libmapper with Pure Data you will need to:
[mapper]external object from our downloads page. Alternatively, you can build the object from source instead.
To create a libmapper device, it is necessary to provide a device
name to the
[mapper] object using the property
There is an initialization period after a device is created where
a unique ordinal is chosen to append to the device name.
This allows multiple devices with the same name to
exist on the network. If no name is given libmapper will choose a
name for your device starting with the string "puredata".
If desired, a device definition file can be specified with the property
@def, which the external will use to add signals
to your device. Otherwise, you device will start with no inputs
or outputs and you will need to add them using messages. In this tutorial,
we will assume that you do not have a prepared device definition file.
A third optional parameter of the
[mapper] object is a network
interface name. By default, libmapper will try to guess which network
interface to use for mapping, defaulting to the local loopback interface
ethernet or wifi is not available. You can force the object to use a
particular interface by using the
An example of creating a device:
Once the object has initialized, it will output its metadata from the right outlet:
Now that we have created a device, we only need to know how to add signals in order to give our program some input/output functionality.
We'll start with creating a "sender", so we will first talk about how to update output signals.
Creating a signal requires two pieces of information:
integer, 'f' for
Additional signal properties can also (optionally) be added:
The only required parameters here are the signal name,
and data type. If no
length property is provided, the signal is
assumed to have length 1. A signal name should start with "/",
as this is how it is represented in the OSC address.
(One will be added if you forget to do this.)
Finally, supported types are currently 'i' or 'f' for
float values, respectively.
The other parameters are not strictly required, but the more
information you provide, the more the mapper can do some things
automatically. For example, if
maximum are provided,
it will be possible to create linear-scaled connections very quickly.
unit is provided, the mapper will be able to similarly figure out
a linear scaling based on unit conversion. (Centimeters to inches for
example.) Currently automatic unit-based scaling is not a supported
feature, but will be added in the future. You can take advantage of
this future development by simply providing unit information whenever
it is available. It is also helpful documentation for users.
An example of creating a "barebones"
int scalar output signal with
no unit, minimum, or maximum information:
An example of a
float signal where some more information is provided:
So far we know how to create a device and to specify an output signal for it.
We can imagine the above program getting sensor information in a loop. It could be running on a computer and reading data from an Arduino over a USB serial port, or it could just be a mouse-controlled GUI slider. However it's getting the data, it must provide it to libmapper so that it will be sent to other devices if that signal is mapped.
This is accomplished by passing messages to the
starting with the signal name:
(<signal_name> <value>) |
So in the "sensor 1 voltage" example, assuming that we have some code
which reads sensor 1's value into a float variable in
the patch becomes:
This is about all that is needed to expose sensor 1's voltage to the network as a mappable parameter. The libmapper GUI can now be used to create a mapping between this value and a receiver, where it could control a synthesizer parameter or change the brightness of an LED, or whatever else you want to do.
Most synthesizers of course will not know what to do with "voltage"--it is an electrical property that has nothing to do with sound or music. This is where libmapper really becomes useful.
Scaling or other signal conditioning can be taken care of before exposing the signal, or it can be performed as part of the mapping. Since the end user can demand any mathematical operation be performed on the signal, he can perform whatever mappings between signals as he wishes.
As a developer, it is therefore your job to provide information that will be useful to the end user.
For example, if sensor 1 is a position sensor, instead of publishing "voltage", you could convert it to centimeters or meters based on the known dimensions of the sensor, and publish a "/sensor1/position" signal instead, providing the unit information as well.
We call such signals "semantic", because they provide information with more meaning than a relatively uninformative value based on the electrical properties of the sensing technique. Some sensors can benefit from low-pass filtering or other measures to reduce noise. Some sensor data may need to be combined in order to derive physical meaning. What you choose to expose as outputs of your device is entirely application-dependent.
You can even publish both "/sensor1/position" and "/sensor1/voltage" if desired, in order to expose both processed and raw data. Keep in mind that these will not take up significant processing time, and zero network bandwidth, if they are not mapped.
Receiving signals is even easier: after adding an input using the
message, updates for this signal will be routed to the left output
[mapper] object. Let's try making two devices in the same patch
If you use your mapping GUI to create a link between the two devices sender and receiver and a connection between your two signals /sendsig and /recvsig, any change made to the float value on the left will cause a corresponding output on the right.
Congratulations - you have created your first mapping connection! This probably seems quite simplistic, since you could have made a patch-cord between the two float objects and accomplished the same thing, but your "mapping" represents something more:
For patches with only outputs, lazy users can also declare the signals
learn mode. You can either send the message
"learn 1" to turn on learning ("learn 0" to turn it off) or you can
[mapper] object with the learn property set as an
the object will watch its inlet for messages formatted as
and add any unknown signals automatically. Signals added this way will not
have associated minimum or maximum values, however, so it is usually
recommended to add the signals explicitly using messages.
Things like device names, signal units, and ranges, are examples of metadata--information about the data you are exposing on the network.
libmapper also provides the ability to specify arbitrary extra metadata in the form of name-value pairs. These are not interpreted by libmapper in any way, but can be retrieved over the network. This can be used for instance to label a device with its location, or to perhaps give a signal some property like "reliability", or some category like "light", "motor", "shaker", etc.
Some GUI implementing a Monitor could then use this information to display information about the network in an intelligent manner.
Any time there may be extra knowledge about a signal or device, it is a good idea to represent it by adding such properties, which can be of any OSC-compatible type. (So, numbers and strings, etc.)
The Pure Data bindings for libmapper do not currently allow dynamically changing the properties of a device or signal, however they can be declared when the entity is created by using jitter-style property arguments
For example, to store a
float indicating the X position of a device
dev, you could instantiate your object like this:
To specify a string property of a signal:
In general you can use any property name not already in use by the
device or signal data structure. Reserved words for signals are:
for devices, they are: