The functionality of MKtl relies on descriptions of the devices to be used. For some controllers, there are already descriptions available, but chances are that your device is not yet among them.
This tutorial first shows simple examples of OSC interfaces, by emulating them within SuperCollider, and then describes how to create a description file for your interface.
The simplest possible OSC input would be a device sends messages with only a single value, such as this:
The description of this element then is:
An entry for \spec
in the description is required to recognize this dict as an element description!
Here is a complete working example of a description dict:
For a row of buttons, such devices often use the same message name, and send an index of the button to address, e.g. for two buttons:
The description of the elements then contains an argTemplate :
Here is how the argTemplate works: The incoming message is [\bt, 0, 1]; the name of the message is dropped, so the message content is [0, 1]; this message body is compared with the argTemplate [0] :
-> all elements contained in the argTemplate are the same as in the message body, so the message matches, and the element knows it should do its action.
For a grid layout of e.g. button elements, devices usually send row and column indices of the element along, so a grid of buttons would be:
Here the incoming message has 4 items: the name (which is dropped), the next two indices which are matched with the argTemplate, and the last element is the new value.
The description of the elements then is:
The argTemplate
can also contain nil
if there is an element in the osc message that needs to be ignored while filtering, see: argTemplate matching for more examples how argTemplates can be matched.
If the osc message sends some other info that we are not interested in inside the message, we can specify at which index we want to read the data (by default we take the last value in the osc message):
The description becomes:
After all this explanation, here is a full example of an OSC device description with one element using argTemplate, and valueAt specification. (Usually, there would be other knobs at at other row/column indices, they are left out here for shortness.)
Some devices may send a different on and off message, e.g. "/button/on"
for the on state, and "/button/off"
for the off state.
So the oscPath you want to match is "/button/*"
- the *
is the standin for the possible matches you want to look for. In an ItemsSpec you define the possible states that can be sent, in the order that they may appear, in our simple example here: ["off","on"]
which means that "off"
will map to 0 and "on"
will map to 1. If you added a third item ["off","maybe","on"]
, the middle item would map to 0.5.
In some cases, an OSC message has no relevant arguments and just functions as a trigger that a certain event happened. In this case you use the type \trigger
for the element. The output value of the element will always be 1.
A simple OSC output is an OSC interface that receives OSC messages with only a value for a single control on the device. For example:
With a description like:
Just like with the simple input example, we can have indices that define additional arguments that specify which led should be on.
With a description like:
If the value needs to be inserted in between identifiers for the message, you can use nil
as a placeholder in the argTemplate
With a description like:
An example of opening an OSC device from a description and sending output to it:
Some OSC devices will send the data for several elements within one OSC message. For example an OSC message updating all 5 buttons of a device:
In this case, we can create a collective
that will listen for the /buttons
message, and pass it on to the elements that are part of the collective:
As in the simple input examples, we can use an argTemplate
for collectives messages. Any wildcards (nil
) in the template will be assumed to contain relevant data. If you want a field to be ignored in the template, and it does not contain relevant data, you can use valueAt
to specify which indices in the OSC message contain the values that are your desired input data. See the following examples for clarification:
Matching a simple argument template with the message ID is at the beginning of the message body:
Matching an argument template with the ID to filter by at the end:
Here is an example for matching an argument template with a nil
wildcard at the beginning, but interesting values are only later on in the message, so we use valueAt
explicitly:
Some OSC devices will listen for data of several elements within a single OSC message. For example an OSC device may have 3 LEDs which are set with one message for the array of 3 LEDs:
In this case, we can create a collective
that will send the /leds
message, whenever the value of one of the elements of the collective changes:
If the value needs to be inserted in between identifiers for the message, you can use nil
as the place holder in the argTemplate
To find out what an OSC device can do, the first thing you can do is read the documentation for the device and see what it tells about the OSC interface. Usually the documentation lists a number of messages that the device sends out, and a number of messages that it reacts upon. For OSC output to the device you have to rely on the documentation about the device to find out what you need to send to it. For the OSC input from the device, there is a tool (OSCMon) to find out what messages it sends.
In this code example, we find out that there is a NetAddr( "127.0.0.1", 57120 )
sending an OSC message like this: [ '/pad', 0, 2, 67 ]
.
For your actual OSC device you would move each control and look at what the incoming message looks like. This way, you can figure out which controls send out which OSC message with which parameters.
For our (fake) example device, we find out that there is only one type of message sent out ('/pad'
), with the first two arguments being a row and column index, and the last value being a kind of velocity value.
Based on our exploration, we can start writing the description file, we first make a simple example for one pad:
So if this seems to work, we can expand the description to have a grid of 3 x 3 pads:
As an example, for the libmanta we find the description of (some of) the OSC output messages for the MantaOSC application:
From this we understand that we should make two types of output elements. For the button leds we need:
For the pad leds we need:
The spec
needed is a special one, a ItemsSpec that defines the mapping of a value to one of the three colors, we can define it in the specs
part of the device description:
For the complete Manta description take a look at its description file:
The underlying protocol of OSC communication in SuperCollider and most other applications is UDP. The addressing of messages is done via IP adresses and ports.
The MKtlDesc for an OSC device has an info dictionary which can contain: ipAddress, srcPort, recvPort, recvPort
. All of these elements are optional. Each of them are described in the following subsections.
For a general discussion on OSC Communication, see this guide: OSC Communication.
The ipAddress
field in the OSC device info is the ipAddress of the OSC device, so an incoming message will have this sending address. If the device is a physical device connected to your computer that uses a particular protocol that is decoded by another program to OSC messages (as is the case for the Manta), then the ipAddress
is the \localhost
or "127.0.0.1"
.
If the OSC device is its own device that connects to a local network (via WIFI or wired), then the device will have its own IP address, and it will be something like "192.168.2.42"
.
If the OSC device is only sending data, you may leave the ipAddress
undefined. Defining it however provides for an additional filter on the data - only messages from the defined ipAddress
will be used. If you have several of the same devices, you will need to define the ipAddress
for each to distinguish between the devices.
The srcPort
defines where the OSC messages come from. This is different from the port that the device sends the OSC messages to (see recvPort).
Some programs use a fixed port for this, others use a random port each time the OSC device is started (e.g. Max/MSP does not allow you to set the port that is sent from and uses a different one each time).
In case the OSC device uses a fixed port, you can use this to filter the data that comes in:
SuperCollider is usually listening on port 57120, but takes another port if that one happens to be not available. You can check the current port by asking:
If an OSC device wants to send OSC data to SuperCollider it needs to know the port to send to. As some OSC devices do not allow you to change the port to which they send, you can define a new port that SuperCollider opens to receive the data from this source:
If you leave the recvPort
undefined then SuperCollider's default port (NetAddr.langPort
) is used, and you need to configure your OSC device to send to that port.
If the OSC device can also receive messages, you need to know on which port it listens. You can find this in the documentation of the device. For sending a message to an OSC device you need to know both the ipAddress
and the recvPort
. If the recvPort
is left undefined, then the OSCMKtlDevice will use the same port as the srcPort
. If the ipAddress
is left undefined then the "127.0.0.1"
is used.
Some devices respond to special messages; e.g. one can send a message to the Manta to tell it to enable outside control of its LEDs. You can define these messages in the specialMessages
field of the device description. For OSC these have to be an Array of messages that need to be sent, each element of the Array contains the OSC path and the parameters that need to be sent.
For example for the Manta we need:
This section gives some examples of how the argTemplate
matching works in OSCdef works. In MKtl the argTemplate
follows the same principles.