Skip to content

Latest commit

 

History

History
745 lines (562 loc) · 34.6 KB

syntax.md

File metadata and controls

745 lines (562 loc) · 34.6 KB

OSC2MIDI Map File Syntax

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.

Syntax

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.

Rules

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.

OSC Patterns

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.

Arguments

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: scale x by a, offset the resulting value by b

  • b+a*x: same as above, written in prefix form (prefix and postfix form can also be mixed, as in b+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: negates x (same as -1*x)

  • x-b: offsets x by -b (same as -b+x)

  • x/a: scales x by 1/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.

MIDI Patterns

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, where n denotes the MIDI channel) on the given MIDI channel chan (0-15 range), for the given note number note (0-127) and velocity vel (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 third val argument the amount of pressure (0-127).

  • controlchange( chan, num, val ): A control change message (0xBn status). Here the second argument num denotes the controller number (0-127) and the third argument val 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 second num argument.

  • aftertouch( chan, val ): A monophonic aftertouch a.k.a. channel pressure message (0xDn status). The val argument denotes the amount of pressure.

  • pitchbend( chan, val ): A pitch bend message (0xEn status). The val 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 of state (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 type m.

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.

Rounding and Clamping

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.)

Special Non-MIDI Functions

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 global channel 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 global velocity 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.

Conversions

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).

Multiple Matches

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.

Unbound Variables

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

Multiply-Bound Variables

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.

Constants and Ranges

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

{i} Placeholders

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.

Examples

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

Appendix

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