Add an internal shift status and SHIFT token to make it possible to translate the same input to different outputs depending on the shift status.

master
Albert Graef 2018-08-14 10:52:34 +02:00
parent 1e6d950a3a
commit 9d6cc38171
5 changed files with 366 additions and 180 deletions

View File

@ -136,7 +136,7 @@ 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. (This isn't necessary if you use midizap's built-in MIDI monitoring facility, as it always prints out MIDI notes in exactly the form that is used in the midizaprc file, no matter what the `MIDI_OCTAVE` offset happens to be.)
Finally, there are some additional directives (and corresponding command line options) to set midizap's Jack client name and the number of input and output ports it uses. (If both the command line options and directives in the midizaprc file are used, the former take priority, so that it's always possible to override the options from the midizaprc file from the command line.)
Also, there are some additional directives (and corresponding command line options) to set midizap's Jack client name and the number of input and output ports it uses. (If both the command line options and directives in the midizaprc file are used, the former take priority, so that it's always possible to override the options from the midizaprc file from the command line.)
Firstly, there's the `-j` option and the `JACK_NAME` directive which change the Jack client name from the default (`midizap`) to whatever you want it to be. To use this option, simply invoke midizap as `midizap -j client-name`, or put the following directive into your midizaprc file:
@ -156,6 +156,31 @@ You may want to place this directive directly into a configuration file if the c
## Secondary MIDI Ports
Some MIDI controllers need a more elaborate setup than what we've seen so far, because they have motor faders, LEDs and similar controls requiring feedback from the application. To accommodate these, you can use the `-o2` option of midizap, or the `JACK_PORTS 2` directive in the midizaprc file, to create a second pair of MIDI input and output ports, named `midi_input2` and `midi_output2`. Use of this option also activates a second MIDI default section in the midizaprc file, labeled `[MIDI2]`, which is used exclusively for translating MIDI from the second input port and sending the resulting MIDI data to the second output port. Typically, the translations in the `[MIDI2]` section will be the inverse of those in the `[MIDI]` section, or whatever it takes to translate the MIDI feedback from the application back to MIDI data which the controller understands.
Some MIDI controllers need a more elaborate setup than what we've seen so far, because they have motor faders, LEDs, etc. requiring feedback from the application. To accommodate these, you can use the `-o2` option of midizap, or the `JACK_PORTS 2` directive in the midizaprc file, to create a second pair of MIDI input and output ports, named `midi_input2` and `midi_output2`. Use of this option also activates a second MIDI default section in the midizaprc file, labeled `[MIDI2]`, which is used exclusively for translating MIDI from the second input port and sending the resulting MIDI data to the second output port. Typically, the translations in the `[MIDI2]` section will be the inverse of those in the `[MIDI]` section, or whatever it takes to translate the MIDI feedback from the application back to MIDI data which the controller understands.
You then wire up the controller to the `midi_input` port of midizap and the `midi_output` port to the application as before, but in addition you also connect the application back to midizap's `midi_input2` port, and the `midi_output2` port to the controller. This reverse path is what is needed to translate the feedback from the application and send it back to the controller. Please check the example.midizaprc file for a simple example illustrating this kind of setup.
## Shift Status
Finally, there's a special `SHIFT` token which can be used to toggle an internal shift state. This comes in handy if you want to generate different output for certain MIDI messages depending on the shift status. Only one such shift status is available in the present implementation. Also note that the `SHIFT` token doesn't generate any output by itself; it merely toggles the internal shift bit which can then be queried in other translations to distinguish between shifted and unshifted bindings for the same input message.
To these ends, there are two additional prefixes which indicate the shift status in which a translation is active. Unprefixed translations are active only in unshifted state. The `^` prefix denotes a translation which is active only in shifted state, while the `?` prefix indicates a translation which is active in *both* shifted and unshifted state.
Many Mackie-like DAW controllers have some designated shift keys which can be used for this purpose, but the following will actually work with any key-style MIDI message. E.g., to bind the shift key (`A#5`) on a Mackie controller:
~~~
?A#5 SHIFT
~~~
Note the `?` prefix indicating 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 the translation for the shift key itself, we can now indicate that a translation should be valid only in shifted state with the `^` prefix. This makes it possible to assign different functions, e.g., to buttons and faders which depend on the shift state. Here's a typical example which maps a control change to either Mackie-style fader values encoded as pitch bends, or incremental encoder values:
~~~
CC48= PB[129]-1 # translate controller to pitch bend when unshifted
^CC48= CC16~ # translate controller to encoder when shifted
~~~

View File

@ -1,55 +1,55 @@
# MCU emulation for the AKAI APCmini
# Mackie emulation for the AKAI APCmini
JACK_NAME "midizap-APCmini"
JACK_PORTS 2
# This emulation is somewhat limited since the APCmini has no encoders and no
# motorized faders, but it should be good enough for basic mixing. Tested in
# Ardour (configure as a Mackie surface, connect midizap's midi_output port to
# Ardour's "mackie control in" port, and vice versa Ardour's "mackie control
# out" to midizap's midi_in2).
# This turns the APCmini into a Mackie-compatible controller, so that it can
# be used with Linux DAW programs like Ardour. The emulation is somewhat
# limited since the APCmini has no encoders, no motorized faders, and not
# nearly as many dedicated buttons as a Mackie device. But it offers enough
# controls to be usable as a basic DAW controller.
# TODO: Figure out whether there's a way to flip the controls so that the
# faders can be used for panning. I tried assigning the MCU flip key as well
# as the various MCU shift keys, but except for the standard shift key they
# don't seem to do anything. :( The standard shift key (A#5, bound to the
# APCmini's shift key D8 below) works, though, and can be used, e.g., to
# shift-select tracks, and to operate the faders in a group together
# Tested with Ardour. Setup: In Ardour, enable the Mackie control surface,
# then connect Ardour's "mackie control" ports to midizap's midi_out and
# midi_in2 ports, and the APCmini to midizap's midi_in and midi_out2 ports.
[MIDI]
# transport (assigned to topmost 5 "scene launch" buttons on the right)
# The APCmini's dedicated shift key is used to provide alternative functions
# to some of the buttons and the faders.
?D8 SHIFT RELEASE SHIFT
# transport (assigned to the topmost 5 "scene launch" buttons on the right)
A#6 A7 # Stop
B6 A#7 # Play
C7 B7 # Rec
#C7 C#7 # Cycle
C#7 G7 # Rew
D7 G#7 # FFwd
# next two keys below are bound to bank select left and right
D#7 A#3 # Bank Left
E7 B3 # Bank Right
# the next three buttons below are used for the MCU shift keys
# NOTE: The MCU actually has four shift keys, so one has to go. You may want
# to rearrange these as needed.
D#7 A#5 # Shift
E7 B5 # Control
F7 C6 # Option
#F7 C#6 # Alt/Cmd
# bottommost "scene launch" button (labeled "stop all clips" on the APCmini)
# assign this to whatever you want, I use it for cycle
F7 D7 # Cycle
# shifted "scene launch" buttons
# We assign these to the function keys F1..F8 here, but of course you can
# remap these as needed.
^A#6 F#4
^B6 G4
^C7 G#4
^C#7 A4
^D7 A#4
^D#7 B4
^E7 C5
^F7 C#5
# shift key (bottom key on the right, above the master fader)
D8 A#5 # Shift
# faders (use 129 as PB step size to get full range)
CC48= PB[129]-1
CC49= PB[129]-2
CC50= PB[129]-3
CC51= PB[129]-4
CC52= PB[129]-5
CC53= PB[129]-6
CC54= PB[129]-7
CC55= PB[129]-8
# master fader
CC56= PB[129]-9
# bottom 3x8 grid: mute/solo/rec (these happen to be identical to the MCU)
# bottom 3x8 grid: mute/solo/rec
# NOTE: Incidentally, these happen to be identical to corresponding MCU input.
# rec (bottom row of the grid)
C0 C0
@ -81,7 +81,7 @@ A1 A1
A#1 A#1
B1 B1
# select (bottom row right above the faders)
# track select (bottom row right above the faders)
E5 C2
F5 C#2
F#5 D2
@ -91,6 +91,41 @@ A5 F2
A#5 F#2
B5 G2
# shifted bottom row
# We have these assigned to the bank/channel and track/pan/send/instr controls,
# but you may want to remap some or all of these as needed.
^E5 A#3 # Bank Left
^F5 B3 # Bank Right
^F#5 C4 # Channel Left
^G5 C#4 # Channel Right
# NOTE: Only Pan and Send appear to be supported in Ardour.
^G#5 E3 # Track (Volume)
^A5 F#3 # Pan
^A#5 F3 # Send
^B5 A3 # Instr (Device)
# faders (MCU uses pitch bends here, use 129 as the step size to get full range)
CC48= PB[129]-1
CC49= PB[129]-2
CC50= PB[129]-3
CC51= PB[129]-4
CC52= PB[129]-5
CC53= PB[129]-6
CC54= PB[129]-7
CC55= PB[129]-8
# master fader
?CC56= PB[129]-9
# faders become encoders when shifted (CC16..CC23, incremental mode)
^CC48= CC16~
^CC49= CC17~
^CC50= CC18~
^CC51= CC19~
^CC52= CC20~
^CC53= CC21~
^CC54= CC22~
^CC55= CC23~
# feedback section ########################################################
[MIDI2]
@ -99,61 +134,57 @@ B5 G2
A7 A#6
A#7 B6
B7 C7[2] # Rec, blinks when engaged
#C#7 C7 # Cycle
G7 C#7
G#7 D7
# bank select
# NOTE: These don't seem to work properly in Ardour (only the bank left button
# lights up), disabled for now.
#A#3 D#7
#B3 E7
# cycle
D7 F7
# NOTE: Ardour seems to provide feedback for the shift key, but it doesn't
# light up, on my APCmini at least. Maybe it has no LED?
A#5 D8
# MCU shift keys
A#5 D#7
B5 E7
C6 F7
# no feedback for faders (faders aren't motorized)
# NOTE: Feedback for rec/solo/mute/select also needs to be recognized in shift
# mode, so that the (shifted) bank/channel left/right keys work as expected.
# rec: color = red (vel. 3)
C0 C0[3]
C#0 C#0[3]
D0 D0[3]
D#0 D#0[3]
E0 E0[3]
F0 F0[3]
F#0 F#0[3]
G0 G0[3]
?C0 C0[3]
?C#0 C#0[3]
?D0 D0[3]
?D#0 D#0[3]
?E0 E0[3]
?F0 F0[3]
?F#0 F#0[3]
?G0 G0[3]
# solo: color = green (vel. 1)
G#0 G#0[1]
A0 A0[1]
A#0 A#0[1]
B0 B0[1]
C1 C1[1]
C#1 C#1[1]
D1 D1[1]
D#1 D#1[1]
?G#0 G#0[1]
?A0 A0[1]
?A#0 A#0[1]
?B0 B0[1]
?C1 C1[1]
?C#1 C#1[1]
?D1 D1[1]
?D#1 D#1[1]
# mute: color = yellow (vel. 5)
E1 E1[5]
F1 F1[5]
F#1 F#1[5]
G1 G1[5]
G#1 G#1[5]
A1 A1[5]
A#1 A#1[5]
B1 B1[5]
?E1 E1[5]
?F1 F1[5]
?F#1 F#1[5]
?G1 G1[5]
?G#1 G#1[5]
?A1 A1[5]
?A#1 A#1[5]
?B1 B1[5]
# select (will light up in red)
# NOTE: For some reason, Ardour doesn't update these when changing banks.
C2 E5
C#2 F5
D2 F#5
D#2 G5
E2 G#5
F2 A5
F#2 A#5
G2 B5
# NOTE: Ardour apparently doesn't update these when changing banks.
?C2 E5
?C#2 F5
?D2 F#5
?D#2 G5
?E2 G#5
?F2 A5
?F#2 A#5
?G2 B5

View File

@ -1,8 +1,6 @@
/*
Contour ShuttlePro v2 interface
Copyright 2013 Eric Messick (FixedImagePhoto.com/Contact)
Copyright 2018 Albert Graef <aggraef@gmail.com>, various improvements
@ -24,6 +22,7 @@ Display *display;
JACK_SEQ seq;
int jack_num_outputs = 0, debug_jack = 0;
int shift = 0;
void
initdisplay(void)
@ -159,19 +158,19 @@ fetch_stroke(translation *tr, uint8_t portno, int status, int chan, int data,
if (tr && tr->portno == portno) {
switch (status) {
case 0x90:
return tr->note[chan][data][index];
return tr->note[shift][chan][data][index];
case 0xc0:
return tr->pc[chan][data][index];
return tr->pc[shift][chan][data][index];
case 0xb0:
if (dir)
return tr->ccs[chan][data][dir>0];
return tr->ccs[shift][chan][data][dir>0];
else
return tr->cc[chan][data][index];
return tr->cc[shift][chan][data][index];
case 0xe0:
if (dir)
return tr->pbs[chan][dir>0];
return tr->pbs[shift][chan][dir>0];
else
return tr->pb[chan][index];
return tr->pb[shift][chan][index];
default:
return NULL;
}
@ -214,40 +213,40 @@ static char *debug_key(translation *tr, char *name,
int status, int chan, int data, int dir)
{
static char *note_names[] = { "C", "C#", "D", "Eb", "E", "F", "F#", "G", "G#", "A", "Bb", "B" };
char *suffix = "";
char *prefix = shift?"^":"", *suffix = "";
strcpy(name, "??");
switch (status) {
case 0x90:
sprintf(name, "%s%d-%d", note_names[data % 12],
sprintf(name, "%s%s%d-%d", prefix, note_names[data % 12],
data / 12 + midi_octave, chan+1);
break;
case 0xb0: {
int step = tr->cc_step[chan][data][dir>0];
int step = tr->cc_step[shift][chan][data][dir>0];
if (!dir)
suffix = "";
else if (tr->is_incr[chan][data])
else if (tr->is_incr[shift][chan][data])
suffix = (dir<0)?"<":">";
else
suffix = (dir<0)?"-":"+";
if (dir && step != 1)
sprintf(name, "CC%d[%d]-%d%s", data, step, chan+1, suffix);
sprintf(name, "%sCC%d[%d]-%d%s", prefix, data, step, chan+1, suffix);
else
sprintf(name, "CC%d-%d%s", data, chan+1, suffix);
sprintf(name, "%sCC%d-%d%s", prefix, data, chan+1, suffix);
break;
}
case 0xc0:
sprintf(name, "PC%d-%d", data, chan+1);
sprintf(name, "%sPC%d-%d", prefix, data, chan+1);
break;
case 0xe0: {
int step = tr->pb_step[chan][dir>0];
int step = tr->pb_step[shift][chan][dir>0];
if (!dir)
suffix = "";
else
suffix = (dir<0)?"-":"+";
if (dir && step != 1)
sprintf(name, "PB[%d]-%d%s", step, chan+1, suffix);
sprintf(name, "%sPB[%d]-%d%s", prefix, step, chan+1, suffix);
else
sprintf(name, "PB-%d%s", chan+1, suffix);
sprintf(name, "%sPB-%d%s", prefix, chan+1, suffix);
break;
}
default: // this can't happen
@ -303,11 +302,19 @@ send_strokes(translation *tr, uint8_t portno, int status, int chan, int data,
{
int nkeys = 0;
stroke *s = fetch_stroke(tr, portno, status, chan, data, index, dir);
// If there's no press/release translation, check whether we have got at
// least the corresponding release/press translation, in order to prevent
// spurious error messages if either the press or release translation just
// happens to be empty.
int chk = s ||
(!dir && fetch_stroke(tr, portno, status, chan, data, !index, dir));
if (!s && jack_num_outputs) {
// fall back to default MIDI translation
tr = default_midi_translation[portno];
s = fetch_stroke(tr, portno, status, chan, data, index, dir);
chk = chk || s ||
(!dir && fetch_stroke(tr, portno, status, chan, data, !index, dir));
// Ignore all MIDI input on the second port if no translation was found in
// the [MIDI2] section (or the section is missing altogether).
if (portno && !s) return;
@ -317,6 +324,8 @@ send_strokes(translation *tr, uint8_t portno, int status, int chan, int data,
// fall back to the default translation
tr = default_translation;
s = fetch_stroke(tr, portno, status, chan, data, index, dir);
chk = chk || s ||
(!dir && fetch_stroke(tr, portno, status, chan, data, !index, dir));
}
if (debug_regex) {
@ -324,10 +333,10 @@ send_strokes(translation *tr, uint8_t portno, int status, int chan, int data,
// found a sequence, print the matching section now
debug_section(tr);
debug_state = 0;
} else {
// no matches yet; to prevent a cascade of spurious messages, we defer
} else if (!chk) {
// No matches yet. To prevent a cascade of spurious messages, we defer
// printing the matched section for now and just record it instead; it
// may then be printed later
// may then be printed later.
debug_tr = tr;
// record that we actually tried to process some input
debug_count = 1;
@ -343,6 +352,9 @@ send_strokes(translation *tr, uint8_t portno, int status, int chan, int data,
if (s->keysym) {
send_key(s->keysym, s->press);
nkeys++;
} else if (s->shift) {
// toggle shift status
shift = !shift;
} else {
send_midi(portno, s->status, s->data, s->step, s->incr, index, dir);
}
@ -475,16 +487,16 @@ int
check_incr(translation *tr, uint8_t portno, int chan, int data)
{
if (tr && tr->portno == portno &&
(tr->ccs[chan][data][0] || tr->ccs[chan][data][1]))
return tr->is_incr[chan][data];
(tr->ccs[shift][chan][data][0] || tr->ccs[shift][chan][data][1]))
return tr->is_incr[shift][chan][data];
tr = default_midi_translation[portno];
if (tr && tr->portno == portno &&
(tr->ccs[chan][data][0] || tr->ccs[chan][data][1]))
return tr->is_incr[chan][data];
(tr->ccs[shift][chan][data][0] || tr->ccs[shift][chan][data][1]))
return tr->is_incr[shift][chan][data];
tr = default_translation;
if (tr && tr->portno == portno &&
(tr->ccs[chan][data][0] || tr->ccs[chan][data][1]))
return tr->is_incr[chan][data];
(tr->ccs[shift][chan][data][0] || tr->ccs[shift][chan][data][1]))
return tr->is_incr[shift][chan][data];
return 0;
}
@ -492,16 +504,16 @@ int
get_cc_step(translation *tr, uint8_t portno, int chan, int data, int dir)
{
if (tr && tr->portno == portno &&
tr->ccs[chan][data][dir>0])
return tr->cc_step[chan][data][dir>0];
tr->ccs[shift][chan][data][dir>0])
return tr->cc_step[shift][chan][data][dir>0];
tr = default_midi_translation[portno];
if (tr && tr->portno == portno &&
tr->ccs[chan][data][dir>0])
return tr->cc_step[chan][data][dir>0];
tr->ccs[shift][chan][data][dir>0])
return tr->cc_step[shift][chan][data][dir>0];
tr = default_translation;
if (tr && tr->portno == portno &&
tr->ccs[chan][data][dir>0])
return tr->cc_step[chan][data][dir>0];
tr->ccs[shift][chan][data][dir>0])
return tr->cc_step[shift][chan][data][dir>0];
return 1;
}
@ -509,15 +521,15 @@ int
check_pbs(translation *tr, uint8_t portno, int chan)
{
if (tr && tr->portno == portno &&
(tr->pbs[chan][0] || tr->pbs[chan][1]))
(tr->pbs[shift][chan][0] || tr->pbs[shift][chan][1]))
return 1;
tr = default_midi_translation[portno];
if (tr && tr->portno == portno &&
(tr->pbs[chan][0] || tr->pbs[chan][1]))
(tr->pbs[shift][chan][0] || tr->pbs[shift][chan][1]))
return 1;
tr = default_translation;
if (tr && tr->portno == portno &&
(tr->pbs[chan][0] || tr->pbs[chan][1]))
(tr->pbs[shift][chan][0] || tr->pbs[shift][chan][1]))
return 1;
return 0;
}
@ -526,16 +538,16 @@ int
get_pb_step(translation *tr, uint8_t portno, int chan, int dir)
{
if (tr && tr->portno == portno &&
tr->pbs[chan][dir>0])
return tr->pb_step[chan][dir>0];
tr->pbs[shift][chan][dir>0])
return tr->pb_step[shift][chan][dir>0];
tr = default_midi_translation[portno];
if (tr && tr->portno == portno &&
tr->pbs[chan][dir>0])
return tr->pb_step[chan][dir>0];
tr->pbs[shift][chan][dir>0])
return tr->pb_step[shift][chan][dir>0];
tr = default_translation;
if (tr && tr->portno == portno &&
tr->pbs[chan][dir>0])
return tr->pb_step[chan][dir>0];
tr->pbs[shift][chan][dir>0])
return tr->pb_step[shift][chan][dir>0];
return 1;
}

View File

@ -47,8 +47,10 @@ typedef struct _stroke {
struct _stroke *next;
// nonzero keysym indicates a key event
KeySym keysym;
int press; // zero -> release, non-zero -> press
// keysym == 0 => MIDI event
int8_t press; // zero -> release, non-zero -> press
// nonzero value indicates a shift event
int8_t shift;
// keysym == shift == 0 => MIDI event
int status, data; // status and, if applicable, first data byte
int step; // step size (1, 127 or 8191 by default, depending on status)
// the incremental bit indicates an incremental control change (typically
@ -69,19 +71,18 @@ typedef struct _translation {
regex_t regex;
uint8_t portno;
// XXXFIXME: This way of storing the translation tables is easy to
// construct, but wastes quite a lot of memory (needs some 128 KB per
// translation section even if most of the entries are NULL pointers). We
// should rather use some kind of dictionary here.
uint8_t is_incr[NUM_CHAN][NUM_KEYS];
stroke *pc[NUM_CHAN][NUM_KEYS][2];
stroke *note[NUM_CHAN][NUM_KEYS][2];
stroke *cc[NUM_CHAN][NUM_KEYS][2];
stroke *ccs[NUM_CHAN][NUM_KEYS][2];
stroke *pb[NUM_CHAN][2];
stroke *pbs[NUM_CHAN][2];
// construct, but wastes quite a lot of memory. We should rather use some
// kind of dictionary here.
uint8_t is_incr[2][NUM_CHAN][NUM_KEYS];
stroke *pc[2][NUM_CHAN][NUM_KEYS][2];
stroke *note[2][NUM_CHAN][NUM_KEYS][2];
stroke *cc[2][NUM_CHAN][NUM_KEYS][2];
stroke *ccs[2][NUM_CHAN][NUM_KEYS][2];
stroke *pb[2][NUM_CHAN][2];
stroke *pbs[2][NUM_CHAN][2];
// step size for control changes and pitch bend (1 by default)
int cc_step[NUM_CHAN][NUM_KEYS][2];
int pb_step[NUM_CHAN][2];
int cc_step[2][NUM_CHAN][NUM_KEYS][2];
int pb_step[2][NUM_CHAN][2];
} translation;
extern void reload_callback(void);
@ -94,5 +95,5 @@ extern int default_debug_regex, default_debug_strokes, default_debug_keys,
default_debug_midi;
extern char *config_file_name;
extern int jack_num_outputs;
extern int midi_octave;
extern int midi_octave, shift;
extern char *jack_client_name;

View File

@ -179,12 +179,36 @@
with keypress-style messages (except PC), where they set the value for
the "on" state.
Finally, on the output side there's a special token of the form
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"
@ -350,28 +374,30 @@ free_strokes(stroke *s)
void
free_translation_section(translation *tr)
{
int i, j;
int i, j, k;
if (tr != NULL) {
free(tr->name);
if (!tr->is_default) {
regfree(&tr->regex);
}
for (i=0; i<NUM_CHAN; i++) {
for (j=0; j<NUM_KEYS; j++) {
free_strokes(tr->pc[i][j][0]);
free_strokes(tr->pc[i][j][1]);
free_strokes(tr->note[i][j][0]);
free_strokes(tr->note[i][j][1]);
free_strokes(tr->cc[i][j][0]);
free_strokes(tr->cc[i][j][1]);
free_strokes(tr->ccs[i][j][0]);
free_strokes(tr->ccs[i][j][1]);
for (k=0; k<2; k++) {
for (i=0; i<NUM_CHAN; i++) {
for (j=0; j<NUM_KEYS; j++) {
free_strokes(tr->pc[k][i][j][0]);
free_strokes(tr->pc[k][i][j][1]);
free_strokes(tr->note[k][i][j][0]);
free_strokes(tr->note[k][i][j][1]);
free_strokes(tr->cc[k][i][j][0]);
free_strokes(tr->cc[k][i][j][1]);
free_strokes(tr->ccs[k][i][j][0]);
free_strokes(tr->ccs[k][i][j][1]);
}
free_strokes(tr->pb[k][i][0]);
free_strokes(tr->pb[k][i][1]);
free_strokes(tr->pbs[k][i][0]);
free_strokes(tr->pbs[k][i][1]);
}
free_strokes(tr->pb[i][0]);
free_strokes(tr->pb[i][1]);
free_strokes(tr->pbs[i][0]);
free_strokes(tr->pbs[i][1]);
}
free(tr);
}
@ -512,6 +538,8 @@ print_stroke(stroke *s)
str = "???";
}
printf("%s/%c ", str, s->press ? 'D' : 'U');
} else if (s->shift) {
printf("SHIFT ");
} else {
int status = s->status & 0xf0;
int channel = (s->status & 0x0f) + 1;
@ -561,7 +589,9 @@ stroke **first_stroke;
stroke *last_stroke;
stroke **press_first_stroke;
stroke **release_first_stroke;
int is_keystroke, is_bidirectional;
stroke **alt_press_stroke;
stroke **alt_release_stroke;
int is_keystroke, is_bidirectional, is_anyshift;
int is_midi;
char *current_translation;
char *key_name;
@ -583,6 +613,25 @@ append_stroke(KeySym sym, int press)
s->next = NULL;
s->keysym = sym;
s->press = press;
s->shift = 0;
s->status = s->data = s->step = s->incr = s->dirty = 0;
if (*first_stroke) {
last_stroke->next = s;
} else {
*first_stroke = s;
}
last_stroke = s;
}
void
append_shift(void)
{
stroke *s = (stroke *)allocate(sizeof(stroke));
s->next = NULL;
s->shift = 1;
s->keysym = 0;
s->press = 0;
s->status = s->data = s->step = s->incr = s->dirty = 0;
if (*first_stroke) {
last_stroke->next = s;
@ -600,6 +649,7 @@ append_midi(int status, int data, int step, int incr)
s->next = NULL;
s->keysym = 0;
s->press = 0;
s->shift = 0;
s->status = status;
s->data = data;
s->step = step;
@ -862,7 +912,7 @@ parse_midi(char *tok, char *s, int lhs,
int
start_translation(translation *tr, char *which_key)
{
int status, data, step, incr, dir;
int k, status, data, step, incr, dir;
char buf[100];
//printf("start_translation(%s)\n", which_key);
@ -873,18 +923,23 @@ start_translation(translation *tr, char *which_key)
}
current_translation = tr->name;
key_name = which_key;
is_keystroke = is_bidirectional = is_midi = 0;
is_keystroke = is_bidirectional = is_midi = is_anyshift = 0;
first_release_stroke = 0;
regular_key_down = 0;
modifier_count = 0;
midi_channel = 0;
if (parse_midi(which_key, buf, 1, &status, &data, &step, &incr, &dir)) {
k = *which_key == '^' || (is_anyshift = *which_key == '?');
if (parse_midi(which_key+k, buf, 1, &status, &data, &step, &incr, &dir)) {
int chan = status & 0x0f;
switch (status & 0xf0) {
case 0x90:
// note on/off
first_stroke = &(tr->note[chan][data][0]);
release_first_stroke = &(tr->note[chan][data][1]);
first_stroke = &(tr->note[k][chan][data][0]);
release_first_stroke = &(tr->note[k][chan][data][1]);
if (is_anyshift) {
alt_press_stroke = &(tr->note[0][chan][data][0]);
alt_release_stroke = &(tr->note[0][chan][data][1]);
}
is_keystroke = 1;
break;
case 0xc0:
@ -893,21 +948,32 @@ start_translation(translation *tr, char *which_key)
// this message has no off state. Thus, when we receive a pc, it's
// supposed to be treated as a "press" sequence immediately followed by
// the corresponding "release" sequence.
first_stroke = &(tr->pc[chan][data][0]);
release_first_stroke = &(tr->pc[chan][data][1]);
first_stroke = &(tr->pc[k][chan][data][0]);
release_first_stroke = &(tr->pc[k][chan][data][1]);
if (is_anyshift) {
alt_press_stroke = &(tr->pc[0][chan][data][0]);
alt_release_stroke = &(tr->pc[0][chan][data][1]);
}
is_keystroke = 1;
break;
case 0xb0:
if (!incr) {
// cc on/off
first_stroke = &(tr->cc[chan][data][0]);
release_first_stroke = &(tr->cc[chan][data][1]);
first_stroke = &(tr->cc[k][chan][data][0]);
release_first_stroke = &(tr->cc[k][chan][data][1]);
if (is_anyshift) {
alt_press_stroke = &(tr->cc[0][chan][data][0]);
alt_release_stroke = &(tr->cc[0][chan][data][1]);
}
is_keystroke = 1;
} else {
// cc (step up, down)
tr->is_incr[chan][data] = incr>1;
first_stroke = &(tr->ccs[chan][data][dir>0]);
tr->cc_step[chan][data][dir>0] = step;
tr->is_incr[k][chan][data] = incr>1;
first_stroke = &(tr->ccs[k][chan][data][dir>0]);
if (is_anyshift) {
alt_press_stroke = &(tr->ccs[0][chan][data][0]);
}
tr->cc_step[k][chan][data][dir>0] = step;
if (!dir) {
// This is a bidirectional translation (=, ~). We first fill in the
// "down" part (pointed to by first_stroke). When finishing off the
@ -917,16 +983,23 @@ start_translation(translation *tr, char *which_key)
// translation, here to remember the "up" part of the translation,
// so that we can fill in that part later.
is_bidirectional = 1;
release_first_stroke = &(tr->ccs[chan][data][1]);
tr->cc_step[chan][data][1] = step;
release_first_stroke = &(tr->ccs[k][chan][data][1]);
if (is_anyshift) {
alt_release_stroke = &(tr->ccs[0][chan][data][1]);
}
tr->cc_step[k][chan][data][1] = step;
}
}
break;
case 0xe0:
if (!incr) {
// pb on/off
first_stroke = &(tr->pb[chan][0]);
release_first_stroke = &(tr->pb[chan][1]);
first_stroke = &(tr->pb[k][chan][0]);
release_first_stroke = &(tr->pb[k][chan][1]);
if (is_anyshift) {
alt_press_stroke = &(tr->pb[0][chan][0]);
alt_release_stroke = &(tr->pb[0][chan][1]);
}
is_keystroke = 1;
} else {
// pb (step up, down)
@ -934,12 +1007,18 @@ start_translation(translation *tr, char *which_key)
fprintf(stderr, "zero or negative step size not permitted here: [%s]%s\n", current_translation, which_key);
return 1;
}
first_stroke = &(tr->pbs[chan][dir>0]);
tr->pb_step[chan][dir>0] = step;
first_stroke = &(tr->pbs[k][chan][dir>0]);
if (is_anyshift) {
alt_press_stroke = &(tr->pbs[0][chan][0]);
}
tr->pb_step[k][chan][dir>0] = step;
if (!dir) {
is_bidirectional = 1;
release_first_stroke = &(tr->pbs[chan][1]);
tr->pb_step[chan][1] = step;
release_first_stroke = &(tr->pbs[k][chan][1]);
if (is_anyshift) {
alt_release_stroke = &(tr->pbs[0][chan][1]);
}
tr->pb_step[k][chan][1] = step;
}
}
break;
@ -953,7 +1032,10 @@ start_translation(translation *tr, char *which_key)
return 1;
}
if (*first_stroke != NULL ||
(is_bidirectional && *release_first_stroke != NULL)) {
(is_bidirectional && *release_first_stroke != NULL) ||
(is_anyshift &&
(*alt_press_stroke != NULL ||
(is_bidirectional && *alt_release_stroke != NULL)))) {
fprintf(stderr, "can't redefine message: [%s]%s\n", current_translation, which_key);
return 1;
}
@ -1005,7 +1087,7 @@ add_release(int all_keys)
// MIDI events in there and add them to the "release" strokes
stroke *s = *press_first_stroke;
while (s) {
if (!s->keysym && s->dirty) {
if (!s->keysym && !s->shift && s->dirty) {
append_midi(s->status, s->data, s->step, s->incr);
s->dirty = 0;
}
@ -1025,12 +1107,43 @@ add_release(int all_keys)
while (s) {
if (s->keysym) {
append_stroke(s->keysym, s->press);
} else if (s->shift) {
append_shift();
} else {
append_midi(s->status, s->data, s->step, s->incr);
}
s = s->next;
}
}
if (all_keys && is_anyshift) {
// create a duplicate for any-shift translations (?)
stroke *s = *press_first_stroke;
first_stroke = alt_press_stroke;
while (s) {
if (s->keysym) {
append_stroke(s->keysym, s->press);
} else if (s->shift) {
append_shift();
} else {
append_midi(s->status, s->data, s->step, s->incr);
}
s = s->next;
}
if (is_keystroke || is_bidirectional) {
s = *release_first_stroke;
first_stroke = alt_release_stroke;
while (s) {
if (s->keysym) {
append_stroke(s->keysym, s->press);
} else if (s->shift) {
append_shift();
} else {
append_midi(s->status, s->data, s->step, s->incr);
}
s = s->next;
}
}
}
}
void
@ -1283,7 +1396,11 @@ read_config_file(void)
case ' ':
case '\t':
case '\n':
if (strncmp(tok, "XK", 2) && strncmp(tok, "RELEASE", 8))
if (!strcmp(tok, "RELEASE"))
add_keystroke(tok, PRESS_RELEASE);
else if (!strcmp(tok, "SHIFT"))
append_shift();
else if (strncmp(tok, "XK", 2))
add_midi(tok);
else
add_keystroke(tok, PRESS_RELEASE);