Skip to content

Messages

A message describes what leaves a control when its state changes. A control can carry any number of them, of any mix of types.

import py2tosc

fader = py2tosc.fader(name="cutoff")
fader.messages.append(py2tosc.OscMessage())
fader.messages.append(py2tosc.MidiMessage())

Messages in the editor

OSC

OscMessage defaults to sending the control's x value to /<control name> on every change, over every connection.

The address and the arguments are both built from Partial segments. A partial contributes either a constant, a property of the control, one of its values, or its index within its parent.

from py2tosc import OscMessage, Partial

message = OscMessage(
    path=[
        Partial("CONSTANT", "STRING", "/"),
        Partial("PROPERTY", "STRING", "parent.name"),
        Partial("CONSTANT", "STRING", "/"),
        Partial("PROPERTY", "STRING", "name"),
    ],
    arguments=[
        Partial("VALUE", "FLOAT", "x", scale_min=0, scale_max=127),
    ],
)

That builds /<parent name>/<control name> with the fader position scaled to 0-127.

The XML it produces:

<osc>
|__<enabled>
|__<send>
|__<receive>
|__<feedback>
|__<noDuplicates>
|__<connections>
|__<triggers>
|  |__<trigger>
|     |__<var>
|     |__<condition>
|__<path>
|  |__<partial>
|     |__<type>
|     |__<conversion>
|     |__<value>
|     |__<scaleMin>
|     |__<scaleMax>
|__<arguments>
   |__<partial>

MIDI

A MIDI message in the editor

MidiMessage pairs a MidiCommand -- the status bytes -- with three MidiValue slots that supply the data.

from py2tosc import MidiCommand, MidiMessage, MidiValue

message = MidiMessage(
    message=MidiCommand("CONTROLCHANGE", channel=0, data1=74),
    values=[
        MidiValue("CONSTANT", "", 0, 15),
        MidiValue("INDEX", "", 0, 1),
        MidiValue("VALUE", "x", 0, 127),
    ],
)
<midi>
|__<enabled>
|__<send>
|__<receive>
|__<feedback>
|__<noDuplicates>
|__<connections>
|__<triggers>
|__<message>
|  |__<type>
|  |__<channel>
|  |__<data1>
|  |__<data2>
|__<values>
   |__<value>
      |__<type>
      |__<key>
      |__<scaleMin>
      |__<scaleMax>

Fixed in py2tosc

tosclib 0.3.x wrote MIDI bindings with a trigger's fields in place of the status bytes, and used a <midivalue> tag the format does not define. Any MIDI binding it produced was invalid.

Local

LocalMessage sends to another control in the same layout rather than out over the network. It needs the destination's id:

from py2tosc import LocalMessage

readout = doc.find("readout")
fader.messages.append(
    LocalMessage(value="x", dst_type="VALUE", dst_var="text", dst_id=readout.id)
)

dst_type says what kind of thing is being written on the destination, so it takes VALUE or PROPERTY -- not a conversion. conversion is the separate field that decides the type the content is converted to on the way.

<local>
|__<enabled>
|__<triggers>
|__<type>
|__<conversion>
|__<value>
|__<scaleMin>
|__<scaleMax>
|__<dstType>
|__<dstVar>
|__<dstID>

Gamepad

GamepadMessage binds a game controller button or axis to one of the control's values. It is the one one-directional binding: the controller drives the control, so there are no triggers and nothing to send.

from py2tosc import GamepadMessage

fader.messages.append(
    GamepadMessage(type="AXIS_LEFT_Y", target_var="x", scale_min=0, scale_max=1)
)
<gamepad>
|__<enabled>
|__<connections>
|__<type>
|__<conversion>
|__<scaleMin>
|__<scaleMax>
|__<targetType>
|__<targetVar>

Triggers

Every message carries Trigger entries saying what causes it to fire. The default fires on any change to x.

from py2tosc import OscMessage, Trigger

# only when the control is touched, and only on the press
OscMessage(triggers=[Trigger(var="touch", condition="RISE")])

Connections

connections is one character per connection slot, 1 for enabled. TouchOSC 1.5 has ten slots, and the default enables all of them.

OscMessage(connections="1000000000")   # first connection only

Shorter ways to say the same thing

The dataclasses above mirror the file format, so they say everything and assume nothing. That is right for reading a file someone else wrote and long-winded for writing one, where a single idea can cost four objects and a dozen positional arguments.

py2tosc.ui builds the same dataclasses from a shorter description. Addresses take an f-string-like template, and both ends of a local binding are described with the same handful of constructors:

from py2tosc import ui

fader.messages.append(ui.osc("/synth/{parent.name}/{name}"))
fader.messages.append(ui.midi_cc(74))
button.messages.append(ui.connect(readout, source=ui.prop("name"), to="text", on="RISE"))

The same four source constructors describe an OSC argument, a MIDI slot and either end of a local binding, so a byte can be drawn from the control rather than written out. A keyboard whose buttons name their own notes needs one line and no numbering:

for key in keyboard:
    key.messages.append(ui.midi_note(ui.prop("name")))

Nothing there reaches a file that a hand-written OscMessage could not, and the two styles mix freely. The module is provisional: it may change in a minor release, where the rest of the API may not. Since the two styles mix, a message written the long way is always available as a fallback.