Add support for incremental (endless rotary) controls, bugfixes.

master
Albert Graef 2018-08-09 09:11:51 +02:00
parent 03e67992f9
commit e1468bd401
3 changed files with 88 additions and 66 deletions

View File

@ -75,7 +75,7 @@ static int16_t pbvalue[16] =
8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192};
void
send_midi(int status, int data, int index, int incr, int step)
send_midi(int status, int data, int step, int incr, int index, int dir)
{
if (!enable_jack_output) return; // MIDI output not enabled
uint8_t msg[3];
@ -91,15 +91,20 @@ send_midi(int status, int data, int index, int incr, int step)
}
break;
case 0xb0:
if (incr) {
// increment (incr==1) or decrement (incr==-1) the current value,
// clamping it to the 0..127 data byte range
if (incr > 0) {
if (ccvalue[chan][data] >= 127) return;
msg[2] = ++ccvalue[chan][data];
if (dir) {
if (incr) {
// incremental controller, simply spit out a relative sign bit value
msg[2] = dir>0?1:65;
} else {
if (ccvalue[chan][data] == 0) return;
msg[2] = --ccvalue[chan][data];
// increment (dir==1) or decrement (dir==-1) the current value,
// clamping it to the 0..127 data byte range
if (dir > 0) {
if (ccvalue[chan][data] >= 127) return;
msg[2] = ++ccvalue[chan][data];
} else {
if (ccvalue[chan][data] == 0) return;
msg[2] = --ccvalue[chan][data];
}
}
} else if (!index) {
msg[2] = 127;
@ -111,16 +116,16 @@ send_midi(int status, int data, int index, int incr, int step)
// pitch bends are treated similarly to a controller, but with a 14 bit
// range (0..16383, with 8192 being the center value)
int pbval = 0;
if (incr) {
if (dir) {
if (!step) return;
incr *= step;
if (incr > 0) {
dir *= step;
if (dir > 0) {
if (pbvalue[chan] >= 16383) return;
pbvalue[chan] += incr;
pbvalue[chan] += dir;
if (pbvalue[chan] > 16383) pbvalue[chan] = 16383;
} else {
if (pbvalue[chan] == 0) return;
pbvalue[chan] += incr;
pbvalue[chan] += dir;
if (pbvalue[chan] < 0) pbvalue[chan] = 0;
}
pbval = pbvalue[chan];
@ -147,7 +152,8 @@ send_midi(int status, int data, int index, int incr, int step)
}
stroke *
fetch_stroke(translation *tr, int status, int chan, int data, int index, int incr)
fetch_stroke(translation *tr, int status, int chan, int data,
int index, int dir)
{
if (tr != NULL) {
switch (status) {
@ -156,13 +162,13 @@ fetch_stroke(translation *tr, int status, int chan, int data, int index, int inc
case 0xc0:
return tr->pc[chan][data][index];
case 0xb0:
if (incr)
return tr->ccs[chan][data][incr>0];
if (dir)
return tr->ccs[chan][data][dir>0];
else
return tr->cc[chan][data][index];
case 0xe0:
if (incr)
return tr->pbs[chan][incr>0];
if (dir)
return tr->pbs[chan][dir>0];
else
return tr->pb[chan][index];
default:
@ -175,14 +181,15 @@ fetch_stroke(translation *tr, int status, int chan, int data, int index, int inc
static char *note_names[] = { "C", "C#", "D", "Eb", "E", "F", "F#", "G", "G#", "A", "Bb", "B" };
void
send_strokes(translation *tr, int status, int chan, int data, int index, int incr)
send_strokes(translation *tr, int status, int chan, int data,
int index, int dir)
{
int nkeys = 0;
stroke *s = fetch_stroke(tr, status, chan, data, index, incr);
stroke *s = fetch_stroke(tr, status, chan, data, index, dir);
if (s == NULL) {
tr = default_translation;
s = fetch_stroke(tr, status, chan, data, index, incr);
s = fetch_stroke(tr, status, chan, data, index, dir);
}
if (debug_keys && s) {
@ -192,35 +199,35 @@ send_strokes(translation *tr, int status, int chan, int data, int index, int inc
sprintf(name, "%s%d-%d", note_names[data % 12], data / 12, chan+1);
break;
case 0xb0:
if (!incr)
if (!dir)
suffix = "";
else if (tr->is_incr)
suffix = (incr<0)?"<":">";
else if (tr->is_incr[chan][data])
suffix = (dir<0)?"<":">";
else
suffix = (incr<0)?"-":"+";
suffix = (dir<0)?"-":"+";
sprintf(name, "CC%d-%d%s", data, chan+1, suffix);
break;
case 0xc0:
sprintf(name, "PC%d-%d", data, chan+1);
break;
case 0xe0:
if (!incr)
if (!dir)
suffix = "";
else
suffix = (incr<0)?"-":"+";
suffix = (dir<0)?"-":"+";
sprintf(name, "PB-%d%s", chan+1, suffix);
break;
default: // this can't happen
break;
}
print_stroke_sequence(name, incr?"":index?"U":"D", s);
print_stroke_sequence(name, dir?"":index?"U":"D", s);
}
while (s) {
if (s->keysym) {
send_key(s->keysym, s->press);
nkeys++;
} else {
send_midi(s->status, s->data, index, incr, s->step);
send_midi(s->status, s->data, s->step, s->incr, index, dir);
}
s = s->next;
}
@ -346,10 +353,10 @@ int
check_incr(translation *tr, int chan, int data)
{
if (tr->ccs[chan][data][0] || tr->ccs[chan][data][1])
return tr->is_incr;
return tr->is_incr[chan][data];
tr = default_translation;
if (tr->ccs[chan][data][0] || tr->ccs[chan][data][1])
return tr->is_incr;
return tr->is_incr[chan][data];
return 0;
}
@ -408,7 +415,7 @@ handle_event(uint8_t *msg)
}
}
if (check_incr(tr, chan, msg[1])) {
// Incremental controller a la MCU. NB: This assumed a signed bit
// Incremental controller a la MCU. NB: This assumes a signed bit
// representation (values above 0x40 indicate counter-clockwise
// rotation), which seems to be what most DAWs expect nowadays.
// But some DAWs may also have it the other way round, so that you may
@ -428,10 +435,10 @@ handle_event(uint8_t *msg)
}
}
} else if (inccvalue[chan][msg[1]] != msg[2]) {
int incr = inccvalue[chan][msg[1]] > msg[2] ? -1 : 1;
int dir = inccvalue[chan][msg[1]] > msg[2] ? -1 : 1;
while (inccvalue[chan][msg[1]] != msg[2]) {
send_strokes(tr, status, chan, msg[1], 0, incr);
inccvalue[chan][msg[1]] += incr;
send_strokes(tr, status, chan, msg[1], 0, dir);
inccvalue[chan][msg[1]] += dir;
}
}
break;
@ -450,15 +457,15 @@ handle_event(uint8_t *msg)
}
}
if (check_pbs(tr, chan) && inpbvalue[chan] - 8192 != bend) {
int incr = inpbvalue[chan] - 8192 > bend ? -1 : 1;
int step = tr->step[chan][incr>0];
int dir = inpbvalue[chan] - 8192 > bend ? -1 : 1;
int step = tr->step[chan][dir>0];
if (step) {
while (inpbvalue[chan] - 8192 != bend) {
int d = abs(inpbvalue[chan] - 8192 - bend);
if (d > step) d = step;
if (d < step) break;
send_strokes(tr, status, chan, 0, 0, incr);
inpbvalue[chan] += incr*d;
send_strokes(tr, status, chan, 0, 0, dir);
inpbvalue[chan] += dir*d;
}
}
}

View File

@ -50,6 +50,9 @@ typedef struct _stroke {
// keysym == 0 => MIDI event
int status, data; // status and, if applicable, first data byte
int step; // step size for pitch bends (1 by default)
// the incremental bit indicates an incremental control change (typically
// used with endless rotary encoders) to be represented as a sign bit value
int incr;
// the dirty bit indicates a MIDI event for which a release event still
// needs to be generated in key events
int dirty;
@ -61,7 +64,7 @@ typedef struct _stroke {
typedef struct _translation {
struct _translation *next;
char *name;
int is_default, is_incr;
int is_default, is_incr[NUM_CHAN][NUM_KEYS];
regex_t regex;
// XXXFIXME: This way of storing the translation tables is easy to
// construct, but wastes quite a lot of memory (needs some 128 KB per

View File

@ -504,7 +504,7 @@ print_stroke(stroke *s)
printf("%s%d-%d ", note_names[s->data % 12], s->data / 12, channel);
break;
case 0xb0:
printf("CC%d-%d ", s->data, channel);
printf("CC%d-%d%s ", s->data, channel, s->incr?"~":"");
break;
case 0xc0:
printf("PC%d-%d ", s->data, channel);
@ -556,7 +556,7 @@ append_stroke(KeySym sym, int press)
s->next = NULL;
s->keysym = sym;
s->press = press;
s->status = s->data = s->step = s->dirty = 0;
s->status = s->data = s->step = s->incr = s->dirty = 0;
if (*first_stroke) {
last_stroke->next = s;
} else {
@ -566,7 +566,7 @@ append_stroke(KeySym sym, int press)
}
void
append_midi(int status, int data, int step)
append_midi(int status, int data, int step, int incr)
{
stroke *s = (stroke *)allocate(sizeof(stroke));
@ -576,6 +576,7 @@ append_midi(int status, int data, int step)
s->status = status;
s->data = data;
s->step = step;
s->incr = incr;
// if this is a keystroke event, for all messages but program change (which
// has no "on" and "off" states), mark the event as "dirty" so that the
// corresponding "off" event gets added later to the "release" strokes
@ -662,19 +663,19 @@ re_press_temp_modifiers(void)
tok ::= ( note | msg ) [number] [ "-" number] [incr]
note ::= ( "a" | ... | "g" ) [ "#" | "b" ]
msg ::= "ch" | "pb" [ "[" number "]" ] | "pc" | "cc"
incr ::= "-" | "+" | "<" | ">"
incr ::= "-" | "+" | "<" | ">" | "~"
Numbers are always in decimal. The meaning of the first number depends on
the context (octave number for notes, the actual data byte for other
messages). If present, the suffix with the second number (after the dash)
denotes the MIDI channel, otherwise the default MIDI channel is used. Note
that not all combinations are possible -- "pb" is *not* followed by a data
byte, but may be followed by a step size in brackets; and "ch" may *not*
have a channel number suffix on it. (In fact, "ch" is no real MIDI message
at all; it just sets the default MIDI channel for subsequent messages.) The
incr flag is only permitted in the first token of a translation, and only
in conjunction with "pb" or "cc", whereas "ch" must *not* occur as the
first token. */
byte, but may be followed by a step size in brackets; and "ch" must *not*
occur as the first token and must *not* have a channel number suffix on
it. (In fact, "ch" is no real MIDI message at all; it just sets the default
MIDI channel for subsequent messages in the output sequence.) The incr flag
is only permitted in conjunction with "pb" or "cc", and it takes on a
different form in the first token of a translation. */
static int note_number(char c, char b, int k)
{
@ -690,7 +691,8 @@ static int note_number(char c, char b, int k)
}
int
parse_midi(char *tok, char *s, int *incr, int *step, int *status, int *data)
parse_midi(char *tok, char *s, int lhs,
int *status, int *data, int *step, int *incr)
{
char *p = tok, *t;
int n, m = -1, k = midi_channel, l;
@ -734,19 +736,29 @@ parse_midi(char *tok, char *s, int *incr, int *step, int *status, int *data)
return 0;
}
}
if (*p && incr && strchr("+-<>", *p)) {
// increment flag ("pb" and "cc" only)
if (*p && strchr("+-<>~", *p)) {
// incremental flag ("pb" and "cc" only)
if (strcmp(s, "pb") && strcmp(s, "cc")) return 0;
if ((*p == '<' || *p == '>') && strcmp(s, "cc")) return 0;
*incr = (*p == '-')?0:(*p == '+')?1:(*p == '<')?2:3;
if ((*p == '<' || *p == '>' || *p == '~') && strcmp(s, "cc")) return 0;
// Only the "~" form is permitted in output messages, and the other forms
// are only permitted on the lhs of a translation. XXXFIXME: This is
// confusing; incr is just a single bit in output messages, whereas it can
// take on various different values on the lhs.
if (lhs) {
if (*p == '~') return 0;
*incr = (*p == '-')?0:(*p == '+')?1:(*p == '<')?2:3;
} else {
if (*p != '~') return 0;
*incr = 1;
}
p++;
} else if (incr) {
*incr = -1;
} else {
*incr = lhs?-1:0;
}
// check for trailing garbage
if (*p) return 0;
if (strcmp(s, "ch") == 0) {
if (incr) return 0;
if (lhs) return 0;
// we return a bogus status of 0 here, along with the MIDI channel in the
// data byte; also check that the MIDI channel is in the proper range
if (m < 1 || m > 16) return 0;
@ -780,7 +792,7 @@ parse_midi(char *tok, char *s, int *incr, int *step, int *status, int *data)
int
start_translation(translation *tr, char *which_key)
{
int status, data, incr, step;
int status, data, step, incr;
char buf[100];
//printf("start_translation(%s)\n", which_key);
@ -796,7 +808,7 @@ start_translation(translation *tr, char *which_key)
regular_key_down = 0;
modifier_count = 0;
midi_channel = 0;
if (parse_midi(which_key, buf, &incr, &step, &status, &data)) {
if (parse_midi(which_key, buf, 1, &status, &data, &step, &incr)) {
int chan = status & 0x0f;
switch (status & 0xf0) {
case 0x90:
@ -823,7 +835,7 @@ start_translation(translation *tr, char *which_key)
is_keystroke = 1;
} else {
// cc (step up, down)
tr->is_incr = incr/2 != 0;
tr->is_incr[chan][data] = incr/2 != 0;
first_stroke = &(tr->ccs[chan][data][incr%2]);
}
break;
@ -905,7 +917,7 @@ add_release(int all_keys)
stroke *s = *press_first_stroke;
while (s) {
if (!s->keysym && s->dirty) {
append_midi(s->status, s->data, s->step);
append_midi(s->status, s->data, s->step, s->incr);
s->dirty = 0;
}
s = s->next;
@ -950,16 +962,16 @@ add_string(char *str)
void
add_midi(char *tok)
{
int status, data, step = 0;
int status, data, step, incr = 0;
char buf[100];
if (parse_midi(tok, buf, NULL, &step, &status, &data)) {
if (parse_midi(tok, buf, 0, &status, &data, &step, &incr)) {
if (status == 0) {
// 'ch' token; this doesn't actually generate any output, it just sets
// the default MIDI channel
midi_channel = data;
} else {
if ((status & 0xf0) != 0xe0 || step != 0)
append_midi(status, data, step);
append_midi(status, data, step, incr);
else
fprintf(stderr, "zero step size not permitted: %s\n", tok);
}