OSC2MIDI map (.omm) files are simply collections of rules specifying the mapping of OSC to MIDI messages. This document describes the format of the mapping rules and explains their meaning, so that you can get up and running quickly using osc2midi and start creating your own mappings.
To get the most out of the description, you should be familiar with the format and meaning of both OSC and MIDI messages. Discussing OSC (Open Sound Control) and MIDI (Musical Instrument Digital Interface) is beyond the scope of this document, but there's a wealth of information available about these on the web. In particular, you may want to refer to the official OSC website and (the mirror of) Jeff Glatt's MIDI website.
A map file is a collection of mapping rules, one rule per line. Empty lines
and lines containing nothing but whitespace and comments will be ignored.
Whitespace serves to delimit the different tokens in a rule, but will be
ignored otherwise, so you can indent and format rules as you like, as long as
each rule remains on a single line. Everything following the hash symbol #
on a line is a comment which will be ignored as well.
A mapping rule will have the form of:
OscPath ArgTypes, VarName1, VarName2, ..., VarNameN : MIDIFUNCTION(args);
To accommodate the widest possible range of OSC applications, the syntax is
deliberately lenient with the OSC path syntax and will accept any string not
containing whitespace. Therefore path
and argtypes
must be delimited by
whitespace (even if argtypes
is empty).
In contrast, the osc2midi parser is fairly strict about the argtypes
string
and only allows valid OSC type symbols there.
In order to provide good error-checking, the parser is also picky about the
end of the line (anything which comes after the mapping rule). Only
whitespace, any number of semicolons and a line-end comment (# ...
) are
permitted there, anything else will be flagged as an error.
If you are familiar with BNF documentation of syntax there is a grammar table in the appendix at the bottom of this document.
Generally speaking, a mapping rule describes how OSC messages are to be converted to corresponding MIDI messages and vice versa. Consequently, they consist of two parts separated by a colon:
-
the left-hand side, which is an OSC message pattern in a symbolic form which specifies type and content of the OSC message to be converted;
-
the right-hand side, which is a MIDI message pattern in a symbolic form which specifies the type and content of the corresponding MIDI message.
Both parts may contain variables, placeholders for the actual values to be
converted. The variables may also be part of simple arithmetic expressions
involving the operators +
, -
, *
and /
which specify how a variable is
to be transformed by multiplying or dividing by a constant scale factor and
then adding or subtracting a constant offset, when converting between OSC
values and MIDI data bytes.
For instance, the following rule maps an OSC message /fader1
with a single
float
argument to a corresponding MIDI control change message (and vice
versa):
/fader1 f, x: controlchange(0, 7, x*127)
In this example, the x
argument in the OSC message is mapped to the
controller value x*127
, scaling the value from the standard OSC argument
range 0-1 to the MIDI data byte range 0-127 (to produce a MIDI data byte,
osc2midi will also round the value to a 7 bit integer). The 0
in the
controlchange
message indicates the first MIDI channel, while 7
denotes
the MIDI controller (the volume controller in this case). Conversely, when
converting from MIDI back to OSC, the same rule causes the 2nd data by of a
control change message for the volume controller on the first MIDI channel to
be converted to an OSC message for the OSC path /fader1
, dividing the value
of the 2nd data byte by 127 to yield the float
argument of the OSC message.
A semicolon and a line comment may optionally follow the rule, so you may also write something like:
/fader1 f, x: controlchange(0, 7, x*127); # map /fader1 to volume change
Anything following the #
symbol on a line is interpreted as a comment which
is ignored by the parser.
One pitfall for beginners is that the OSC path and the OSC type string must be separated by whitespace (even if the type string is empty), and that the type string must be followed by a comma, even if no arguments follow. Thus, if an OSC message has no arguments, it must be written like this:
/start , : rawmidi(250, 0, 0)
Note the whitespace before the comma; the parser accepts any non-whitespace character (including a comma) as part of the OSC path, so the whitespace is really needed there. (The blanks around the colon and the commas on the right-hand side are optional, however.)
The left-hand side of a rule can be omitted if it's the same as for the previous rule. This lets you write collections of MIDI conversions for the same OSC message pattern without having to retype the same left-hand side for each rule. For instance:
/xy1 ff, x, y : controlchange(0, 1, x*127) # map x to controller #1
: controlchange(0, 2, y*127) # and y to controller #2
This has exactly the same meaning as if you'd have written:
/xy1 ff, x, y : controlchange(0, 1, x*127) # map x to controller #1
/xy1 ff, x, y : controlchange(0, 2, y*127) # and y to controller #2
The syntax and meaning of the OSC and MIDI patterns are described in greater detail below.
The OSC message pattern on the left-hand side of a rule consists of the following elements:
-
The OSC path denotes the OSC address of the message, which normally specifies a control element of your OSC device (such as a button or a slider). It takes the form of an arbitrary string not containing whitespace and not beginning with a colon. osc2midi allows the OSC path to contain one or more special placeholders of the form
{i}
which may stand for an arbitrary integer constant embedded in the path which can then be associated with a variable. This makes it possible to write generic rules mapping an entire collection of OSC control elements to a single type of MIDI message; we'll discuss this under "Conversions" below. -
The OSC type string tells osc2midi about the number and types of the argument values of the message. Each argument type is denoted by a corresponding letter, as detailed below. As already mentioned, the OSC path and the type string must be separated by whitespace (even if the message doesn't have any arguments and thus the type string is empty), and the type string (even if it is empty) must be followed by a comma to separate it from the list of arguments (see below).
-
The last element of the message pattern is a comma-delimited argument list for each of the
{i}
placeholders in the path, and each of the message arguments specified in the type string. If an argument is not needed for the conversion, the corresponding spot may be left empty (but must still be followed by a,
or:
delimiter). A non-empty argument may be a constant, a range or a variable, as detailed below. You can also omit arguments at the end of the list if they aren't needed.
In the OSC type string, osc2midi readily supports OSC arguments of the
following numeric types: i
(integer), h
(long integer), f
(single
precision floating point numbers), d
(double precision floating point
numbers), c
(character code). It also recognizes the following special
constant designations and maps them to the corresponding values: T
(true,
1), F
(false, 0), N
(nil, 0), I
(impulse, 1). Internally, these are all
converted to double precision floating point numbers.
osc2midi also understands the m
(MIDI message) type which allows short MIDI
messages to be passed between OSC and MIDI devices in a direct fashion, using
the special midimessage
operation. Other customary types of message
arguments, like S
(symbol), s
(string), b
(blob) and t
(time tag) are
recognized, but are not supported by osc2midi at present; messages
containing such data will be silently ignored.
Argument lists are used to denote the content of an OSC or MIDI message. Arguments are delimited by commas, and if an argument is not needed it can also be omitted by leaving the corresponding spot empty. Valid arguments are:
-
Constants: A numeric constant (integer or floating point value) will be output as is. On input, it must be matched literally in the input to enable a rule. Examples:
0
,1
,-1.5
. -
Ranges: A pair of numeric constants separated by
-
denotes a range of eligible values for the corresponding argument. On input, any of the values in the given range matches. On output, the minimum value of the range will be used. Examples:0-1
,0-0.5
,0-127
. -
Variables: These let you refer to the variable parts of an OSC or MIDI message. The variable name may be any non-empty string of non-whitespace characters which doesn't look like a number and doesn't contain any of the special symbols delimiters
,
,:
and)
, or any of the operator symbols+
,-
,*
and/
used in conditioning (discussed below). Examples:foo
,bar99
,baz'
.
Optionally, a variable x
may be conditioned using a constant scale factor
a
and a constant offset b
as follows, to transform its range:
-
x*a+b
: scalex
bya
, offset the resulting value byb
-
b+a*x
: same as above, written in prefix form (prefix and postfix form can also be mixed, as inb+x*a
)
Thus, x*a+b
maps the range [0,1] to [b,a+b]. This transformation will be
applied automatically when outputting a message. On input, the reverse
transformation will be applied to the actual message argument (subtracting
b
, then dividing the result by a
) to give the value of the variable x
.
Both the scale factor a
and the offset b
can be omitted, in which case
they default to 1 and 0, respectively. If the scale factor is given, it must
be nonzero, otherwise the argument is considered to be a constant (the offset
value), and a warning message will be printed. Thus, e.g., 0*x+b
denotes
just b
.
There are also some alternative forms which are all equivalent to one of the above:
-
-x
: negatesx
(same as-1*x
) -
x-b
: offsetsx
by-b
(same as-b+x
) -
x/a
: scalesx
by1/a
The conditioning of OSC arguments is useful if the range of an integer or
floating point argument deviates from the standard [0,1] range. In such a case
it is often convenient to map the variable back to a normalized range such as
[0,1] or [-1,1]. For instance, if the OSC value ranges from -10 to 10, then
x*10
transforms x
to the [-1,1] range, and x*20-10
to the [0,1] range.
Occasionally it may also be useful to scale an OSC argument in the default
range to some larger range; e.g., a x/127
on the left-hand side scales the
x
variable to the [0,127] range of a MIDI data byte. However, more commonly
this kind of conditioning is done on the right-hand side of a rule, where
x*127
is used to achieve the same effect.
In any case, conditioning on the left-hand and the right-hand side of a rule can be mixed freely as needed. When constructing the output message, first any conditioning of a variable in the source pattern is applied in reverse, before its conditioning in the target pattern is applied. This gives you some leeway in formulating a rule. For instance, the following rule takes advantage of the automatic rounding of MIDI arguments to evenly distribute note messages among eight different MIDI channels:
/xy ff, x/127, y/127 : noteon( x/16, x, y )
Here we applied conditionings on the left-hand side of the rule to transform
the x
and y
variables to the [0,127] range. The x/16
expression on the
right-hand side then yields the MIDI channel of each note, scaling the note
number to a value between 0 and 7.
Note that it's always possible to achieve the same effect with conditioning on just one side of the rule; after all, the net effect is always just an affine transformation (scaling and offsetting a value). E.g., the above rule is in fact equivalent to the following:
/xy ff, x, y : noteon( 127*x/16, x*127, y*127 )
However, as the example shows, separating different transformations by applying the appropriate conditionings to both sides of a rule may sometimes help to simplify rules or at least make it easier to think about them.
The right-hand side of a mapping rule (everything which follows the colon)
takes the form of a function call, consisting of a function name denoting
the type (status byte) of a MIDI message, and an argument list enclosed in
( )
denoting the arguments (MIDI channel and data bytes) of the message.
All the usual MIDI voice messages are readily supported by osc2midi. They take the MIDI channel as the first argument and the requisite data bytes in the remaining arguments. One important thing to note here is that osc2midi consistently uses zero-based numbering throughout (just like in the actual binary encoding of MIDI messages). This affects, in particular, the MIDI channel numbers, so the first MIDI channel is denoted 0, not 1, and MIDI channel #10 (commonly used as the percussion channel on General MIDI instruments) has the number 9. Likewise, the MIDI program numbers used in program change messages are denoted 0-127, not 1-128 as in most printed MIDI bank charts, so the first program (the Grand Piano in General MIDI) is denoted 0, not 1.
The supported MIDI messages are as follows:
-
noteoff( chan, note, vel )
: A note off message (0x8n
status, wheren
denotes the MIDI channel) on the given MIDI channelchan
(0-15 range), for the given note numbernote
(0-127) and velocityvel
(0-127). -
noteon( chan, note, vel )
: A note on message (0x9n
status). This will also match any note off message (0x8n
status), interpreting it as a note on message with zero velocity, as mandated by the MIDI standard. -
polyaftertouch( chan, note, val )
: A polyphonic aftertouch message (0xAn
status). The second argument denotes the note number (0-127), the thirdval
argument the amount of pressure (0-127). -
controlchange( chan, num, val )
: A control change message (0xBn
status). Here the second argumentnum
denotes the controller number (0-127) and the third argumentval
the controller value (0-127). -
programchange( chan, num )
: A program change message (0xCn
status). This takes a single data byte denoting the program number (0-127) as its secondnum
argument. -
aftertouch( chan, val )
: A monophonic aftertouch a.k.a. channel pressure message (0xDn
status). Theval
argument denotes the amount of pressure. -
pitchbend( chan, val )
: A pitch bend message (0xEn
status). Theval
argument is a 14 bit number (0-16383), with 8192 denoting the center value. On output, osc2midi automatically converts this to two separate data bytes, the LSB and MSB values of the pitch bend message.
Note on and off messages can also be denoted using the note
function which
takes an extra argument:
note( chan, note, vel, state )
: This expands to a note off or on message, depending on the value ofstate
(zero denotes off, nonzero on).
There are also two additional functions to denote raw MIDI messages, which are useful to transfer message types which aren't readily supported by osc2midi:
-
rawmidi( status, byte1, byte2 )
: Denotes the MIDI message with the given status byte (128-255) and data bytes (0-127). Note that all three arguments must be specified, even if the message takes just one data byte or none at all. Just specify the extra arguments as zero, they will be ignored. -
midimessage( msg )
: The argument must always be an (unconditioned) variable here, which matches any MIDI message. The corresponding OSC argument must be of typem
.
The midimessage
function lets you pass short MIDI messages between the OSC
and the MIDI side of the bridge, by writing a rule like:
/msg m, msg: midimessage( msg )
To make this work, your OSC device must be able to handle such MIDI arguments.
The rawmidi
function provides you with a way to emit short system realtime
messages, which aren't readily supported by the built-in OSC MIDI functions.
For instance:
/start , : rawmidi( 250, 0, 0 )
/cont , : rawmidi( 251, 0, 0 )
/stop , : rawmidi( 252, 0, 0 )
OSC arguments can be passed as usual:
/select f, num: rawmidi( 243, num*127, 0 ) # song select message
Unfortunately, sysex messages can't be created this way, since they are all
longer than three bytes and thus can't be handled by osc2midi in the current
implementation. This limitation also holds for the midimessage
function.
Using the other types of MIDI messages is rather straightforward. Arguments of MIDI messages take the same format as in OSC patterns, thus they may be constants, ranges or variables. In the latter case, conditioning is often used to map the variable values from the OSC to the MIDI range. For instance:
/fader f, x: controlchange( 0, 7, x*127 )
This maps a fader movement (assuming the standard OSC value range 0-1
) to a
corresponding control change message on the first MIDI channel, using the
volume controller (controller #7). The third argument, the controller value,
maps the OSC value to a MIDI byte in the range 0-127
, by scaling the x
argument appropriately.
osc2midi automatically rounds MIDI arguments to integer values, using rounding
towards zero (i.e., truncating floating point values by cutting off their
fractional parts). To illustrate this, let's have another look at the /fader
rule from above:
/fader f, x: controlchange( 0, 7, x*127 )
An x
value of 0, 0.5 and 1 then gets mapped to 0, 63 and 127, respectively.
Note the 63, which is due to the fact that for x=0.5
, 127*x
is a little
less than 64 and thus gets rounded down to 63. This might be undesirable in
some situations where the value 64 is used to denote the center value. A
remedy for this will be discussed below.
osc2midi also makes sure that MIDI arguments will not overflow the appropriate
ranges. If an argument would cause overflow, it will be clamped to the
maximum possible value: 15 in the case of MIDI channels, 127 in the case of a
regular data byte, 255 in the case of a status byte (1st argument of
rawmidi
), and 16383 in the case of a pitch bend value. Likewise, if a MIDI
argument becomes negative, causing underflow, it will be clamped to a zero
value. So variables can be conditioned generously without having to worry
about illegal MIDI values. For instance, you may write:
/fader f, x: controlchange( 0, 7, x*128 )
This maps the OSC value 1 to 128 which isn't a legal MIDI data byte, but
osc2midi turns this into 127, so everything is all right. And the rule above
makes sure that an OSC value of 0.5 produces the MIDI data byte 64 which is
customarily used in MIDI to denote the center value. This solves the "center
value problem" mentioned above. (On the other hand, the MIDI controller value
127 will now produce a value a little less than 1 for the OSC argument x
when converting back from MIDI to OSC. Something has to give here, since the
MIDI data byte range 0-127 isn't symmetric about the 64 center value.)
Last but not least, there are three other special convenience functions which can occur on the right-hand side of a mapping rule. In contrast to the MIDI functions discussed above, these three are used solely for the purpose of setting various global parameters in osc2midi and don't generate or receive any MIDI messages:
-
setchannel( chan )
: Sets the value of the globalchannel
variable (0-15) which can be used in the first argument of voice messages the denote the MIDI channel. By default,channel
is set to zero, unless the user specified a different default value on the command line, using osc2midi's-c
option. -
setvelocity( vel )
: Sets the value of the globalvelocity
variable (0-127) which can be used in the third argument of note messages the denote the velocity value. By default,velocity
is set to 100, unless the user specified a different default value on the command line, using osc2midi's-vel
option. -
setshift( shift )
: Sets a global pitch shift value for note messages. This value isn't directly accessible in rules, but can be used to implement automatic transposition of notes using osc2midi's "MIDI filter" feature described below. The default shift value is zero, this can be set on the command line using osc2midi's-s
option.
Here's how setchannel
and setvelocity
are used to set the MIDI channel and
velocity values of generated MIDI notes:
/fader1 f, x: setchannel( 15*x )
/fader2 f, x: setvelocity( 127*x )
# generate notes using the channels and velocities set above:
/fader3 f, x: noteon( channel, x*127, velocity )
Note that the special channel
and velocity
variables can only be used in
the corresponding positions of a MIDI message. They can't be conditioned.
The setshift
function works differently. If it is used, osc2midi creates an
extra pair of Jack MIDI input and output ports named filter_in
and
filter_out
. Any MIDI note (or polyphonic aftertouch) message received at
filter_in
gets transposed by the amount of semitones denoted by the current
setshift
value and output to filter_out
. All other kinds of MIDI messages
are passed through unchanged.
You can then hook up osc2midi's midi_out
port to filter_in
and connect the
filter_out
port (instead of midi_out
) to your MIDI playback device, in
order to transpose generated note messages. This provides a quick way to
implement a simple transposition feature like on some MIDI keyboards. Note
that this functionality works independently of the other OSC MIDI conversion
facilities provided by osc2midi, so it will work just as well with any other
Jack MIDI source.
The proof of the pudding is in the eating, so let's finally discuss how osc2midi uses our mapping rules to convert incoming OSC and MIDI messages. For the most part, the conversion process is rather straightforward: match up incoming messages with rules and construct the converted messages by substituting variable values. But there are some interesting issues and corner cases which will be discussed in the following.
When converting from OSC to MIDI and vice versa, osc2midi applies mapping rules both ways. To these ends, it keeps monitoring its OSC and MIDI input ports for incoming messages. OSC messages are matched against the left-hand sides of rules, MIDI messages against the right-hand sides. We also call this the source pattern, the other side of the rule the target pattern. That is, for OSC messages the left-hand side is the source and the right-hand side the target pattern. For MIDI messages, it is the other way round.
A rule matches if there is a binding of the variables in the source pattern so that it matches the incoming message. The variable values are then substituted into the target pattern to yield the converted message which is sent to its OSC or MIDI output port, depending on the type of message. If none of the rules in the map file match then the input message is ignored.
For instance, consider:
/fader f, x : controlchange( 0, 7, 127*x )
If we receive the OSC message /fader 0.5
then the left-hand side of the
above rule matches with x=0.5
, so after substituting that value the
right-hand side becomes controlchange(0, 7, 63)
, because 127*0.5=63
after
rounding down. This is the converted message which is sent to osc2midi's MIDI
output.
Conversely, if we receive a control change on the first MIDI channel for
controller 7 with value 64, then this matches the right-hand side of the rule
with x=0.503937
(=64/127
, rounded to 6 decimals here). Substituting this
into the left-hand side gives the outgoing OSC message /fader 0.503937
.
All this happens in real time, i.e., osc2midi responds to incoming messages immediately (ideally without much latency, but this depends on the size of incoming messages, the number of mapping rules, cpu power and various other system characteristics).
In general, there may well be several rules that match an incoming message. osc2midi will go through all the rules of the loaded map file and output one converted message for each matching rule, in the same order in which the rules occur in the map file. Thus each input message can produce as many output messages as needed.
This is osc2midi's default or "multi" mode of operation, which should be
appropriate for most use cases. You can also invoke osc2midi with the
-single
option to have it emit a message only for the first rule that
matches, but this is rarely needed. In the following we generally assume that
osc2midi is running in "multi" mode.
For instance, consider:
/xy ff, x, y : controlchange( 0, 12, x*127 )
/xy ff, x, y : controlchange( 0, 13, y*127 )
This is a rather typical example of an OSC message containing multiple values which should be mapped to separate MIDI messages. As already mentioned, you can also abbreviate this as:
/xy ff, x, y : controlchange( 0, 12, x*127 )
: controlchange( 0, 13, y*127 )
Now, if we receive the OSC message /xy 0.5 0.2
, then both rules match and
osc2midi sends out two control change messages, controlchange(0, 12, 63)
from the first and controlchange(0, 13, 25)
from the second rule, in that
order.
Unfortunately, rules like the above also raise an issue with the reverse, MIDI
to OSC conversions. Clearly, the intention in the previous example is to map a
pair of values x
and y
in the OSC message (which might the coordinates of
some kind of x-y controller) to a corresponding pair of MIDI control changes.
The reverse conversion should keep this intact. That is, we'd like to be able
to convert a pair of control changes for the MIDI controllers 12 and 13 back
to a /xy x y
OSC message.
However, when the first rule is applied in MIDI to OSC conversion, upon
receiving a message for controller 12 the current value of y
isn't known
since it's not in the MIDI message, and the same holds for x
in the second
rule. If we simply use zero default values for the unbound variables, then one
of the OSC message arguments would always be zero, which is not what we want
here.
To remedy this, osc2midi automatically keeps track of groups of related rules (i.e., rules with the same OSC path and argument types) and records previous values for all the arguments in these rules. The values get recorded whenever an OSC message triggers a rule in the group, and also if a MIDI message is received which carries a value for one of the OSC message arguments.
Thus, if the message controlchange(0, 12, x*127)
is received then the value
of x
is remembered and gets output again when subsequently the message
controlchange(0, 13, y*127)
is received, along with the y
value in the
second message (which then gets remembered for subsequent messages as well).
So, if the controller messages arrive in pairs then we get the behavior that's
expected in most cases:
controlchange( 0, 12, x1*127 ) --> /xy x1 0.0 # y unknown, use default
controlchange( 0, 13, y1*127 ) --> /xy x1 y1 # use recorded x value
controlchange( 0, 12, x2*127 ) --> /xy x2 y1 # use recorded y value
controlchange( 0, 13, y2*127 ) --> /xy x2 y2 # use recorded x value
The same applies if you want to send a fixed control change message if an OSC control changes, no matter what the current value is:
/fader f, x : controlchange( 0, 80, 127 )
Here a control change for controller 80 with value 127 is sent, ignoring the
x
value in the message. In such a case you can also omit the variable:
/fader f, : controlchange( 0, 80, 127 )
If you leave the spot of an argument in the source pattern empty, then any value matches there (and will be ignored). However, the value will still be recorded for subsequent MIDI to OSC conversions:
/fader 0.5 --> controlchange( 0, 80, 127 ) # x value 0.5 is recorded
controlchange( 0, 80, 127 ) --> /fader 0.5 # use recorded x value
Another special case arises if the source pattern contains multiple instances of the same variable. For instance:
/fader f, x/127 : noteon( x/64, x, 127 )
The above rule can be used to implement a simple kind of "split keyboard"
which emits MIDI notes in the lower range 0-63
on the first, and notes in
the upper range 64-127
on the second MIDI channel.
But what happens in MIDI to OSC conversions? The problem here is that the MIDI
pattern will usually give two different possible bindings for the x
variable. E.g., consider the MIDI message noteon(0, 48, 127)
. Then x
equals 0
(0*64
) in the first argument, and 48
in the second one. So
which of the two values are we going to use?
Right now osc2midi uses a very simple strategy to resolve such ambiguities:
simply pick one of the values and run with it. As it's implemented, on the
right-hand side of a rule this happens to be the rightmost occurrence of a
variable, which makes sense because the channel argument, being a 4 bit value,
usually has a much lower resolution than the second and third arguments. So
in our example osc2midi would go with the x=48
binding.
Incidentally, this is also the mathematically correct solution, since x/64
then gives 0
after rounding down, so the right-hand side of the rule
actually matches with this binding. But note that osc2midi will not actually
verify this in its normal mode of operation. Thus, e.g., osc2midi will also
happily convert noteon(1, 48, 127)
to /fader 0.377953
even though this
message does not match the right-hand side of this rule.
As a remedy, osc2midi also offers a "strict" mode (which is enabled by
invoking it with the -strict
option) in which it actually verifies the
consistency of variable bindings if the same variable occurs more than once in
the source pattern. It will then accept noteon(0, 48, 127)
with x=48
, but
reject noteon(1, 48, 127)
, because the binding x=48
is inconsistent with
the channel argument 1
in the first argument.
Multiple occurrences of variables in OSC patterns are handled in an analogous fashion. For instance, consider:
/xy ff, x, x : controlchange( 0, 12, x*127 )
In strict mode, this rule will only be matched if both arguments of the OSC
message are exactly the same. No rounding is performed in the OSC case. Also
note that on the left-hand side osc2midi always picks the leftmost
occurrence of a variable. Thus in non-strict mode /xy 0 y
will always be
converted to controlchange(0, 12, 0)
, no matter what the value of y
is.
As mentioned previously, values in the input message can also be matched against constants and ranges in the source pattern. Constants are typically used in situations where a control only emits certain discrete values, such as a button control which only has the values 0 and 1:
/button f, 0 : controlchange( 0, 80, 0 )
/button f, 1 : controlchange( 0, 80, 127 )
This maps the OSC values 0.0 and 1.0 of the /button
control to a
corresponding MIDI control change (controller 80 in this example) with values
0 and 127, and vice versa. Note that in this case the given values have to be
matched exactly by the input message, any other input values will cause the
input message to be ignored.
The input value can also be matched against a range of values. For instance:
/fader f, 0-0.5 : controlchange( 0, 80, 0 )
/fader f, 0.5-1 : controlchange( 0, 80, 127 )
This realizes a kind of gate function where the controller value becomes 0 or
127, depending on whether the actual input value is below or above 0.5,
respectively. (Note that the message /fader 0.5
will trigger both rules
here, since 0.5 belongs to both ranges.)
Ranges can be used on both sides of a rule, so you may write:
/fader f, 0-0.5 : controlchange( 0, 80, 0-64 )
/fader f, 0.5-1 : controlchange( 0, 80, 64-127 )
If such a rule is triggered, the lower bound of the range in the target pattern is used as the target value. E.g.:
/fader 1.0 --> controlchange( 0, 80, 64 )
controlchange( 0, 80, 0 ) --> /fader 0.0
Using the {i}
placeholders makes it possible to extract integer values from
the OSC path of a message and bind them to a variable. This is often used with
groups of related controls. For instance, assume some faders /fader/1
,
/fader/2
etc. which should be mapped to corresponding MIDI controllers:
/fader/1 f, x : controlchange( 0, 1, x*127 )
/fader/2 f, x : controlchange( 0, 2, x*127 )
With a {i}
placeholder it becomes possible to condense these rules into a
single generic rule:
/fader/{i} f, k, x : controlchange( 0, k, x*127 )
Note the index variable k
which is bound to the actual control number
represented by the {i}
placeholder. The left-hand side can now be matched
against any OSC path of the form /fader/k
with integer k
:
/fader/1 1.0 --> controlchange( 0, 1, 127 )
/fader/2 1.0 --> controlchange( 0, 2, 127 )
/fader/9 1.0 --> controlchange( 0, 9, 127 )
Conversely, just as one might expect, values are also substituted into the
{i}
placeholders in the OSC path in MIDI to OSC conversions:
controlchange( 0, 1, 127 ) --> /fader/1 1.0
controlchange( 0, 2, 127 ) --> /fader/2 1.0
controlchange( 0, 9, 127 ) --> /fader/9 1.0
We mention in passing that it's also possible to have multiple {i}
placeholders in a single rule, each bound to their own variable, but this is
used much less frequently.
Here are some examples illustrating the rule syntax and the concepts discussed above. More examples can be found in default.omm and the other sample maps included in the osc2midi distribution.
TBD: faders, knobs, buttons, encoders, xy controls, multi controls: multi faders, radio buttons, button arrays, keyboards, split keyboard
The syntax of rules is described by the following grammar in extended BNF. The meaning of the various elements of a rule is discussed in the text.
rule ::= [ osc-pattern ] ':' midi-pattern
osc-pattern ::= path argtypes ',' [ arglist ]
midi-pattern ::= command '(' arglist ')'
path ::= OSC path string, must be non-empty
argtypes ::= OSC type string, may be empty
arglist ::= [ arg ] { ',' [ arg ] }
arg ::= [ pre ] var [ post ] | number | number '-' number
pre ::= '-' | [ number add-op ] [ number '*' ]
post ::= [ mul-op number ] [ add-op number ]
add-op ::= '+' | '-'
mul-op ::= '*' | '/'
var ::= may contain anything but operators, comma and ':' or ')'
delimiter, must not be empty