Add support for aftertouch (key and channel pressure) messages, update documentation.

master
Albert Graef 2018-08-19 12:19:48 +02:00
parent aa881bbd1c
commit 496591cd5d
6 changed files with 774 additions and 439 deletions

111
README.md
View File

@ -134,43 +134,45 @@ Note the `-10` suffix on the output messages in the above example, which indicat
E.g., the input note `C4` is mapped to `C3-10`, the note C in the third MIDI octave, which on channel 10 will produce the sound of a bass drum, at least on GM compatible synthesizers like Fluidsynth. The binding for the volume controller (`CC7`) at the end of the entry sends volume changes to the same drum channel (`CC7-10`), so that you can use the volume control on your keyboard to dial in the volume on the drum channel that you want. The program keeps track of the values of both input and output controllers on all MIDI channels internally, so with the translations above all that happens automagically. E.g., the input note `C4` is mapped to `C3-10`, the note C in the third MIDI octave, which on channel 10 will produce the sound of a bass drum, at least on GM compatible synthesizers like Fluidsynth. The binding for the volume controller (`CC7`) at the end of the entry sends volume changes to the same drum channel (`CC7-10`), so that you can use the volume control on your keyboard to dial in the volume on the drum channel that you want. The program keeps track of the values of both input and output controllers on all MIDI channels internally, so with the translations above all that happens automagically.
Besides MIDI notes and control change (`CC`) messages, the midizap program also recognizes program change (`PC`) and pitch bend (`PB`) messages, which should cover most common use cases; see below for details. Other messages (in particular, aftertouch and system messages) are not supported right now, but may be added in the future. Besides MIDI notes and control change (`CC`) messages, the midizap program also recognizes key and channel pressure (`KP`, `CP`), program change (`PC`) and pitch bend (`PB`) messages, which should cover most common use cases; see below for details.
# Translation Syntax # Translation Syntax
The midizap configuration file consists of sections defining translation classes. The general format looks somewhat like this: The midizap configuration file consists of sections defining translation classes. Each section generally looks like this:
~~~ ~~~
[name] regex [name] regex
CC<0..127> output # control change CC<0..127> <output> # control change
PC<0..127> output # program change PC<0..127> <output> # program change
PB output # pitch bend PB <output> # pitch bend
<A..G><#b><-11..11> output # note CP <output> # channel pressure
<A..G><#b><number> <output> # note
KP:<A..G><#b><number> <output> # key pressure (aftertouch)
~~~ ~~~
After the first line with the section header, each subsequent line indicates a translation rule belonging to that section. Note that we used `<X..Y>` here to indicate ranges, and `output` as a placeholder for the output sequence. We'll describe each of these elements in much more detail below. After the first line with the section header, each subsequent line indicates a translation rule belonging to that section. Note that we used `<X..Y>` here to indicate ranges, `<number>` to denote a MIDI octave number, and `<output>` as a placeholder for the output sequence. We'll describe each of these elements in much more detail below.
Also note that the `#` character at the beginning of a line and after whitespace is special; it indicates that the rest of the line is a comment, which is skipped by the parser. Empty lines and lines containing nothing but whitespace are also ignored. The `#` character at the beginning of a line and after whitespace is special; it indicates that the rest of the line is a comment, which is skipped by the parser. Empty lines and lines containing nothing but whitespace are also ignored.
Each `[name] regex` line introduces the list of MIDI message translations for the named translation class. The name is only used for debugging output, and needn't be unique. The following lines indicate what output should be produced for the given MIDI messages. Each `[name] regex` line introduces the list of MIDI message translations for the named translation class. The name is only used for debugging output, and needn't be unique. The following lines indicate what output should be produced for the given MIDI messages.
When focus is on a window whose class or title matches the regular expression `regex`, the following translation class is in effect. An empty regex for the last class will always match, allowing default translations. Any output sequences not bound in a matched section will be loaded from the default section if they are bound there. When focus is on a window whose class or title matches the regular expression `regex`, the following translation class is in effect. An empty regex for the last class will always match, allowing default translations. Any output sequences not bound in a matched section will be loaded from the default section if they are bound there.
The left-hand side (first token) of each translation denotes the MIDI message to be translated. MIDI messages are on channel 1 by default; a suffix of the form `-<1..16>` can be used to specify a different MIDI channel. E.g., `C3-10` denotes note `C3` on MIDI channel 10. The left-hand side (first token) of each translation denotes the MIDI message to be translated. MIDI messages are on channel 1 by default; a suffix of the form `-<1..16>` can be used to specify a MIDI channel. E.g., `C3-10` denotes note `C3` on MIDI channel 10.
Note messages are specified using the customary notation (note name `A..G`, optionally followed by an accidental, `#` or `b`, followed by the MIDI octave number. Note that all MIDI octaves start at the note C, so `B0` comes before `C1`. By default, `C5` denotes middle C. Enharmonic spellings are equivalent, so, e.g., `D#` and `Eb` denote exactly the same MIDI note. Note messages are specified using the customary notation (note name `A..G`, optionally followed by an accidental, `#` or `b`, followed by the MIDI octave number. The same notation is used for key pressure (`KP`) messages. Note that all MIDI octaves start at the note C, so `B0` comes before `C1`. By default, `C5` denotes middle C. Enharmonic spellings are equivalent, so, e.g., `D#` and `Eb` denote exactly the same MIDI note.
We will go into most of the other syntactic bits and pieces of MIDI message designations later, but it's good to have the following grammar in EBNF notation handy for reference: We will go into most of the other syntactic bits and pieces of MIDI message designations later, but it's good to have the following grammar in EBNF notation handy for reference:
~~~ ~~~
tok ::= ( note | msg ) [ number ] [ "[" number "]" ] token ::= ( note | msg ) [ number ] [ "[" number "]" ]
[ "-" number] [ incr ] [ "-" number] [ incr ]
note ::= ( "a" | ... | "g" ) [ "#" | "b" ] note ::= ( "A" | ... | "G" ) [ "#" | "b" ]
msg ::= "ch" | "pb" | "pc" | "cc" msg ::= "CH" | "PB" | "PC" | "CC" | "CP" | "KP:" note
incr ::= "-" | "+" | "=" | "<" | ">" | "~" incr ::= "-" | "+" | "=" | "<" | ">" | "~"
~~~ ~~~
Case is ignored, so `CC`, `cc` or even `Cc` are considered to be exactly the same token by the parser. Numbers are always integers in decimal. The meaning of the first number depends on the context (octave number for notes, controller or program number in the range 0..127 for other messages). This can optionally be followed by a number in brackets, denoting a nonzero step size. Also optionally, the suffix with the third number (after the dash) denotes the MIDI channel in the range 1..16; otherwise the default MIDI channel is used (which is always 1 on the left-hand side, but can be set on the right-hand side with `CH`). The optional incr flag at the end of a token indicates an "incremental" translation which responds to numeric (up/down) value changes rather than key presses, cf. *Key vs. Incremental* below. Case is ignored here, so `CC`, `cc` or even `Cc` are considered to be exactly the same token by the parser, although by convention we usually write them in uppercase. Numbers are always integers in decimal. The meaning of the first number depends on the context (octave number for notes and key pressure, controller or program number in the range 0..127 for other messages). This can optionally be followed by a number in brackets, denoting a nonzero step size. Also optionally, the suffix with the third number (after the dash) denotes the MIDI channel in the range 1..16; otherwise the default MIDI channel is used (which is always 1 on the left-hand side, but can be set on the right-hand side with `CH`). The optional incr (increment) flag at the end of a token indicates a "data" translation which responds to numeric (up/down) value changes rather than key presses, cf. *Key and Data Input* below.
## Octave Numbering ## Octave Numbering
@ -184,25 +186,40 @@ MIDI_OCTAVE -1 # ASA pitches (middle C is C4)
This is useful, in particular, if you use some external MIDI monitoring software to figure out which notes to put into your midizaprc file. To these ends, just check how the program prints middle C, and adjust the `MIDI_OCTAVE` offset in your midizaprc file accordingly. (Note that midizap's built-in MIDI monitoring facility always prints out MIDI notes using the `MIDI_OCTAVE` offset that is in effect. Thus in this case the printed note tokens will always be in exactly the form that is to be used in the midizaprc file, no matter what the `MIDI_OCTAVE` offset happens to be.) This is useful, in particular, if you use some external MIDI monitoring software to figure out which notes to put into your midizaprc file. To these ends, just check how the program prints middle C, and adjust the `MIDI_OCTAVE` offset in your midizaprc file accordingly. (Note that midizap's built-in MIDI monitoring facility always prints out MIDI notes using the `MIDI_OCTAVE` offset that is in effect. Thus in this case the printed note tokens will always be in exactly the form that is to be used in the midizaprc file, no matter what the `MIDI_OCTAVE` offset happens to be.)
## Key vs. Incremental ## Key and Data Input
Input MIDI messages can generally be processed in two different ways, "key mode" and "incremental mode". Input messages can generally be processed in two different ways, "key mode" and "data mode". In either mode, the extra data payload of the message is considered, which we refer to as the *parameter value* (or just *value*, for short) of a message. The only exception here is the program change message which has no associated parameter value at all, as the program number is considered part of the message token. For note, key and channel pressure messages the parameter value is the velocity value. For control changes, it is the controller value, for pitch bend messages the pitch bend value. Note that the latter is actually a 14 bit value which is considered as a signed quantity in the range -8192..8191, where 0 denotes the center value. In all other cases, the parameter value is an unsigned 7 bit quantity in the range 0..127.
*Key mode* is the default mode and is available for all message types. In this mode, MIDI messages are processed as if they were keys on a computer keyboard, i.e., they can be "on" ("pressed") or "off" ("released"). For notes, a nonzero velocity means "pressed", zero "released". Similarly, for control changes any nonzero value indicates "pressed". Same goes for pitch bends, but in this case 0 denotes the center value (considering pitch bend values as signed quantities in the range -8192..8191), and any nonzero (positive or negative) value means "pressed", while 0 (the center value) means "released". Finally, while program changes don't actually come in "on"/"off" pairs, they are treated in the same key-like fashion, assuming that they are "pressed" and then "released" immediately afterwards. *Key mode* is the default mode and is available for all message types. In this mode, MIDI messages are considered as keys which can be "pressed" ("on") or "released" ("off"). Any nonzero data value means "pressed", zero "released". Two special cases need to be considered here:
*Incremental mode* is only available with `CC` (control change) and `PB` (pitch bend) messages. In this mode, the actual *amount* of change in the value of the message (increment or decrement, a.k.a. "up" or "down") is being processed rather than the on/off state. Incremental mode is indicated with a special suffix on the message token which indicates the direction of the change which the rule should apply to: increment (`+`), decrement (`-`), or both (`=`). - For pitch bends, any positive *or* negative value means "pressed", while 0 (the center value) means "released".
Incremental mode usually keeps track of changes in the *absolute* value of a control. However, it's also possible to deal with *relative* controllers. (These are sometimes also called "incremental." To avoid the confusion with incremental-mode processing, we stick to the term "relative" here.) The most common case of these are the (endless) rotary encoders and jog wheels you find on many DAW controllers. These emit special *sign bit* values, where a value < 64 denotes an increment (usually representing clockwise rotation), and a value > 64 a decrement (counter-clockwise rotation). The actual amount of change is in the lower 6 bits of the value. - Since program changes have no parameter value associated with them, they don't really have an "on" or "off" status. But they are treated in the same key-like fashion anyway, assuming that they are "pressed" and then "released" immediately afterwards.
In the message syntax, these kinds of controls are indicated by using the suffix `<`, `>` and `~` in lieu of `-`, `+` and `=`, respectively. Note that these suffixes are only used with `CC` messages. *Data mode* is available for all messages where the notion of step-wise value *changes* makes sense. Thus note messages (which always come in on/off pairs) and program changes (which don't have an associated value at all) are excluded here, but data mode can be used with all the remaining messages (key and channel pressure, control changes, and pitch bends). In this mode, the actual *amount* of change in the value of the message (increment or decrement, a.k.a. "up" or "down") is processed rather than the on/off state. Data mode is indicated with a special suffix on the message token which indicates the direction of the change which the rule should apply to: increment (`+`), decrement (`-`), or both (`=`).
Each MIDI message may have at most one translation associated with it in each translation section, so that translations are determined uniquely. MIDI messages with different MIDI channels count as different messages, however, as do messages processed in different modes. Thus, `CC` and `PB` messages may have both key- and incremental-mode translations associated with them, in which case the former will be executed before the latter. Data mode usually tracks changes in the *absolute* value of a control. However, for `CC` messages there's also a special mode for so-called *incremental* controllers. The most common case of these are the (endless) rotary encoders and jog wheels you find on many DAW controllers. These emit a special *sign bit* value indicating a relative change, where a value < 64 denotes an increment (usually representing clockwise rotation), and a value > 64 a decrement (counter-clockwise rotation). The actual amount of change is in the lower 6 bits of the value. In the message syntax, these kinds of controls are indicated by using the suffix `<`, `>` and `~` in lieu of `-`, `+` and `=`, respectively. These suffixes are only permitted with `CC` messages.
## Key Translations Each MIDI message can have at most one translation in each mode associated with it per translation section, so that translations are determined uniquely in each translation class. Note that the MIDI channel is part of the message, so messages with different MIDI channels count as different messages here. Also, data mode messages can have key mode translations associated with them, and vice versa (except for note and program change messages which are only processed in key mode). In such a case the key translation will always be output before the data translation. Thus, e.g., a `CC` message can be translated to both an on/off value *and* a data increment/decrement, in this order.
## Keyboard and Mouse Translations
The right-hand side of a translation (i.e., everything following the first token) is a sequence of one or more tokens, separated by whitespace, indicating either MIDI messages or X11 keyboard and mouse events to be output. The right-hand side of a translation (i.e., everything following the first token) is a sequence of one or more tokens, separated by whitespace, indicating either MIDI messages or X11 keyboard and mouse events to be output.
Let's look at keyboard and mouse output first. It consists of X key codes (symbolic constants prefixed with `XK_` from the /usr/include/X11/keysymdef.h file) with optional up/down indicators, or strings of printable characters enclosed in double quotes. Also, there are some special keycodes to denote mouse button (`XK_Button_1`, `XK_Button_2`, `XK_Button_3`) and scroll wheel (`XK_Scroll_Up`, `XK_Scroll_Down`) events. Sequences may have separate press and release sequences, separated by the special word `RELEASE`. Let's look at keyboard and mouse output first. It consists of X key codes with optional up/down indicators, or strings of printable characters enclosed in double quotes. The syntax of these items, as well as the special `RELEASE` and `SHIFT` tokens which will be discussed later, are described by the following grammar:
~~~
token ::= "RELEASE" | "SHIFT" | keycode [ "/" flag ] | string
keycode ::= "XK_Button_1" | "XK_Button_2" | "XK_Button_3" |
"XK_Scroll_Up" | "XK_Scroll_Down" |
"XK_..." (X keysyms, see /usr/include/X11/keysymdef.h)
flag ::= "U" | "D" | "H"
string ::= '"' { character } '"'
~~~
Here, case *is* significant (except in character strings, see the remarks below), so the special tokens `RELEASE` and `SHIFT` must be in all caps, and the `XK_` symbols need to be written in mixed case exactly as they appear in the X11 keysymdef.h file.
Besides the key codes from the keysymdef.h file, there are also some special additional key codes to denote mouse button (`XK_Button_1`, `XK_Button_2`, `XK_Button_3`) and scroll wheel (`XK_Scroll_Up`, `XK_Scroll_Down`) events. Sequences may have separate press and release sequences, separated by the special word `RELEASE`.
Examples: Examples:
@ -216,25 +233,29 @@ G5 XK_Alt_L/D "v" XK_Alt_L/U "x" RELEASE "q"
Any keycode can be followed by an optional `/D`, `/U`, or `/H` flag, indicating that the key is just going down (without being released), going up, or going down and being held until the "off" event is received. Any keycode can be followed by an optional `/D`, `/U`, or `/H` flag, indicating that the key is just going down (without being released), going up, or going down and being held until the "off" event is received.
So, in general, modifier key codes will be followed by `/D`, and precede the keycodes they are intended to modify. If a sequence requires different sets of modifiers for different keycodes, `/U` can be used to release a modifier that was previously pressed with `/D`. So, in general, modifier key codes will be followed by `/D`, and precede the keycodes they are intended to modify. If a sequence requires different sets of modifiers for different keycodes, `/U` can be used to release a modifier that was previously pressed with `/D`.
Key-mode MIDI messages translate to separate press and release sequences. At the end of the press sequence, all down keys marked by `/D` will be released, and the last key not marked by `/D`, `/U`, or `/H` will remain pressed. The release sequence will begin by releasing the last held key. If keys are to be pressed as part of the release sequence, then any keys marked with `/D` will be repressed before continuing the sequence. Keycodes marked with `/H` remain held between the press and release sequences. One major pitfall for beginners is that character strings in double quotes are just a shorthand for the corresponding X key codes (ignoring case). Thus, e.g., `"abc"` actually denotes the keysym sequence `XK_a XK_b XK_c`, as does `"ABC"`. So in either case the lowercase string `abc` will be output, which probably isn't what you want if you write `"ABC"`. But to output uppercase letters, it is necessary to explicitly add one of the shift modifiers to the output sequence, e.g.: `XK_Shift_L/D "abc"`.
Incremental-mode `CC` (control change) and `PB` (pitch bend) messages are treated differently. Instead of providing separate press and release sequences, the output of such translations is executed whenever the controller increases or decreases, respectively. At the end of such sequences, all down keys will be released. For instance, the following translations output the letter `"a"` whenever the volume controller (`CC7`) is increased, and the letter `"b"` if it is decreased. Also, the number of times one of these keys is output corresponds to the actual change in the controller value. (Thus, if in the example `CC7` increases by 4, say, `"a"` will be output 4 times.) Keyboard and mouse translations are handled differently depending on the input mode (cf. *Key and Data Input* above). In key mode, they translate to separate press and release sequences. At the end of the press sequence, all down keys marked by `/D` will be released, and the last key not marked by `/D`, `/U`, or `/H` will remain pressed. The release sequence will begin by releasing the last held key. If keys are to be pressed as part of the release sequence, then any keys marked with `/D` will be repressed before continuing the sequence. Keycodes marked with `/H` remain held between the press and release sequences.
Data mode is handled differently. Instead of providing separate press and release sequences, the output of such translations is executed whenever the message value increases or decreases, respectively. At the end of such sequences, all down keys will be released. For instance, the following translations output the letter `"a"` whenever the volume controller (`CC7`) is increased, and the letter `"b"` if it is decreased. Also, the number of times one of these keys is output corresponds to the actual change in the value. (Thus, if in the example `CC7` increases by 4, say, `"a"` will be output 4 times.)
~~~ ~~~
CC7+ "a" CC7+ "a"
CC7- "b" CC7- "b"
~~~ ~~~
Incremental-mode relative `CC` messages are treated in an analogous fashion, but in this case the increment or decrement is determined directly by the input message. For instance, the jog wheel on the Mackie MCU is of this kind, so it can be processed as follows, using `<` and `>` in lieu of `-` and `+` as the suffix of the `CC` message: Incremental `CC` messages are treated in an analogous fashion, but in this case the increment or decrement is determined directly by the input message. One example for this type of controller is the jog wheel on the Mackie MCU, which can be processed as follows (using `<` and `>` in lieu of `-` and `+` as the suffix of the `CC` message):
~~~ ~~~
CC60< XK_Left CC60< XK_Left
CC60> XK_Right CC60> XK_Right
~~~ ~~~
Incremental `CC` and `PB` messages can also have a *step size* associated with them, which enables you to downscale controller and pitch bend changes. The default step size is 1 (no scaling). To change it, the desired step size is written in brackets immediately after the message token, but before the increment suffix. Thus, e.g., `CC1[2]+` denotes a sequence to be executed once whenever the controller increases by an amount of 2. As another (more useful) example, `PB[1170]` will give you 7 steps up and down, which is useful to emulate a shuttle wheel with the pitch bend wheel available on many MIDI keyboards. For instance, we might map this to the `"j"` and `"k"` keys used to control the playback speed in various video editors as follows: (The corresponding "bidirectional" translations, which are indicated with the `=` and `~` suffixes, are not often used with keyboard and mouse translations. Same goes for the special `SHIFT` token. Thus we'll discuss these in later sections, see *MIDI Translations* and *Shift State* below.)
Data messages can also have a *step size* associated with them, which enables you to downscale pressure, controller and pitch bend changes. The default step size is 1 (no scaling). To change it, the desired step size is written in brackets immediately after the message token, but before the increment suffix. Thus, e.g., `CC1[2]+` denotes a sequence to be executed once whenever the controller increases by an amount of 2. As another (more useful) example, `PB[1170]` will give you 7 steps up and down, which is useful to emulate a shuttle wheel with the pitch bend wheel available on many MIDI keyboards. For instance, we might map this to the `"j"` and `"k"` keys used to control the playback speed in various video editors as follows:
~~~ ~~~
PB[1170]- "j" PB[1170]- "j"
@ -243,25 +264,23 @@ PB[1170]+ "l"
## MIDI Translations ## MIDI Translations
Most of the notations for MIDI messages on the left-hand side of a translation rule also carry over to the output side, in order to translate MIDI input to MIDI output. As already discussed in Section *MIDI Output* above, you need to invoke the midizap program with the `-o` option to make this work. (Otherwise, MIDI messages in the output translations will just be silently ignored.) Most of the notations for MIDI messages on the left-hand side of a translation rule also carry over to the output side, in order to translate MIDI input to MIDI output. As already discussed in Section *MIDI Output* above, you need to invoke the midizap program with the `-o` option to make this work. (Otherwise, MIDI messages in the output translations will just be silently ignored.)
The output sequence can involve as many MIDI messages as you want, and these can be combined freely with keypress events in any order. There's no limitation on the type or number of MIDI messages that you can put into a translation rule. The output sequence can involve as many MIDI messages as you want, and these can be combined freely with keyboard and mouse events in any order. There's no limitation on the type or number of MIDI messages that you can put into a translation rule. However, the `+-<>` suffixes aren't permitted, because the *input* message determines whether it is a key- or data-mode kind of event, and whether it increments or decrements the value in the latter case.
Note that on output, the `+-<>` suffixes aren't supported, because the *input* message determines whether it is a key- or incremental-mode of event, and which direction it goes in the latter case. For key-mode inputs, the corresponding "on" or "off" event is generated for all MIDI messages in the output sequence, where the "on" value defaults to the maximum value (127 for controller values, 8191 for pitch bends). Thus, e.g., the following rule outputs a `CC80` message with controller value 127 each time middle C (`C5`) is pressed (and another `CC80` message with value 0 when the note is released again):
For key-mode inputs, such as a note or a non-incremental control change message, the corresponding "on" or "off" event is generated for all MIDI messages in the output sequence, where the "on" value defaults to the maximum value (127 for controller values, 8191 for pitch bends). Thus, e.g., the following rule outputs a `CC80` message with controller value 127 each time middle C (`C5`) is pressed (and another `CC80` message with value 0 when the note is released again):
~~~ ~~~
C5 CC80 C5 CC80
~~~ ~~~
It is also possible to specify a step size in this case, which explicitly sets the value for the "on" state. For instance, the following variation of the rule above produces a `CC80` message with value 64 (rather than the default "on" value of 127) whenever the MIDI note `C5` is pressed: The value for the "on" state can also be denoted explicitly with a step size here. For instance, the following variation of the rule above produces a `CC80` message with value 64 (rather than the default "on" value of 127) whenever the MIDI note `C5` is pressed:
~~~ ~~~
C5 CC80[64] C5 CC80[64]
~~~ ~~~
On the left-hand side of a translation, there are two additional suffixes `=` and `~` for incremental `CC` and `PB` messages which are most useful with pure MIDI translations, which is why we deferred their discussion until now. If the increment and decrement sequences for these messages are the same, the `=` suffix can be used to indicate that this sequence should be output for both increments and decrements. For instance, to map the modulation wheel (`CC1`) to the volume controller (`CC7`): On the left-hand side of a translation, there are two additional suffixes `=` and `~` for data translations which are most useful with pure MIDI translations, which is why we deferred their discussion until now. If the increment and decrement sequences for these messages are the same, the `=` suffix can be used to indicate that this sequence should be output for both increments and decrements. For instance, to map the modulation wheel (`CC1`) to the volume controller (`CC7`):
~~~ ~~~
CC1= CC7 CC1= CC7
@ -274,19 +293,19 @@ CC1+ CC7
CC1- CC7 CC1- CC7
~~~ ~~~
The same goes for `<`/`>` and `~` with sign-bit relative encoders: The same goes for `<`/`>` and `~` with sign-bit incremental encoders:
~~~ ~~~
CC60~ CC7 CC60~ CC7
~~~ ~~~
The `~` suffix can be used to denote relative controllers in output messages, too. E.g., to translate a standard (absolute) MIDI controller to a relative encoder value, you might use a rule like: The `~` suffix can be used to denote incremental controllers in output messages, too. E.g., to translate a standard (absolute) MIDI controller to an incremental encoder value, you might use a rule like:
~~~ ~~~
CC48= CC16~ CC48= CC16~
~~~ ~~~
Specifying step sizes on the right-hand side of incremental translations works as well, but scales the values *up* rather than down. This is most commonly used when scaling up controller values to pitch bends, which cover 128 times the range of a controller: Specifying step sizes on the right-hand side of incremental translations works as well, but there it scales the values *up* rather than down. This is most commonly used when scaling up controller values to pitch bends, which cover 128 times the range of a controller:
~~~ ~~~
CC1= PB[128] CC1= PB[128]
@ -298,7 +317,7 @@ Another possible use is to scale controller values *both* down and up with a com
CC1[3]= CC1[2] CC1[3]= CC1[2]
~~~ ~~~
There are two other special tokens on the output side, `CH` which selects the default MIDI channel for output, and `SHIFT` which is used for processing shift state. We'll discuss the latter in its own section below. The `CH` token, which is followed by a MIDI channel number in the range 1..16, doesn't actually generate any MIDI message, but merely sets the default MIDI channel for subsequent MIDI messages in the same output sequence. This is convenient if multiple messages are output to the same MIDI channel. For instance, the sequence `C5-2 E5-2 G5-2`, which outputs a C major chord on MIDI channel 2, can also be abbreviated as `CH2 C5 E5 G5`. There are two other special tokens on the output side, `CH` which selects the default MIDI channel for output, and `SHIFT` which is used for processing shift state. We'll discuss the latter in its own section below. The `CH` token, which is followed by a MIDI channel number in the range 1..16, doesn't actually generate any MIDI message, but merely sets the default MIDI channel for subsequent MIDI messages in the same output sequence. This is convenient if multiple messages are output to the same MIDI channel. For instance, the sequence `C5-2 E5-2 G5-2`, which outputs a C major chord on MIDI channel 2, can also be abbreviated as `CH2 C5 E5 G5`.
## Shift State ## Shift State
@ -359,11 +378,11 @@ You then wire up midizap's `midi_input` and `midi_output` ports to controller an
# Bugs # Bugs
There probably are some. Please submit bug reports and pull requests at the midizap [git repository][agraef/midizap]. There probably are some. Please submit bug reports and pull requests at the midizap [git repository][agraef/midizap].
The names of some of the debugging options are rather peculiar. That's mainly due to historical reasons and my laziness; midizap inherited them from Eric Messick's ShuttlePRO program on which midizap is based (see below). So they'll probably last until someone comes up with some really good names. The names of some of the debugging options are rather peculiar. That's mainly due to historical reasons and my laziness; midizap inherited them from Eric Messick's ShuttlePRO program on which midizap is based (see below). So they'll probably last until someone comes up with some really good names.
midizap tries to keep things simple, which implies that it has its limitations. In particular, aftertouch and system messages are not supported right now, there's only one internal shift state, and midizap lacks some more interesting ways of mapping MIDI data. There are other, more powerful ways of doing these things, but they are also more complicated and usually require programming. So, while midizap often does the job reasonably well for simple mapping tasks, if things start getting fiddly then you're usually better off using a more comprehensive tool like [Pd][]. midizap tries to keep things simple, which implies that it has its limitations. In particular, system messages are not supported right now, there's only one internal shift state, and midizap lacks some more interesting ways of mapping MIDI data. There are other, more powerful ways of doing these things, but they are also more complicated and usually require programming. So, while midizap often does the job reasonably well for simple mapping tasks, if things start getting fiddly then you're usually better off using a more comprehensive tool like [Pd][].
[Pd]: http://puredata.info/ [Pd]: http://puredata.info/

View File

@ -55,74 +55,79 @@
# CC<0..127>: control change message for the given controller # CC<0..127>: control change message for the given controller
# PC<0..127>: program change message # PC<0..127>: program change message
# PB: pitch bend message # PB: pitch bend message
# <A..G><#b><0..10> (MIDI notes): MIDI note (on or off) # CP: channel pressure
# KP:<note>: key pressure (aftertouch)
# <A..G><#b><num>: MIDI note (on or off)
# Note messages are specified using the cutomary notation (note name # Note messages are specified using the customary notation (note name
# A..G, optionally followed by an accidental, # or b, followed by a MIDI # A..G, optionally followed by an accidental, # or b, followed by a MIDI
# octave number. Enharmonic spellings are equivalent, so, e.g., D# and # octave number. The same notation is also used with aftertouch (KP)
# Eb denote exactly the same MIDI note. All MIDI octaves start at the # messages, which always apply to a specific note (in contrast, channel
# note C, so B0 comes before C1. By default, octave numbers are # pressure (CP) always applies to all notes on a single MIDI channel).
# zero-based, so C0 is MIDI note 0, C5 denotes middle C, A5 is the # Enharmonic spellings are equivalent, so, e.g., D# and Eb denote
# chamber pitch (usually at 440 Hz), etc. However, you can adjust this # exactly the same MIDI note. All MIDI octaves start at the note C, so
# to your liking by specifying the offset of the lowest MIDI octave. # B0 comes before C1. By default, octave numbers are zero-based, so C0
# Two of the most common alternatives are listed below (uncomment one of # is MIDI note 0, C5 denotes middle C, A5 is the chamber pitch, etc.
# the following lines to use these): # However, you can adjust this to your liking by specifying the offset
# of the lowest MIDI octave. Two of the most common alternatives are
# listed below (uncomment one of the following lines to use these):
#MIDI_OCTAVE -1 # ASA (Acoustical Society of America; middle C is C4) #MIDI_OCTAVE -1 # ASA (Acoustical Society of America; middle C is C4)
#MIDI_OCTAVE -2 # alternate MIDI (various manufacturers; middle C is C3) #MIDI_OCTAVE -2 # alternate MIDI (various manufacturers; middle C is C3)
# The program distinguishes between messages on different MIDI # The program distinguishes between messages on different MIDI channels.
# channels. By default, messages are assumed to be on MIDI channel 1, # By default, messages are assumed to be on MIDI channel 1, but the MIDI
# but the MIDI channel can be specified explicitly following a dash at # channel can be specified explicitly following a dash at the end of the
# the end of the message token. E.g., a message on MIDI channel 10 would # message token. E.g., a message on MIDI channel 10 would be denoted,
# be denoted, e.g., CC7-10 or C#3-10. # e.g., CC7-10 or C#3-10.
# Each of these messages can be either "on" or "off", and so they can # Each of these messages can be either "on" or "off", and so they can
# have different "press" and "release" keystrokes associated with them. # have different "press" and "release" keystrokes associated with them.
# E.g., a "note on" message with non-zero velocity emulates a button # E.g., a "note on" message with non-zero velocity emulates a button
# press, while the corresponding "note off" emulates a button release, # press, while the corresponding "note off" emulates a button release,
# just as if the MIDI keys were just ordinary keys on a computer # just as if the MIDI keys were just ordinary keys on a computer
# keyboard. The same holds true for control change messages (here any # keyboard. The same holds true for control change, channel and key
# non-zero controller value means "on", zero "off"), and pitch bends # pressure messages (here any non-zero value means "on", zero "off"),
# (here the center value of the pitch wheel means "off", any other value # and pitch bends (here the center value of the pitch wheel means "off",
# means "on"). The program change messages play a somewhat special role # any other value means "on"). The program change messages play a
# in that they don't actually have any "off" messages associated with # somewhat special role in that they don't actually have any "off"
# them, so to keep in line with the other kinds of MIDI messages we # messages associated with them, so to keep in line with the other kinds
# consider them as being "pressed" and then "released" immediately # of MIDI messages we consider them as being "pressed" and then
# afterwards. # "released" immediately afterwards.
# In addition, control change and pitch bend messages can also be # In addition, control change, channel and key pressure, and pitch bend
# interpreted as incremental changes, and have associated key bindings # messages can also have their value changes translated, in which case
# which are executed each time the controller or pitch bend value # they have associated key bindings which are executed each time the
# increases or decreases, respectively. Such bindings are indicated with # value increases or decreases, respectively. Such bindings are
# the suffixes "+" and "-". Thus, e.g., a key sequence bound to CC7+ # indicated with the suffixes "+" and "-". Thus, e.g., a key sequence
# will be executed each time the value of controller 7 increases, and # bound to CC7+ will be executed each time the value of controller 7
# CC7- will be executed each time it decreases. PB+ and PB- do they same # increases, and CC7- will be executed each time it decreases. The same
# for pitch bends. You can also use the "=" suffix to indicate that the # applies to channel and key pressure as well as pitch bend messages.
# same translation should be applied to both increases and decreases of # You can also use the "=" suffix to indicate that the same translation
# the controller or pitch bend value. Thus, e.g., CC7= indicates that # should be applied to both increases and decreases of the controller or
# the same translation applies for both CC7+ and CC7-. This is most # pitch bend value. Thus, e.g., CC7= indicates that the same
# commonly used with pure MIDI -> MIDI translations. # translation applies for both CC7+ and CC7-. This is most commonly
# used with pure MIDI -> MIDI translations.
# There is also another special mode for the incremental bindings, # There is also another special mode for these incremental bindings,
# incremental "bit-sign" mode. The suffixes "<", ">" and"~" can be used # incremental "bit-sign" mode. The suffixes "<", ">" and "~" can be
# in lieu of "+", "-" and "=" with the CC token to properly interpret # used in lieu of "+", "-" and "=" with the CC token to properly
# the control values of endless rotary encoders and jog wheels on # interpret the control values of endless rotary encoders and jog wheels
# Mackie-like devices. These encoders send values < 64 for increases, # on Mackie-like devices. These encoders send values < 64 for
# and > 64 for decreases, where the first 6 bits of the value denote the # increases, and > 64 for decreases, where the first 6 bits of the value
# actual amount of change relative to the current value. # denote the actual amount of change relative to the current value.
# As already mentioned, translations can also contain other MIDI # As already mentioned, translations can also contain other MIDI
# messages, in order to translate MIDI input to MIDI output to be passed # messages, in order to translate MIDI input to MIDI output to be passed
# on to to other MIDI devices and applications. In fact, X KeySyms and # on to to other MIDI devices and applications. In fact, X KeySyms and
# MIDI messages can be mixed freely in the output. To enable this, # MIDI messages can be mixed freely in the output. To enable this,
# invoke the program with the '-o' option. This creates a MIDI output # invoke the program with the '-o' option. This creates a MIDI output
# port, which can then be hooked up to other Jack MIDI applications. # port, which can then be hooked up to other Jack MIDI applications.
# (Otherwise, MIDI messages in the translations will just be ignored.) # (Otherwise, MIDI messages in the translations will just be ignored.)
# Debugging options: You want to run the program in a terminal window to # Debugging options: You want to run the program in a terminal window to
# see its output when using these. The following line, when uncommented, # see its output when using these. The following line, when
# prints the section recognized for the window in focus: # uncommented, prints the section recognized for the window in focus:
#DEBUG_REGEX #DEBUG_REGEX
@ -137,10 +142,10 @@
#DEBUG_KEYS #DEBUG_KEYS
# Finally, the following option prints all MIDI input (with the input port # Finally, the following option prints all MIDI input (with the input
# number in the first, and the actual data value in the last column). This is # port number in the first, and the actual data value in the last
# useful as a simple MIDI monitor, especially if you want to figure out which # column). This is useful as a simple MIDI monitor, especially if you
# tokens to use in your translations. # want to figure out which tokens to use in your translations.
#DEBUG_MIDI #DEBUG_MIDI
@ -152,7 +157,7 @@
# Sample bindings for video editing and mouse emulation. # Sample bindings for video editing and mouse emulation.
# These mostly assume a Mackie MCU-like DAW controller device. We use # These mostly assume a Mackie MCU-like DAW controller device. We use
# this as an example throughout, since devices of this kind are standard # this as an example throughout, since devices of this kind are standard
# gear in many studios, and they offer an abundance of useful controls. # gear in many studios, and they offer an abundance of useful controls.
# If you don't have one of these lying around, there are inexpensive MCU # If you don't have one of these lying around, there are inexpensive MCU
@ -160,7 +165,7 @@
# On most MCU-style devices there are some playback controls and cursor # On most MCU-style devices there are some playback controls and cursor
# keys which generate various note events, and a jog wheel which # keys which generate various note events, and a jog wheel which
# generates CC60 messages. We put all of these to good use here. Note # generates CC60 messages. We put all of these to good use here. Note
# that the CC60 control requires use of the aforementioned special # that the CC60 control requires use of the aforementioned special
# incremental mode for endless rotary encoders. # incremental mode for endless rotary encoders.
@ -229,8 +234,8 @@
[MIDI] [MIDI]
# The special "MIDI" default section is only active when MIDI output is # The special "MIDI" default section is only active when MIDI output is
# enabled (midizap -o). This allows you to translate midizap's MIDI # enabled (midizap -o). This allows you to translate midizap's MIDI
# input for use with other MIDI devices and applications. Here's a # input for use with other MIDI devices and applications. Here's a
# simple example for illustration purposes, which shows how to map both # simple example for illustration purposes, which shows how to map both
# the MCU master fader and the jog wheel to CC7, so that they can be # the MCU master fader and the jog wheel to CC7, so that they can be
# used as volume controls. # used as volume controls.
@ -242,13 +247,13 @@
PB[128]-9= CC7 PB[128]-9= CC7
CC60~ CC7 CC60~ CC7
# Drumkit example. The following translations should work on most MIDI # Drumkit example. The following translations should work on most MIDI
# keyboards. We assume that the keyboard is set to MIDI channel 1 (the # keyboards. We assume that the keyboard is set to MIDI channel 1 (the
# usual default). The first four white keys (C, D, E and F) in the # usual default). The first four white keys (C, D, E and F) in the
# fourth MIDI octave are mapped to the notes of a little drumkit on MIDI # fourth MIDI octave are mapped to the notes of a little drumkit on MIDI
# channel 10, and the volume controller (CC7) is bound to the volume # channel 10, and the volume controller (CC7) is bound to the volume
# controller on the same channel, so that you can change the output # controller on the same channel, so that you can change the output
# volume as you play the drumkit. Note that you need a GM-compatible # volume as you play the drumkit. Note that you need a GM-compatible
# software synthesizer such as Fluidsynth/Qsynth to make this work. # software synthesizer such as Fluidsynth/Qsynth to make this work.
C4 C3-10 C4 C3-10
@ -261,10 +266,10 @@
[MIDI2] [MIDI2]
# Auxiliary MIDI translations. This is only used when midizap is invoked # Auxiliary MIDI translations. This is only used when midizap is
# with the -o2 option, so that it creates a second pair of MIDI input # invoked with the -o2 option, so that it creates a second pair of MIDI
# and output ports. Input for this section only comes from the second # input and output ports. Input for this section only comes from the
# input port, and output goes to the second output port. This is # second input port, and output goes to the second output port. This is
# typically used for feedback to controllers featuring motor faders, # typically used for feedback to controllers featuring motor faders,
# LEDs and the like, in which case the translations are often the # LEDs and the like, in which case the translations are often the
# inverse of what's in the [MIDI] section. # inverse of what's in the [MIDI] section.
@ -293,10 +298,10 @@
CC60< XK_Scroll_Up CC60< XK_Scroll_Up
CC60> XK_Scroll_Down CC60> XK_Scroll_Down
# The following bindings should work on any MIDI keyboard. The C, D and # The following bindings should work on any MIDI keyboard. The C, D and
# E keys in the middle octave are bound to the three mouse buttons, and # E keys in the middle octave are bound to the three mouse buttons, and
# the modulation wheel (CC1) emulates the mouse wheel. The F, G, A and B # the modulation wheel (CC1) emulates the mouse wheel. The F, G, A and
# keys in the middle octave are mapped to the cursor keys (Left, Up, # B keys in the middle octave are mapped to the cursor keys (Left, Up,
# Down, Right). # Down, Right).
C5 XK_Button_1 C5 XK_Button_1

300
midizap.1
View File

@ -335,36 +335,38 @@ controllers on all MIDI channels internally, so with the translations
above all that happens automagically. above all that happens automagically.
.PP .PP
Besides MIDI notes and control change (\f[C]CC\f[]) messages, the Besides MIDI notes and control change (\f[C]CC\f[]) messages, the
midizap program also recognizes program change (\f[C]PC\f[]) and pitch midizap program also recognizes key and channel pressure (\f[C]KP\f[],
bend (\f[C]PB\f[]) messages, which should cover most common use cases; \f[C]CP\f[]), program change (\f[C]PC\f[]) and pitch bend (\f[C]PB\f[])
see below for details. messages, which should cover most common use cases; see below for
Other messages (in particular, aftertouch and system messages) are not details.
supported right now, but may be added in the future.
.SH Translation Syntax .SH Translation Syntax
.PP .PP
The midizap configuration file consists of sections defining translation The midizap configuration file consists of sections defining translation
classes. classes.
The general format looks somewhat like this: Each section generally looks like this:
.IP .IP
.nf .nf
\f[C] \f[C]
[name]\ regex [name]\ regex
CC<0..127>\ output\ \ \ \ \ \ \ \ \ \ \ #\ control\ change CC<0..127>\ <output>\ \ \ \ \ \ \ \ \ \ \ \ \ #\ control\ change
PC<0..127>\ output\ \ \ \ \ \ \ \ \ \ \ #\ program\ change PC<0..127>\ <output>\ \ \ \ \ \ \ \ \ \ \ \ \ #\ program\ change
PB\ output\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ pitch\ bend PB\ <output>\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ pitch\ bend
<A..G><#b><\-11..11>\ output\ \ #\ note CP\ <output>\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ #\ channel\ pressure
<A..G><#b><number>\ <output>\ \ \ \ \ #\ note
KP:<A..G><#b><number>\ <output>\ \ #\ key\ pressure\ (aftertouch)
\f[] \f[]
.fi .fi
.PP .PP
After the first line with the section header, each subsequent line After the first line with the section header, each subsequent line
indicates a translation rule belonging to that section. indicates a translation rule belonging to that section.
Note that we used \f[C]<X..Y>\f[] here to indicate ranges, and Note that we used \f[C]<X..Y>\f[] here to indicate ranges,
\f[C]output\f[] as a placeholder for the output sequence. \f[C]<number>\f[] to denote a MIDI octave number, and \f[C]<output>\f[]
as a placeholder for the output sequence.
We'll describe each of these elements in much more detail below. We'll describe each of these elements in much more detail below.
.PP .PP
Also note that the \f[C]#\f[] character at the beginning of a line and The \f[C]#\f[] character at the beginning of a line and after whitespace
after whitespace is special; it indicates that the rest of the line is a is special; it indicates that the rest of the line is a comment, which
comment, which is skipped by the parser. is skipped by the parser.
Empty lines and lines containing nothing but whitespace are also Empty lines and lines containing nothing but whitespace are also
ignored. ignored.
.PP .PP
@ -384,12 +386,13 @@ the default section if they are bound there.
The left\-hand side (first token) of each translation denotes the MIDI The left\-hand side (first token) of each translation denotes the MIDI
message to be translated. message to be translated.
MIDI messages are on channel 1 by default; a suffix of the form MIDI messages are on channel 1 by default; a suffix of the form
\f[C]\-<1..16>\f[] can be used to specify a different MIDI channel. \f[C]\-<1..16>\f[] can be used to specify a MIDI channel.
E.g., \f[C]C3\-10\f[] denotes note \f[C]C3\f[] on MIDI channel 10. E.g., \f[C]C3\-10\f[] denotes note \f[C]C3\f[] on MIDI channel 10.
.PP .PP
Note messages are specified using the customary notation (note name Note messages are specified using the customary notation (note name
\f[C]A..G\f[], optionally followed by an accidental, \f[C]#\f[] or \f[C]A..G\f[], optionally followed by an accidental, \f[C]#\f[] or
\f[C]b\f[], followed by the MIDI octave number. \f[C]b\f[], followed by the MIDI octave number.
The same notation is used for key pressure (\f[C]KP\f[]) messages.
Note that all MIDI octaves start at the note C, so \f[C]B0\f[] comes Note that all MIDI octaves start at the note C, so \f[C]B0\f[] comes
before \f[C]C1\f[]. before \f[C]C1\f[].
By default, \f[C]C5\f[] denotes middle C. By default, \f[C]C5\f[] denotes middle C.
@ -402,30 +405,31 @@ in EBNF notation handy for reference:
.IP .IP
.nf .nf
\f[C] \f[C]
tok\ \ ::=\ (\ note\ |\ msg\ )\ [\ number\ ]\ [\ "["\ number\ "]"\ ] token\ ::=\ (\ note\ |\ msg\ )\ [\ number\ ]\ [\ "["\ number\ "]"\ ]
\ \ \ \ \ \ \ \ \ [\ "\-"\ number]\ [\ incr\ ] \ \ \ \ \ \ \ \ \ \ [\ "\-"\ number]\ [\ incr\ ]
note\ ::=\ (\ "a"\ |\ ...\ |\ "g"\ )\ [\ "#"\ |\ "b"\ ] note\ \ ::=\ (\ "A"\ |\ ...\ |\ "G"\ )\ [\ "#"\ |\ "b"\ ]
msg\ \ ::=\ "ch"\ |\ "pb"\ |\ "pc"\ |\ "cc" msg\ \ \ ::=\ "CH"\ |\ "PB"\ |\ "PC"\ |\ "CC"\ |\ "CP"\ |\ "KP:"\ note
incr\ ::=\ "\-"\ |\ "+"\ |\ "="\ |\ "<"\ |\ ">"\ |\ "~" incr\ \ ::=\ "\-"\ |\ "+"\ |\ "="\ |\ "<"\ |\ ">"\ |\ "~"
\f[] \f[]
.fi .fi
.PP .PP
Case is ignored, so \f[C]CC\f[], \f[C]cc\f[] or even \f[C]Cc\f[] are Case is ignored here, so \f[C]CC\f[], \f[C]cc\f[] or even \f[C]Cc\f[]
considered to be exactly the same token by the parser. are considered to be exactly the same token by the parser, although by
convention we usually write them in uppercase.
Numbers are always integers in decimal. Numbers are always integers in decimal.
The meaning of the first number depends on the context (octave number The meaning of the first number depends on the context (octave number
for notes, controller or program number in the range 0..127 for other for notes and key pressure, controller or program number in the range
messages). 0..127 for other messages).
This can optionally be followed by a number in brackets, denoting a This can optionally be followed by a number in brackets, denoting a
nonzero step size. nonzero step size.
Also optionally, the suffix with the third number (after the dash) Also optionally, the suffix with the third number (after the dash)
denotes the MIDI channel in the range 1..16; otherwise the default MIDI denotes the MIDI channel in the range 1..16; otherwise the default MIDI
channel is used (which is always 1 on the left\-hand side, but can be channel is used (which is always 1 on the left\-hand side, but can be
set on the right\-hand side with \f[C]CH\f[]). set on the right\-hand side with \f[C]CH\f[]).
The optional incr flag at the end of a token indicates an The optional incr (increment) flag at the end of a token indicates a
\[lq]incremental\[rq] translation which responds to numeric (up/down) \[lq]data\[rq] translation which responds to numeric (up/down) value
value changes rather than key presses, cf. changes rather than key presses, cf.
\f[I]Key vs.\ Incremental\f[] below. \f[I]Key and Data Input\f[] below.
.SS Octave Numbering .SS Octave Numbering
.PP .PP
A note on the octave numbers in MIDI note designations is in order here. A note on the octave numbers in MIDI note designations is in order here.
@ -464,66 +468,85 @@ out MIDI notes using the \f[C]MIDI_OCTAVE\f[] offset that is in effect.
Thus in this case the printed note tokens will always be in exactly the Thus in this case the printed note tokens will always be in exactly the
form that is to be used in the midizaprc file, no matter what the form that is to be used in the midizaprc file, no matter what the
\f[C]MIDI_OCTAVE\f[] offset happens to be.) \f[C]MIDI_OCTAVE\f[] offset happens to be.)
.SS Key vs.\ Incremental .SS Key and Data Input
.PP .PP
Input MIDI messages can generally be processed in two different ways, Input messages can generally be processed in two different ways,
\[lq]key mode\[rq] and \[lq]incremental mode\[rq]. \[lq]key mode\[rq] and \[lq]data mode\[rq].
In either mode, the extra data payload of the message is considered,
which we refer to as the \f[I]parameter value\f[] (or just
\f[I]value\f[], for short) of a message.
The only exception here is the program change message which has no
associated parameter value at all, as the program number is considered
part of the message token.
For note, key and channel pressure messages the parameter value is the
velocity value.
For control changes, it is the controller value, for pitch bend messages
the pitch bend value.
Note that the latter is actually a 14 bit value which is considered as a
signed quantity in the range \-8192..8191, where 0 denotes the center
value.
In all other cases, the parameter value is an unsigned 7 bit quantity in
the range 0..127.
.PP .PP
\f[I]Key mode\f[] is the default mode and is available for all message \f[I]Key mode\f[] is the default mode and is available for all message
types. types.
In this mode, MIDI messages are processed as if they were keys on a In this mode, MIDI messages are considered as keys which can be
computer keyboard, i.e., they can be \[lq]on\[rq] (\[lq]pressed\[rq]) or \[lq]pressed\[rq] (\[lq]on\[rq]) or \[lq]released\[rq] (\[lq]off\[rq]).
\[lq]off\[rq] (\[lq]released\[rq]). Any nonzero data value means \[lq]pressed\[rq], zero \[lq]released\[rq].
For notes, a nonzero velocity means \[lq]pressed\[rq], zero Two special cases need to be considered here:
\[lq]released\[rq]. .IP \[bu] 2
Similarly, for control changes any nonzero value indicates For pitch bends, any positive \f[I]or\f[] negative value means
\[lq]pressed\[rq].
Same goes for pitch bends, but in this case 0 denotes the center value
(considering pitch bend values as signed quantities in the range
\-8192..8191), and any nonzero (positive or negative) value means
\[lq]pressed\[rq], while 0 (the center value) means \[lq]released\[rq]. \[lq]pressed\[rq], while 0 (the center value) means \[lq]released\[rq].
Finally, while program changes don't actually come in .IP \[bu] 2
\[lq]on\[rq]/\[lq]off\[rq] pairs, they are treated in the same key\-like Since program changes have no parameter value associated with them, they
fashion, assuming that they are \[lq]pressed\[rq] and then don't really have an \[lq]on\[rq] or \[lq]off\[rq] status.
\[lq]released\[rq] immediately afterwards. But they are treated in the same key\-like fashion anyway, assuming that
they are \[lq]pressed\[rq] and then \[lq]released\[rq] immediately
afterwards.
.PP .PP
\f[I]Incremental mode\f[] is only available with \f[C]CC\f[] (control \f[I]Data mode\f[] is available for all messages where the notion of
change) and \f[C]PB\f[] (pitch bend) messages. step\-wise value \f[I]changes\f[] makes sense.
Thus note messages (which always come in on/off pairs) and program
changes (which don't have an associated value at all) are excluded here,
but data mode can be used with all the remaining messages (key and
channel pressure, control changes, and pitch bends).
In this mode, the actual \f[I]amount\f[] of change in the value of the In this mode, the actual \f[I]amount\f[] of change in the value of the
message (increment or decrement, a.k.a. message (increment or decrement, a.k.a.
\[lq]up\[rq] or \[lq]down\[rq]) is being processed rather than the \[lq]up\[rq] or \[lq]down\[rq]) is processed rather than the on/off
on/off state. state.
Incremental mode is indicated with a special suffix on the message token Data mode is indicated with a special suffix on the message token which
which indicates the direction of the change which the rule should apply indicates the direction of the change which the rule should apply to:
to: increment (\f[C]+\f[]), decrement (\f[C]\-\f[]), or both increment (\f[C]+\f[]), decrement (\f[C]\-\f[]), or both (\f[C]=\f[]).
(\f[C]=\f[]).
.PP .PP
Incremental mode usually keeps track of changes in the \f[I]absolute\f[] Data mode usually tracks changes in the \f[I]absolute\f[] value of a
value of a control. control.
However, it's also possible to deal with \f[I]relative\f[] controllers. However, for \f[C]CC\f[] messages there's also a special mode for
(These are sometimes also called \[lq]incremental.\[rq] To avoid the so\-called \f[I]incremental\f[] controllers.
confusion with incremental\-mode processing, we stick to the term The most common case of these are the (endless) rotary encoders and jog
\[lq]relative\[rq] here.) The most common case of these are the wheels you find on many DAW controllers.
(endless) rotary encoders and jog wheels you find on many DAW These emit a special \f[I]sign bit\f[] value indicating a relative
controllers. change, where a value < 64 denotes an increment (usually representing
These emit special \f[I]sign bit\f[] values, where a value < 64 denotes clockwise rotation), and a value > 64 a decrement (counter\-clockwise
an increment (usually representing clockwise rotation), and a value > 64 rotation).
a decrement (counter\-clockwise rotation).
The actual amount of change is in the lower 6 bits of the value. The actual amount of change is in the lower 6 bits of the value.
.PP
In the message syntax, these kinds of controls are indicated by using In the message syntax, these kinds of controls are indicated by using
the suffix \f[C]<\f[], \f[C]>\f[] and \f[C]~\f[] in lieu of \f[C]\-\f[], the suffix \f[C]<\f[], \f[C]>\f[] and \f[C]~\f[] in lieu of \f[C]\-\f[],
\f[C]+\f[] and \f[C]=\f[], respectively. \f[C]+\f[] and \f[C]=\f[], respectively.
Note that these suffixes are only used with \f[C]CC\f[] messages. These suffixes are only permitted with \f[C]CC\f[] messages.
.PP .PP
Each MIDI message may have at most one translation associated with it in Each MIDI message can have at most one translation in each mode
each translation section, so that translations are determined uniquely. associated with it per translation section, so that translations are
MIDI messages with different MIDI channels count as different messages, determined uniquely in each translation class.
however, as do messages processed in different modes. Note that the MIDI channel is part of the message, so messages with
Thus, \f[C]CC\f[] and \f[C]PB\f[] messages may have both key\- and different MIDI channels count as different messages here.
incremental\-mode translations associated with them, in which case the Also, data mode messages can have key mode translations associated with
former will be executed before the latter. them, and vice versa (except for note and program change messages which
.SS Key Translations are only processed in key mode).
In such a case the key translation will always be output before the data
translation.
Thus, e.g., a \f[C]CC\f[] message can be translated to both an on/off
value \f[I]and\f[] a data increment/decrement, in this order.
.SS Keyboard and Mouse Translations
.PP .PP
The right\-hand side of a translation (i.e., everything following the The right\-hand side of a translation (i.e., everything following the
first token) is a sequence of one or more tokens, separated by first token) is a sequence of one or more tokens, separated by
@ -531,11 +554,31 @@ whitespace, indicating either MIDI messages or X11 keyboard and mouse
events to be output. events to be output.
.PP .PP
Let's look at keyboard and mouse output first. Let's look at keyboard and mouse output first.
It consists of X key codes (symbolic constants prefixed with It consists of X key codes with optional up/down indicators, or strings
\f[C]XK_\f[] from the /usr/include/X11/keysymdef.h file) with optional of printable characters enclosed in double quotes.
up/down indicators, or strings of printable characters enclosed in The syntax of these items, as well as the special \f[C]RELEASE\f[] and
double quotes. \f[C]SHIFT\f[] tokens which will be discussed later, are described by
Also, there are some special keycodes to denote mouse button the following grammar:
.IP
.nf
\f[C]
token\ \ \ ::=\ "RELEASE"\ |\ "SHIFT"\ |\ keycode\ [\ "/"\ flag\ ]\ |\ string
keycode\ ::=\ "XK_Button_1"\ |\ "XK_Button_2"\ |\ "XK_Button_3"\ |
\ \ \ \ \ \ \ \ \ \ \ \ "XK_Scroll_Up"\ |\ "XK_Scroll_Down"\ |
\ \ \ \ \ \ \ \ \ \ \ \ "XK_..."\ (X\ keysyms,\ see\ /usr/include/X11/keysymdef.h)
flag\ \ \ \ ::=\ "U"\ |\ "D"\ |\ "H"
string\ \ ::=\ \[aq]"\[aq]\ {\ character\ }\ \[aq]"\[aq]
\f[]
.fi
.PP
Here, case \f[I]is\f[] significant (except in character strings, see the
remarks below), so the special tokens \f[C]RELEASE\f[] and
\f[C]SHIFT\f[] must be in all caps, and the \f[C]XK_\f[] symbols need to
be written in mixed case exactly as they appear in the X11 keysymdef.h
file.
.PP
Besides the key codes from the keysymdef.h file, there are also some
special additional key codes to denote mouse button
(\f[C]XK_Button_1\f[], \f[C]XK_Button_2\f[], \f[C]XK_Button_3\f[]) and (\f[C]XK_Button_1\f[], \f[C]XK_Button_2\f[], \f[C]XK_Button_3\f[]) and
scroll wheel (\f[C]XK_Scroll_Up\f[], \f[C]XK_Scroll_Down\f[]) events. scroll wheel (\f[C]XK_Scroll_Up\f[], \f[C]XK_Scroll_Down\f[]) events.
Sequences may have separate press and release sequences, separated by Sequences may have separate press and release sequences, separated by
@ -564,8 +607,21 @@ If a sequence requires different sets of modifiers for different
keycodes, \f[C]/U\f[] can be used to release a modifier that was keycodes, \f[C]/U\f[] can be used to release a modifier that was
previously pressed with \f[C]/D\f[]. previously pressed with \f[C]/D\f[].
.PP .PP
Key\-mode MIDI messages translate to separate press and release One major pitfall for beginners is that character strings in double
sequences. quotes are just a shorthand for the corresponding X key codes (ignoring
case).
Thus, e.g., \f[C]"abc"\f[] actually denotes the keysym sequence
\f[C]XK_a\ XK_b\ XK_c\f[], as does \f[C]"ABC"\f[].
So in either case the lowercase string \f[C]abc\f[] will be output,
which probably isn't what you want if you write \f[C]"ABC"\f[].
But to output uppercase letters, it is necessary to explicitly add one
of the shift modifiers to the output sequence, e.g.:
\f[C]XK_Shift_L/D\ "abc"\f[].
.PP
Keyboard and mouse translations are handled differently depending on the
input mode (cf.
\f[I]Key and Data Input\f[] above).
In key mode, they translate to separate press and release sequences.
At the end of the press sequence, all down keys marked by \f[C]/D\f[] At the end of the press sequence, all down keys marked by \f[C]/D\f[]
will be released, and the last key not marked by \f[C]/D\f[], will be released, and the last key not marked by \f[C]/D\f[],
\f[C]/U\f[], or \f[C]/H\f[] will remain pressed. \f[C]/U\f[], or \f[C]/H\f[] will remain pressed.
@ -576,17 +632,16 @@ sequence.
Keycodes marked with \f[C]/H\f[] remain held between the press and Keycodes marked with \f[C]/H\f[] remain held between the press and
release sequences. release sequences.
.PP .PP
Incremental\-mode \f[C]CC\f[] (control change) and \f[C]PB\f[] (pitch Data mode is handled differently.
bend) messages are treated differently.
Instead of providing separate press and release sequences, the output of Instead of providing separate press and release sequences, the output of
such translations is executed whenever the controller increases or such translations is executed whenever the message value increases or
decreases, respectively. decreases, respectively.
At the end of such sequences, all down keys will be released. At the end of such sequences, all down keys will be released.
For instance, the following translations output the letter \f[C]"a"\f[] For instance, the following translations output the letter \f[C]"a"\f[]
whenever the volume controller (\f[C]CC7\f[]) is increased, and the whenever the volume controller (\f[C]CC7\f[]) is increased, and the
letter \f[C]"b"\f[] if it is decreased. letter \f[C]"b"\f[] if it is decreased.
Also, the number of times one of these keys is output corresponds to the Also, the number of times one of these keys is output corresponds to the
actual change in the controller value. actual change in the value.
(Thus, if in the example \f[C]CC7\f[] increases by 4, say, \f[C]"a"\f[] (Thus, if in the example \f[C]CC7\f[] increases by 4, say, \f[C]"a"\f[]
will be output 4 times.) will be output 4 times.)
.IP .IP
@ -597,12 +652,13 @@ CC7\-\ "b"
\f[] \f[]
.fi .fi
.PP .PP
Incremental\-mode relative \f[C]CC\f[] messages are treated in an Incremental \f[C]CC\f[] messages are treated in an analogous fashion,
analogous fashion, but in this case the increment or decrement is but in this case the increment or decrement is determined directly by
determined directly by the input message. the input message.
For instance, the jog wheel on the Mackie MCU is of this kind, so it can One example for this type of controller is the jog wheel on the Mackie
be processed as follows, using \f[C]<\f[] and \f[C]>\f[] in lieu of MCU, which can be processed as follows (using \f[C]<\f[] and \f[C]>\f[]
\f[C]\-\f[] and \f[C]+\f[] as the suffix of the \f[C]CC\f[] message: in lieu of \f[C]\-\f[] and \f[C]+\f[] as the suffix of the \f[C]CC\f[]
message):
.IP .IP
.nf .nf
\f[C] \f[C]
@ -611,9 +667,16 @@ CC60>\ XK_Right
\f[] \f[]
.fi .fi
.PP .PP
Incremental \f[C]CC\f[] and \f[C]PB\f[] messages can also have a (The corresponding \[lq]bidirectional\[rq] translations, which are
\f[I]step size\f[] associated with them, which enables you to downscale indicated with the \f[C]=\f[] and \f[C]~\f[] suffixes, are not often
controller and pitch bend changes. used with keyboard and mouse translations.
Same goes for the special \f[C]SHIFT\f[] token.
Thus we'll discuss these in later sections, see \f[I]MIDI
Translations\f[] and \f[I]Shift State\f[] below.)
.PP
Data messages can also have a \f[I]step size\f[] associated with them,
which enables you to downscale pressure, controller and pitch bend
changes.
The default step size is 1 (no scaling). The default step size is 1 (no scaling).
To change it, the desired step size is written in brackets immediately To change it, the desired step size is written in brackets immediately
after the message token, but before the increment suffix. after the message token, but before the increment suffix.
@ -644,19 +707,18 @@ work.
silently ignored.) silently ignored.)
.PP .PP
The output sequence can involve as many MIDI messages as you want, and The output sequence can involve as many MIDI messages as you want, and
these can be combined freely with keypress events in any order. these can be combined freely with keyboard and mouse events in any
order.
There's no limitation on the type or number of MIDI messages that you There's no limitation on the type or number of MIDI messages that you
can put into a translation rule. can put into a translation rule.
However, the \f[C]+\-<>\f[] suffixes aren't permitted, because the
\f[I]input\f[] message determines whether it is a key\- or data\-mode
kind of event, and whether it increments or decrements the value in the
latter case.
.PP .PP
Note that on output, the \f[C]+\-<>\f[] suffixes aren't supported, For key\-mode inputs, the corresponding \[lq]on\[rq] or \[lq]off\[rq]
because the \f[I]input\f[] message determines whether it is a key\- or event is generated for all MIDI messages in the output sequence, where
incremental\-mode of event, and which direction it goes in the latter the \[lq]on\[rq] value defaults to the maximum value (127 for controller
case.
.PP
For key\-mode inputs, such as a note or a non\-incremental control
change message, the corresponding \[lq]on\[rq] or \[lq]off\[rq] event is
generated for all MIDI messages in the output sequence, where the
\[lq]on\[rq] value defaults to the maximum value (127 for controller
values, 8191 for pitch bends). values, 8191 for pitch bends).
Thus, e.g., the following rule outputs a \f[C]CC80\f[] message with Thus, e.g., the following rule outputs a \f[C]CC80\f[] message with
controller value 127 each time middle C (\f[C]C5\f[]) is pressed (and controller value 127 each time middle C (\f[C]C5\f[]) is pressed (and
@ -669,8 +731,8 @@ C5\ CC80
\f[] \f[]
.fi .fi
.PP .PP
It is also possible to specify a step size in this case, which The value for the \[lq]on\[rq] state can also be denoted explicitly with
explicitly sets the value for the \[lq]on\[rq] state. a step size here.
For instance, the following variation of the rule above produces a For instance, the following variation of the rule above produces a
\f[C]CC80\f[] message with value 64 (rather than the default \f[C]CC80\f[] message with value 64 (rather than the default
\[lq]on\[rq] value of 127) whenever the MIDI note \f[C]C5\f[] is \[lq]on\[rq] value of 127) whenever the MIDI note \f[C]C5\f[] is
@ -683,9 +745,9 @@ C5\ CC80[64]
.fi .fi
.PP .PP
On the left\-hand side of a translation, there are two additional On the left\-hand side of a translation, there are two additional
suffixes \f[C]=\f[] and \f[C]~\f[] for incremental \f[C]CC\f[] and suffixes \f[C]=\f[] and \f[C]~\f[] for data translations which are most
\f[C]PB\f[] messages which are most useful with pure MIDI translations, useful with pure MIDI translations, which is why we deferred their
which is why we deferred their discussion until now. discussion until now.
If the increment and decrement sequences for these messages are the If the increment and decrement sequences for these messages are the
same, the \f[C]=\f[] suffix can be used to indicate that this sequence same, the \f[C]=\f[] suffix can be used to indicate that this sequence
should be output for both increments and decrements. should be output for both increments and decrements.
@ -708,7 +770,7 @@ CC1\-\ CC7
.fi .fi
.PP .PP
The same goes for \f[C]<\f[]/\f[C]>\f[] and \f[C]~\f[] with sign\-bit The same goes for \f[C]<\f[]/\f[C]>\f[] and \f[C]~\f[] with sign\-bit
relative encoders: incremental encoders:
.IP .IP
.nf .nf
\f[C] \f[C]
@ -716,10 +778,10 @@ CC60~\ CC7
\f[] \f[]
.fi .fi
.PP .PP
The \f[C]~\f[] suffix can be used to denote relative controllers in The \f[C]~\f[] suffix can be used to denote incremental controllers in
output messages, too. output messages, too.
E.g., to translate a standard (absolute) MIDI controller to a relative E.g., to translate a standard (absolute) MIDI controller to an
encoder value, you might use a rule like: incremental encoder value, you might use a rule like:
.IP .IP
.nf .nf
\f[C] \f[C]
@ -728,8 +790,8 @@ CC48=\ CC16~
.fi .fi
.PP .PP
Specifying step sizes on the right\-hand side of incremental Specifying step sizes on the right\-hand side of incremental
translations works as well, but scales the values \f[I]up\f[] rather translations works as well, but there it scales the values \f[I]up\f[]
than down. rather than down.
This is most commonly used when scaling up controller values to pitch This is most commonly used when scaling up controller values to pitch
bends, which cover 128 times the range of a controller: bends, which cover 128 times the range of a controller:
.IP .IP
@ -946,9 +1008,9 @@ names.
.PP .PP
midizap tries to keep things simple, which implies that it has its midizap tries to keep things simple, which implies that it has its
limitations. limitations.
In particular, aftertouch and system messages are not supported right In particular, system messages are not supported right now, there's only
now, there's only one internal shift state, and midizap lacks some more one internal shift state, and midizap lacks some more interesting ways
interesting ways of mapping MIDI data. of mapping MIDI data.
There are other, more powerful ways of doing these things, but they are There are other, more powerful ways of doing these things, but they are
also more complicated and usually require programming. also more complicated and usually require programming.
So, while midizap often does the job reasonably well for simple mapping So, while midizap often does the job reasonably well for simple mapping

280
midizap.c
View File

@ -61,6 +61,8 @@ send_key(KeySym key, int press)
// cached controller and pitch bend values // cached controller and pitch bend values
static int16_t ccvalue[16][128]; static int16_t ccvalue[16][128];
static int16_t kpvalue[16][128];
static int16_t cpvalue[16];
static int16_t pbvalue[16] = static int16_t pbvalue[16] =
{8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, {8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192,
8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192}; 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192};
@ -114,6 +116,52 @@ send_midi(uint8_t portno, int status, int data, int step, int incr, int index, i
msg[2] = 0; msg[2] = 0;
} }
break; break;
case 0xa0:
if (dir) {
// increment (dir==1) or decrement (dir==-1) the current value,
// clamping it to the 0..127 data byte range
if (!step) step = 1;
dir *= step;
if (dir > 0) {
if (kpvalue[chan][data] >= 127) return;
kpvalue[chan][data] += dir;
if (kpvalue[chan][data] > 127) kpvalue[chan][data] = 127;
} else {
if (kpvalue[chan][data] == 0) return;
kpvalue[chan][data] += dir;
if (kpvalue[chan][data] < 0) kpvalue[chan][data] = 0;
}
msg[2] = kpvalue[chan][data];
} else if (!index) {
msg[2] = step?step:127;
if (msg[2] > 127) msg[2] = 127;
} else {
msg[2] = 0;
}
break;
case 0xd0:
if (dir) {
// increment (dir==1) or decrement (dir==-1) the current value,
// clamping it to the 0..127 data byte range
if (!step) step = 1;
dir *= step;
if (dir > 0) {
if (cpvalue[chan] >= 127) return;
cpvalue[chan] += dir;
if (cpvalue[chan] > 127) cpvalue[chan] = 127;
} else {
if (cpvalue[chan] == 0) return;
cpvalue[chan] += dir;
if (cpvalue[chan] < 0) cpvalue[chan] = 0;
}
msg[1] = cpvalue[chan];
} else if (!index) {
msg[1] = step?step:127;
if (msg[1] > 127) msg[1] = 127;
} else {
msg[1] = 0;
}
break;
case 0xe0: { case 0xe0: {
// pitch bends are treated similarly to a controller, but with a 14 bit // pitch bends are treated similarly to a controller, but with a 14 bit
// range (0..16383, with 8192 being the center value) // range (0..16383, with 8192 being the center value)
@ -226,6 +274,34 @@ static stroke *find_ccs(translation *tr, int shift,
tr->n_ccs[shift]); tr->n_ccs[shift]);
} }
static stroke *find_kp(translation *tr, int shift,
int chan, int data, int index)
{
return find_stroke_data(tr->kp[shift], chan, data, index, 0, 0,
tr->n_kp[shift]);
}
static stroke *find_kps(translation *tr, int shift,
int chan, int data, int index, int *step)
{
return find_stroke_data(tr->kps[shift], chan, data, index, step, 0,
tr->n_kps[shift]);
}
static stroke *find_cp(translation *tr, int shift,
int chan, int index)
{
return find_stroke_data(tr->cp[shift], chan, 0, index, 0, 0,
tr->n_cp[shift]);
}
static stroke *find_cps(translation *tr, int shift,
int chan, int index, int *step)
{
return find_stroke_data(tr->cps[shift], chan, 0, index, step, 0,
tr->n_cps[shift]);
}
static stroke *find_pb(translation *tr, int shift, static stroke *find_pb(translation *tr, int shift,
int chan, int index) int chan, int index)
{ {
@ -255,6 +331,16 @@ fetch_stroke(translation *tr, uint8_t portno, int status, int chan, int data,
return find_ccs(tr, shift, chan, data, dir>0, step, incr); return find_ccs(tr, shift, chan, data, dir>0, step, incr);
else else
return find_cc(tr, shift, chan, data, index); return find_cc(tr, shift, chan, data, index);
case 0xa0:
if (dir)
return find_kps(tr, shift, chan, data, dir>0, step);
else
return find_kp(tr, shift, chan, data, index);
case 0xd0:
if (dir)
return find_cps(tr, shift, chan, dir>0, step);
else
return find_cp(tr, shift, chan, index);
case 0xe0: case 0xe0:
if (dir) if (dir)
return find_pbs(tr, shift, chan, dir>0, step); return find_pbs(tr, shift, chan, dir>0, step);
@ -309,10 +395,21 @@ static char *debug_key(translation *tr, char *name,
sprintf(name, "%s%s%d-%d", prefix, note_names[data % 12], sprintf(name, "%s%s%d-%d", prefix, note_names[data % 12],
data / 12 + midi_octave, chan+1); data / 12 + midi_octave, chan+1);
break; break;
case 0xa0: case 0xa0: {
sprintf(name, "%sKP:%s%d-%d", prefix, note_names[data % 12], int step = 1;
data / 12 + midi_octave, chan+1); if (tr) (void)find_kps(tr, shift, chan, data, dir>0, &step);
if (!dir)
suffix = "";
else
suffix = (dir<0)?"-":"+";
if (dir && step != 1)
sprintf(name, "%sKP:%s%d[%d]-%d%s", prefix, note_names[data % 12],
data / 12 + midi_octave, step, chan+1, suffix);
else
sprintf(name, "%sKP:%s%d-%d%s", prefix, note_names[data % 12],
data / 12 + midi_octave, chan+1, suffix);
break; break;
}
case 0xb0: { case 0xb0: {
int step = 1, is_incr = 0; int step = 1, is_incr = 0;
if (tr) (void)find_ccs(tr, shift, chan, data, dir>0, &step, &is_incr); if (tr) (void)find_ccs(tr, shift, chan, data, dir>0, &step, &is_incr);
@ -331,9 +428,19 @@ static char *debug_key(translation *tr, char *name,
case 0xc0: case 0xc0:
sprintf(name, "%sPC%d-%d", prefix, data, chan+1); sprintf(name, "%sPC%d-%d", prefix, data, chan+1);
break; break;
case 0xd0: case 0xd0: {
sprintf(name, "%sCP-%d", prefix, chan+1); int step = 1;
if (tr) (void)find_cps(tr, shift, chan, dir>0, &step);
if (!dir)
suffix = "";
else
suffix = (dir<0)?"-":"+";
if (dir && step != 1)
sprintf(name, "%sCP[%d]-%d%s", prefix, step, chan+1, suffix);
else
sprintf(name, "%sCP-%d%s", prefix, chan+1, suffix);
break; break;
}
case 0xe0: { case 0xe0: {
int step = 1; int step = 1;
if (tr) (void)find_pbs(tr, shift, chan, dir>0, &step); if (tr) (void)find_pbs(tr, shift, chan, dir>0, &step);
@ -569,6 +676,8 @@ get_focused_window_translation()
} }
static int8_t inccvalue[2][16][128]; static int8_t inccvalue[2][16][128];
static int8_t inkpvalue[2][16][128];
static int8_t incpvalue[2][16];
static int16_t inpbvalue[2][16] = static int16_t inpbvalue[2][16] =
{{8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, {{8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192,
8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192}, 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192},
@ -585,6 +694,8 @@ static int keydown_tracker = 0;
static uint8_t notedown[2][16][128]; static uint8_t notedown[2][16][128];
static uint8_t inccdown[2][16][128]; static uint8_t inccdown[2][16][128];
static uint8_t inpbdown[2][16]; static uint8_t inpbdown[2][16];
static uint8_t inkpdown[2][16][128];
static uint8_t incpdown[2][16];
int int
check_incr(translation *tr, uint8_t portno, int chan, int data) check_incr(translation *tr, uint8_t portno, int chan, int data)
@ -607,6 +718,26 @@ check_incr(translation *tr, uint8_t portno, int chan, int data)
return 0; return 0;
} }
int
check_ccs(translation *tr, uint8_t portno, int chan, int data)
{
if (tr && tr->portno == portno &&
(find_ccs(tr, shift, chan, data, 0, 0, 0) ||
find_ccs(tr, shift, chan, data, 1, 0, 0)))
return 1;
tr = default_midi_translation[portno];
if (tr && tr->portno == portno &&
(find_ccs(tr, shift, chan, data, 0, 0, 0) ||
find_ccs(tr, shift, chan, data, 1, 0, 0)))
return 1;
tr = default_translation;
if (tr && tr->portno == portno &&
(find_ccs(tr, shift, chan, data, 0, 0, 0) ||
find_ccs(tr, shift, chan, data, 1, 0, 0)))
return 1;
return 0;
}
int int
get_cc_step(translation *tr, uint8_t portno, int chan, int data, int dir) get_cc_step(translation *tr, uint8_t portno, int chan, int data, int dir)
{ {
@ -625,6 +756,82 @@ get_cc_step(translation *tr, uint8_t portno, int chan, int data, int dir)
return 1; return 1;
} }
int
check_kps(translation *tr, uint8_t portno, int chan, int data)
{
if (tr && tr->portno == portno &&
(find_kps(tr, shift, chan, data, 0, 0) ||
find_kps(tr, shift, chan, data, 1, 0)))
return 1;
tr = default_midi_translation[portno];
if (tr && tr->portno == portno &&
(find_kps(tr, shift, chan, data, 0, 0) ||
find_kps(tr, shift, chan, data, 1, 0)))
return 1;
tr = default_translation;
if (tr && tr->portno == portno &&
(find_kps(tr, shift, chan, data, 0, 0) ||
find_kps(tr, shift, chan, data, 1, 0)))
return 1;
return 0;
}
int
get_kp_step(translation *tr, uint8_t portno, int chan, int data, int dir)
{
int step;
if (tr && tr->portno == portno &&
find_kps(tr, shift, chan, data, dir>0, &step))
return step;
tr = default_midi_translation[portno];
if (tr && tr->portno == portno &&
find_kps(tr, shift, chan, data, dir>0, &step))
return step;
tr = default_translation;
if (tr && tr->portno == portno &&
find_kps(tr, shift, chan, data, dir>0, &step))
return step;
return 1;
}
int
check_cps(translation *tr, uint8_t portno, int chan)
{
if (tr && tr->portno == portno &&
(find_cps(tr, shift, chan, 0, 0) ||
find_cps(tr, shift, chan, 1, 0)))
return 1;
tr = default_midi_translation[portno];
if (tr && tr->portno == portno &&
(find_cps(tr, shift, chan, 0, 0) ||
find_cps(tr, shift, chan, 1, 0)))
return 1;
tr = default_translation;
if (tr && tr->portno == portno &&
(find_cps(tr, shift, chan, 0, 0) ||
find_cps(tr, shift, chan, 1, 0)))
return 1;
return 0;
}
int
get_cp_step(translation *tr, uint8_t portno, int chan, int dir)
{
int step;
if (tr && tr->portno == portno &&
find_cps(tr, shift, chan, dir>0, &step))
return step;
tr = default_midi_translation[portno];
if (tr && tr->portno == portno &&
find_cps(tr, shift, chan, dir>0, &step))
return step;
tr = default_translation;
if (tr && tr->portno == portno &&
find_cps(tr, shift, chan, dir>0, &step))
return step;
return 1;
}
int int
check_pbs(translation *tr, uint8_t portno, int chan) check_pbs(translation *tr, uint8_t portno, int chan)
{ {
@ -744,7 +951,8 @@ handle_event(uint8_t *msg, uint8_t portno)
} }
} }
} }
} else if (inccvalue[portno][chan][msg[1]] != msg[2]) { } else if (check_ccs(tr, portno, chan, msg[1]) &&
inccvalue[portno][chan][msg[1]] != msg[2]) {
int dir = inccvalue[portno][chan][msg[1]] > msg[2] ? -1 : 1; int dir = inccvalue[portno][chan][msg[1]] > msg[2] ? -1 : 1;
int step = get_cc_step(tr, portno, chan, msg[1], dir); int step = get_cc_step(tr, portno, chan, msg[1], dir);
if (step) { if (step) {
@ -759,6 +967,66 @@ handle_event(uint8_t *msg, uint8_t portno)
} }
end_debug(); end_debug();
break; break;
case 0xa0:
start_debug();
if (msg[2]) {
if (!keydown_tracker || !inkpdown[portno][chan][msg[1]]) {
send_strokes(tr, portno, status, chan, msg[1], 0, 0);
inkpdown[portno][chan][msg[1]] = 1;
}
} else {
if (!keydown_tracker || inkpdown[portno][chan][msg[1]]) {
send_strokes(tr, portno, status, chan, msg[1], 1, 0);
inkpdown[portno][chan][msg[1]] = 0;
}
}
debug_count = 0;
if (check_kps(tr, portno, chan, msg[1]) &&
inkpvalue[portno][chan][msg[1]] != msg[2]) {
int dir = inkpvalue[portno][chan][msg[1]] > msg[2] ? -1 : 1;
int step = get_kp_step(tr, portno, chan, msg[1], dir);
if (step) {
while (inkpvalue[portno][chan][msg[1]] != msg[2]) {
int d = abs(inkpvalue[portno][chan][msg[1]] - msg[2]);
if (d > step) d = step;
if (d < step) break;
send_strokes(tr, portno, status, chan, msg[1], 0, dir);
inkpvalue[portno][chan][msg[1]] += dir*d;
}
}
}
end_debug();
break;
case 0xd0:
start_debug();
if (msg[1]) {
if (!keydown_tracker || !incpdown[portno][chan]) {
send_strokes(tr, portno, status, chan, 0, 0, 0);
incpdown[portno][chan] = 1;
}
} else {
if (!keydown_tracker || incpdown[portno][chan]) {
send_strokes(tr, portno, status, chan, 0, 1, 0);
incpdown[portno][chan] = 0;
}
}
debug_count = 0;
if (check_cps(tr, portno, chan) &&
incpvalue[portno][chan] != msg[1]) {
int dir = incpvalue[portno][chan] > msg[1] ? -1 : 1;
int step = get_cp_step(tr, portno, chan, dir);
if (step) {
while (incpvalue[portno][chan] != msg[1]) {
int d = abs(incpvalue[portno][chan] - msg[1]);
if (d > step) d = step;
if (d < step) break;
send_strokes(tr, portno, status, chan, 0, 0, dir);
incpvalue[portno][chan] += dir*d;
}
}
}
end_debug();
break;
case 0xe0: { case 0xe0: {
int bend = ((msg[2] << 7) | msg[1]) - 8192; int bend = ((msg[2] << 7) | msg[1]) - 8192;
start_debug(); start_debug();

View File

@ -67,7 +67,7 @@ typedef struct _stroke_data {
uint8_t chan, data; uint8_t chan, data;
// stroke data, indexed by press/release or up/down index // stroke data, indexed by press/release or up/down index
stroke *s[2]; stroke *s[2];
// step size (CC and PB only) // step size (CP, KP, CC and PB only)
int step[2]; int step[2];
// incr flag (CC only) // incr flag (CC only)
uint8_t is_incr; uint8_t is_incr;
@ -86,9 +86,15 @@ typedef struct _translation {
stroke_data *ccs[2]; stroke_data *ccs[2];
stroke_data *pb[2]; stroke_data *pb[2];
stroke_data *pbs[2]; stroke_data *pbs[2];
stroke_data *kp[2];
stroke_data *kps[2];
stroke_data *cp[2];
stroke_data *cps[2];
// actual and allocated sizes (can be at most 16*128) // actual and allocated sizes (can be at most 16*128)
uint16_t n_note[2], n_pc[2], n_cc[2], n_ccs[2], n_pb[2], n_pbs[2]; uint16_t n_note[2], n_pc[2], n_cc[2], n_ccs[2], n_pb[2], n_pbs[2],
uint16_t a_note[2], a_pc[2], a_cc[2], a_ccs[2], a_pb[2], a_pbs[2]; n_kp[2], n_kps[2], n_cp[2], n_cps[2];
uint16_t a_note[2], a_pc[2], a_cc[2], a_ccs[2], a_pb[2], a_pbs[2],
a_kp[2], a_kps[2], a_cp[2], a_cps[2];
} translation; } translation;
extern void reload_callback(void); extern void reload_callback(void);

View File

@ -12,10 +12,12 @@
section takes the following form: section takes the following form:
[name] regex [name] regex
CC<0..127> output # control change CC<0..127> output # control change
PC<0..127> output # program change PC<0..127> output # program change
PB output # pitch bend PB output # pitch bend
<A-G>[#b]<0..11> output # note CP output # channel pressure
KP:<A-G>[#b]<-11..11> output # key pressure (aftertouch)
<A-G>[#b]<-11..11> output # note
When focus is on a window whose class or title matches regex, the When focus is on a window whose class or title matches regex, the
following translation class is in effect. An empty regex for the last following translation class is in effect. An empty regex for the last
@ -28,11 +30,6 @@
for debugging output, and needn't be unique. The following lines for debugging output, and needn't be unique. The following lines
indicate what output should be produced for the given MIDI messages. indicate what output should be produced for the given MIDI messages.
Note that not all MIDI message types are supported right now (no
aftertouch, no system messages), but that subset should be enough to
handle most common use cases. (In any case, adding more message types
should be a piece of cake.)
MIDI messages are on channel 1 by default; a suffix of the form MIDI messages are on channel 1 by default; a suffix of the form
-<1..16> can be used to specify a different MIDI channel. E.g., C3-10 -<1..16> can be used to specify a different MIDI channel. E.g., C3-10
denotes note C3 on MIDI channel 10. denotes note C3 on MIDI channel 10.
@ -40,175 +37,13 @@
Note messages are specified using the cutomary notation (note name Note messages are specified using the cutomary notation (note name
A..G, optionally followed by an accidental, # or b, followed by a A..G, optionally followed by an accidental, # or b, followed by a
(zero-based) MIDI octave number. Note that all MIDI octaves start at (zero-based) MIDI octave number. Note that all MIDI octaves start at
the note C, so B0 comes before C1. C5 denotes middle C, A5 is the the note C, so B0 comes before C1. By default, C5 denotes middle C, A5
chamber pitch (usually at 440 Hz). Enharmonic spellings are is the chamber pitch (usually at 440 Hz). Enharmonic spellings are
equivalent, so, e.g., D# and Eb denote exactly the same MIDI note. equivalent, so, e.g., D# and Eb denote exactly the same MIDI note.
More details on the syntax of MIDI messages can be found in the More details on the syntax of MIDI messages can be found in the
comments preceding the parse_midi() routine below. comments preceding the parse_midi() routine below.
By default, all messages are interpreted in the same way as keys on a
computer keyboard, i.e., they can be "on" ("pressed") or "off"
("released"). For notes, a nonzero velocity means "pressed", zero
"released". Similarly, for control changes any nonzero value
indicates "pressed". Same goes for pitch bends, but in this case 0
denotes the center value (considering pitch bend values as signed
quantities in the range -8192..8191). Again, any nonzero (positive or
negative) value means "pressed", and 0 (the center value) "released".
Finally, while program changes don't actually come in "on"/"off"
pairs, they are treated in the same key-like fashion, assuming that
they are "pressed" and then "released" immediately afterwards.
output is a sequence of one or more key codes with optional up/down
indicators, or strings of printable characters enclosed in double
quotes, separated by whitespace. Sequences may have separate press
and release sequences, separated by the word RELEASE.
Examples:
C5 "qwer"
D5 XK_Right
E5 XK_Alt_L/D XK_Right
F5 "V" XK_Left XK_Page_Up "v"
G5 XK_Alt_L/D "v" XK_Alt_L/U "x" RELEASE "q"
Any keycode can be followed by an optional /D, /U, or /H, indicating
that the key is just going down (without being released), going up,
or going down and being held until the "off" event is received.
So, in general, modifier key codes will be followed by /D, and
precede the keycodes they are intended to modify. If a sequence
requires different sets of modifiers for different keycodes, /U can
be used to release a modifier that was previously pressed with /D.
By default, MIDI messages translate to separate press and release
sequences. At the end of the press sequence, all down keys marked by
/D will be released, and the last key not marked by /D, /U, or /H will
remain pressed. The release sequence will begin by releasing the last
held key. If keys are to be pressed as part of the release sequence,
then any keys marked with /D will be repressed before continuing the
sequence. Keycodes marked with /H remain held between the press and
release sequences.
By marking CC (control change) and PB (pitch bend) messages with a
trailing "+" or "-", they can also be used to report incremental
changes. These work a bit differently from the key press semantics.
Instead of providing separate press and release sequences, the output
of such translations is executed whenever the controller increases or
decreases, respectively. At the end of such sequences, all down keys
will be released. For instance, the following translations output the
letter "a" whenever the volume controller (CC7) is increased, and the
letter "b" if it is decreased. Also, the number of times one of these
keys is output corresponds to the actual change in the controller
value. (Thus, if in the example CC7 increases by 32, say, 32 "a"s
will be output.)
CC7+ "a"
CC7- "b"
CC also has an alternative "incremental" mode which handles relative
control changes encoded in "sign bit" format. Here, a value < 64
denotes an increase, and a value > 64 a decrease (thus the 7th bit is
the sign of the value change). The lower 6 bits then denote the
amount of change (e.g., 2 increments the control by 2, whereas 66
decrements by 2). This format is often used with endless rotary
encoders, such as the jog wheel on some DAW controllers like the
Mackie MCU. It is denoted by using "<" and ">" in lieu of "-" and "+"
as the suffix of the CC message. Example:
CC60< XK_Left
CC60> XK_Right
If the "up" and "down" sequences for controller and pitch bend changes
are the same, the notation "=" can be used to indicate that the same
sequence should be output in either case. This most commonly arises in
pure MIDI translations. For instance, to map the modulation wheel
(CC1) to the volume controller (CC7):
CC1= CC7
Which is exactly the same as the two translations:
CC1+ CC7
CC1- CC7
The same goes for "<"/">" and "~" in incremental mode. E.g., CC1~ CC7
is exactly the same as:
CC1< CC7
CC1> CC7
Furthermore, incremental CC and PB messages can have a step size
associated with them, which enable you to scale controller and pitch
bend changes. The default step size is 1 (no scaling). To change it,
the desired step size is written in brackets immediately after the
message token, but before the increment suffix. Thus, e.g., CC1[2]=
denotes a sequence to be executed once whenever the controller changes
by an amount of 2. For instance, the following translation scales
down the values of a controller, effectively dividing them by 2, so
that the output range becomes 0..63 (127/2, rounded down):
CC1[2]= CC1
As another example, PB[1170] will give you 7 steps up and down, which
is useful to emulate a shuttle wheel such as those on the Contour
Design devices. Example:
PB[1170]- "j"
PB[1170]+ "l"
Most of the notations for MIDI messages also carry over to the output
side, in order to translate MIDI input to MIDI output. To make this
work, you need to invoke the midizap program with the -o option, which
equips the program with an additional MIDI output port, to which the
translated MIDI messages are sent. (Otherwise, MIDI messages in the
output translations will be ignored.)
Bindings can involve as many MIDI messages as you want, and these can
be combined freely with keypress events in any order. There's no
limitation on the type or number of MIDI messages that you can put
into a binding.
Note that on output, the +-=<> suffixes aren't supported, because the
*input* message determines whether it is a key press or value change
type of event, and which direction it goes in the latter case. Only
the "~" suffix can be used to indicate an incremental CC message in
sign bit encoding. Specifying step sizes with incremental CC and PB
messages works as well, but scales the values *up* rather than down on
the output side. In fact, on the output side step sizes also work
with keypress-style messages (except PC), where they set the value for
the "on" state.
Also, on the output side there's a special token of the form
CH<1..16>, which doesn't actually generate any MIDI message. Rather,
it sets the default MIDI channel for subsequent MIDI messages in the
same output sequence, which is convenient if multiple messages are
output to the same MIDI channel.
Finally, there's a special SHIFT token which toggles an internal shift
state. If your controller has a dedicated shift key (as many
Mackie-like DAW controllers do), this makes it possible to have
different bindings depending on the internal shift state. E.g., to
bind the shift key (`A#5`) on a Mackie controller:
?A#5 SHIFT
Note that the "?" prefix tells the parser that this translation is
active in both unshifted and shifted state, so it is used to turn
shift state both on and off, giving a Caps Lock-style of toggle key.
If you'd rather have an ordinary shift key which turns on shift state
when pressed and immediately turns it off when released again, you can
do that as follows:
?A#5 SHIFT RELEASE SHIFT
Having set up our shift key, we can now add translations like the
following (using the "^" prefix to indicate translations only valid in
shifted state):
CC48= PB[129]-1 # translate controller to pitch bend when unshifted
^CC48= CC16~ # translate controller to encoder when shifted
*/ */
#include "midizap.h" #include "midizap.h"
@ -459,6 +294,34 @@ static stroke **find_ccs(translation *tr, int shift,
&tr->n_ccs[shift], &tr->a_ccs[shift]); &tr->n_ccs[shift], &tr->a_ccs[shift]);
} }
static stroke **find_kp(translation *tr, int shift,
int chan, int data, int index)
{
return find_stroke_data(&tr->kp[shift], chan, data, index, 0, 0,
&tr->n_kp[shift], &tr->a_kp[shift]);
}
static stroke **find_kps(translation *tr, int shift,
int chan, int data, int index, int step)
{
return find_stroke_data(&tr->kps[shift], chan, data, index, step, 0,
&tr->n_kps[shift], &tr->a_kps[shift]);
}
static stroke **find_cp(translation *tr, int shift,
int chan, int index)
{
return find_stroke_data(&tr->cp[shift], chan, 0, index, 0, 0,
&tr->n_cp[shift], &tr->a_cp[shift]);
}
static stroke **find_cps(translation *tr, int shift,
int chan, int index, int step)
{
return find_stroke_data(&tr->cps[shift], chan, 0, index, step, 0,
&tr->n_cps[shift], &tr->a_cps[shift]);
}
static stroke **find_pb(translation *tr, int shift, static stroke **find_pb(translation *tr, int shift,
int chan, int index) int chan, int index)
{ {
@ -661,6 +524,14 @@ print_stroke(stroke *s)
printf("%s%d-%d ", note_names[s->data % 12], printf("%s%d-%d ", note_names[s->data % 12],
s->data / 12 + midi_octave, channel); s->data / 12 + midi_octave, channel);
break; break;
case 0xa0:
if (s->step)
printf("KP:%s%d[%d]-%d ", note_names[s->data % 12],
s->data / 12 + midi_octave, s->step, channel);
else
printf("KP:%s%d-%d ", note_names[s->data % 12],
s->data / 12 + midi_octave, channel);
break;
case 0xb0: case 0xb0:
if (s->step) if (s->step)
printf("CC%d[%d]-%d%s ", s->data, s->step, channel, s->incr?"~":""); printf("CC%d[%d]-%d%s ", s->data, s->step, channel, s->incr?"~":"");
@ -670,6 +541,12 @@ print_stroke(stroke *s)
case 0xc0: case 0xc0:
printf("PC%d-%d ", s->data, channel); printf("PC%d-%d ", s->data, channel);
break; break;
case 0xd0:
if (s->step)
printf("CP[%d]-%d ", s->step, channel);
else
printf("CP-%d ", channel);
break;
case 0xe0: case 0xe0:
if (s->step) if (s->step)
printf("PB[%d]-%d ", s->step, channel); printf("PB[%d]-%d ", s->step, channel);
@ -850,32 +727,33 @@ re_press_temp_modifiers(void)
tok ::= ( note | msg ) [ number ] [ "[" number "]" ] [ "-" number] [ incr ] tok ::= ( note | msg ) [ number ] [ "[" number "]" ] [ "-" number] [ incr ]
note ::= ( "a" | ... | "g" ) [ "#" | "b" ] note ::= ( "a" | ... | "g" ) [ "#" | "b" ]
msg ::= "ch" | "pb" | "pc" | "cc" msg ::= "ch" | "pb" | "pc" | "cc" | "cp" | "kp:" note
incr ::= "-" | "+" | "=" | "<" | ">" | "~" incr ::= "-" | "+" | "=" | "<" | ">" | "~"
Case is insignificant. Numbers are always in decimal. The meaning of Case is insignificant. Numbers are always in decimal. The meaning of
the first number depends on the context (octave number for notes, the the first number depends on the context (octave number for notes and
actual data byte for other messages). This can optionally be followed key pressure, the actual data byte for other messages). This can
by a number in brackets, denoting a step size. Also optionally, the optionally be followed by a number in brackets, denoting a step
suffix with the third number (after the dash) denotes the MIDI size. Also optionally, the suffix with the third number (after the
channel; otherwise the default MIDI channel is used. dash) denotes the MIDI channel; otherwise the default MIDI channel is
used.
Note that not all combinations are possible -- "pb" has no data byte; Note that not all combinations are possible -- "pb" and "cp" have no
on the lhs, a step size in brackets is only permitted with "cc" and data byte; on the lhs, a step size in brackets is only permitted with
"pb"; and "ch" must *not* occur on the lhs at all, and is followed by "cc", "pb", "cp" and "kp"; and "ch" must *not* occur on the lhs at
just a channel number. (In fact, "ch" is no real MIDI message at all, and is followed by just a channel number. (In fact, "ch" is no
all; it just sets the default MIDI channel for subsequent messages in real MIDI message at all; it just sets the default MIDI channel for
the output sequence.) subsequent messages in the output sequence.)
The incr flag indicates an "incremental" controller or pitch bend The incr flag indicates an "incremental" controller or pitch bend
value which responds to up ("+") and down ("-") changes; it is only value which responds to up ("+") and down ("-") changes; it is only
permitted in conjunction with "cc" and "pb", and (with one exception, permitted in conjunction with "cc", "pb", "cp" and "kp", and (with
see below) only on the lhs of a translation. In addition, "<" and ">" one exception, see below) only on the lhs of a translation. In
can be used in lieu of "-" and "-" to indicate a relative controller addition, "<" and ">" can be used in lieu of "-" and "-" to indicate
in "sign bit" representation, where controller values > 64 denote a relative controller in "sign bit" representation, where controller
down, and values < 64 up changes. This notation is only permitted values > 64 denote down, and values < 64 up changes. This notation is
with "cc". It is used for endless rotary encoders, jog wheels and the only permitted with "cc". It is used for endless rotary encoders, jog
like, as can be found, e.g., on Mackie-like units. wheels and the like, as can be found, e.g., on Mackie-like units.
Finally, the flags "=" and "~" are used in lieu of "+"/"-" or Finally, the flags "=" and "~" are used in lieu of "+"/"-" or
"<"/">", respectively, to denote a "bidirectional" translation which "<"/">", respectively, to denote a "bidirectional" translation which
@ -908,17 +786,40 @@ parse_midi(char *tok, char *s, int lhs,
char *p = tok, *t; char *p = tok, *t;
int n, m = -1, k = midi_channel, l; int n, m = -1, k = midi_channel, l;
s[0] = 0; s[0] = 0;
while (*p && !isdigit(*p) && !strchr("+-=<>~[", *p)) p++; while (*p && !isdigit(*p) && !strchr("+-=<>~[:", *p)) p++;
if (p == tok || p-tok > 10) return 0; // no valid token if (p == tok || p-tok > 10) return 0; // no valid token
// the token by itself // the token by itself
strncpy(s, tok, p-tok); s[p-tok] = 0; strncpy(s, tok, p-tok); s[p-tok] = 0;
// normalize to lowercase // normalize to lowercase
for (t = s; *t; t++) *t = tolower(*t); for (t = s; *t; t++) *t = tolower(*t);
// octave number or data byte (not permitted with 'pb', otherwise required) // octave number or data byte
if (strcmp(s, "pb")) { if (strcmp(s, "pb") && strcmp(s, "cp")) {
if ((*p == '-' || isdigit(*p)) && if ((*p == '-' || isdigit(*p))) {
sscanf(p, "%d%n", &m, &n) == 1) { if (sscanf(p, "%d%n", &m, &n) == 1)
p += n; p += n;
else
return 0;
} else if (!strcmp(s, "kp")) {
// key pressure, must be followed by colon and note name
if (*p == ':' && p[1]) {
char c = *++p, b = *++p;
if (*p == '#' || tolower(*p) == 'b')
p++;
else
b = 0;
int k = note_number(c, b, 0);
if (k < 0) return 0;
if ((*p == '-' || isdigit(*p)) &&
sscanf(p, "%d%n", &m, &n) == 1) {
// octave number
m = k + 12 * (m - midi_octave);
p += n;
} else {
return 0;
}
} else {
return 0;
}
} else { } else {
return 0; return 0;
} }
@ -951,9 +852,10 @@ parse_midi(char *tok, char *s, int lhs,
return 0; return 0;
} }
} }
// incremental flag ("pb" and "cc" only) // incremental flag ("cc", "pb", "cp" and "kp" only)
if (*p && strchr("+-=<>~", *p)) { if (*p && strchr("+-=<>~", *p)) {
if (strcmp(s, "pb") && strcmp(s, "cc")) return 0; if (strcmp(s, "pb") && strcmp(s, "cc") &&
strcmp(s, "cp") && strcmp(s, "kp")) return 0;
// these are only permitted with "cc" // these are only permitted with "cc"
if (strchr("<>~", *p) && strcmp(s, "cc")) return 0; if (strchr("<>~", *p) && strcmp(s, "cc")) return 0;
if (lhs) { if (lhs) {
@ -992,6 +894,13 @@ parse_midi(char *tok, char *s, int lhs,
if (lhs && *step && !*incr) return 0; if (lhs && *step && !*incr) return 0;
if (lhs && !*step) *step = 1; // default if (lhs && !*step) *step = 1; // default
return 1; return 1;
} else if (strcmp(s, "cp") == 0) {
// channel pressure, no data byte
*status = 0xd0 | k; *data = 0;
// step size only permitted on lhs if incremental
if (lhs && *step && !*incr) return 0;
if (lhs && !*step) *step = 1; // default
return 1;
} else if (strcmp(s, "pc") == 0) { } else if (strcmp(s, "pc") == 0) {
// program change // program change
if (*step) return 0; // step size not permitted if (*step) return 0; // step size not permitted
@ -1006,6 +915,14 @@ parse_midi(char *tok, char *s, int lhs,
if (lhs && *step && !*incr) return 0; if (lhs && *step && !*incr) return 0;
if (lhs && !*step) *step = 1; // default if (lhs && !*step) *step = 1; // default
return 1; return 1;
} else if (strcmp(s, "kp") == 0) {
// key pressure
if (m < 0 || m > 127) return 0;
*status = 0xa0 | k; *data = m;
// step size only permitted on lhs if incremental
if (lhs && *step && !*incr) return 0;
if (lhs && !*step) *step = 1; // default
return 1;
} else { } else {
if (lhs && *step) return 0; // step size not permitted on lhs if (lhs && *step) return 0; // step size not permitted on lhs
// we must be looking at a MIDI note here, with m denoting the // we must be looking at a MIDI note here, with m denoting the
@ -1101,6 +1018,64 @@ start_translation(translation *tr, char *which_key)
} }
} }
break; break;
case 0xa0:
if (!incr) {
// kp on/off
first_stroke = find_kp(tr, k, chan, data, 0);
release_first_stroke = find_kp(tr, k, chan, data, 1);
if (is_anyshift) {
alt_press_stroke = find_kp(tr, 0, chan, data, 0);
alt_release_stroke = find_kp(tr, 0, chan, data, 1);
}
is_keystroke = 1;
} else {
// kp (step up, down)
if (step <= 0) {
fprintf(stderr, "zero or negative step size not permitted here: [%s]%s\n", current_translation, which_key);
return 1;
}
first_stroke = find_kps(tr, k, chan, data, dir>0, step);
if (is_anyshift) {
alt_press_stroke = find_kps(tr, 0, chan, data, dir>0, step);
}
if (!dir) {
is_bidirectional = 1;
release_first_stroke = find_kps(tr, k, chan, data, 1, step);
if (is_anyshift) {
alt_release_stroke = find_kps(tr, 0, chan, data, 1, step);
}
}
}
break;
case 0xd0:
if (!incr) {
// cp on/off
first_stroke = find_cp(tr, k, chan, 0);
release_first_stroke = find_cp(tr, k, chan, 1);
if (is_anyshift) {
alt_press_stroke = find_cp(tr, 0, chan, 0);
alt_release_stroke = find_cp(tr, 0, chan, 1);
}
is_keystroke = 1;
} else {
// cp (step up, down)
if (step <= 0) {
fprintf(stderr, "zero or negative step size not permitted here: [%s]%s\n", current_translation, which_key);
return 1;
}
first_stroke = find_cps(tr, k, chan, dir>0, step);
if (is_anyshift) {
alt_press_stroke = find_cps(tr, 0, chan, dir>0, step);
}
if (!dir) {
is_bidirectional = 1;
release_first_stroke = find_cps(tr, k, chan, 1, step);
if (is_anyshift) {
alt_release_stroke = find_cps(tr, 0, chan, 1, step);
}
}
}
break;
case 0xe0: case 0xe0:
if (!incr) { if (!incr) {
// pb on/off // pb on/off