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}; 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192};
void 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 if (!enable_jack_output) return; // MIDI output not enabled
uint8_t msg[3]; uint8_t msg[3];
@ -91,15 +91,20 @@ send_midi(int status, int data, int index, int incr, int step)
} }
break; break;
case 0xb0: case 0xb0:
if (incr) { if (dir) {
// increment (incr==1) or decrement (incr==-1) the current value, if (incr) {
// clamping it to the 0..127 data byte range // incremental controller, simply spit out a relative sign bit value
if (incr > 0) { msg[2] = dir>0?1:65;
if (ccvalue[chan][data] >= 127) return;
msg[2] = ++ccvalue[chan][data];
} else { } else {
if (ccvalue[chan][data] == 0) return; // increment (dir==1) or decrement (dir==-1) the current value,
msg[2] = --ccvalue[chan][data]; // 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) { } else if (!index) {
msg[2] = 127; 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 // 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)
int pbval = 0; int pbval = 0;
if (incr) { if (dir) {
if (!step) return; if (!step) return;
incr *= step; dir *= step;
if (incr > 0) { if (dir > 0) {
if (pbvalue[chan] >= 16383) return; if (pbvalue[chan] >= 16383) return;
pbvalue[chan] += incr; pbvalue[chan] += dir;
if (pbvalue[chan] > 16383) pbvalue[chan] = 16383; if (pbvalue[chan] > 16383) pbvalue[chan] = 16383;
} else { } else {
if (pbvalue[chan] == 0) return; if (pbvalue[chan] == 0) return;
pbvalue[chan] += incr; pbvalue[chan] += dir;
if (pbvalue[chan] < 0) pbvalue[chan] = 0; if (pbvalue[chan] < 0) pbvalue[chan] = 0;
} }
pbval = pbvalue[chan]; pbval = pbvalue[chan];
@ -147,7 +152,8 @@ send_midi(int status, int data, int index, int incr, int step)
} }
stroke * 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) { if (tr != NULL) {
switch (status) { switch (status) {
@ -156,13 +162,13 @@ fetch_stroke(translation *tr, int status, int chan, int data, int index, int inc
case 0xc0: case 0xc0:
return tr->pc[chan][data][index]; return tr->pc[chan][data][index];
case 0xb0: case 0xb0:
if (incr) if (dir)
return tr->ccs[chan][data][incr>0]; return tr->ccs[chan][data][dir>0];
else else
return tr->cc[chan][data][index]; return tr->cc[chan][data][index];
case 0xe0: case 0xe0:
if (incr) if (dir)
return tr->pbs[chan][incr>0]; return tr->pbs[chan][dir>0];
else else
return tr->pb[chan][index]; return tr->pb[chan][index];
default: 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" }; static char *note_names[] = { "C", "C#", "D", "Eb", "E", "F", "F#", "G", "G#", "A", "Bb", "B" };
void 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; 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) { if (s == NULL) {
tr = default_translation; 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) { 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); sprintf(name, "%s%d-%d", note_names[data % 12], data / 12, chan+1);
break; break;
case 0xb0: case 0xb0:
if (!incr) if (!dir)
suffix = ""; suffix = "";
else if (tr->is_incr) else if (tr->is_incr[chan][data])
suffix = (incr<0)?"<":">"; suffix = (dir<0)?"<":">";
else else
suffix = (incr<0)?"-":"+"; suffix = (dir<0)?"-":"+";
sprintf(name, "CC%d-%d%s", data, chan+1, suffix); sprintf(name, "CC%d-%d%s", data, chan+1, suffix);
break; break;
case 0xc0: case 0xc0:
sprintf(name, "PC%d-%d", data, chan+1); sprintf(name, "PC%d-%d", data, chan+1);
break; break;
case 0xe0: case 0xe0:
if (!incr) if (!dir)
suffix = ""; suffix = "";
else else
suffix = (incr<0)?"-":"+"; suffix = (dir<0)?"-":"+";
sprintf(name, "PB-%d%s", chan+1, suffix); sprintf(name, "PB-%d%s", chan+1, suffix);
break; break;
default: // this can't happen default: // this can't happen
break; break;
} }
print_stroke_sequence(name, incr?"":index?"U":"D", s); print_stroke_sequence(name, dir?"":index?"U":"D", s);
} }
while (s) { while (s) {
if (s->keysym) { if (s->keysym) {
send_key(s->keysym, s->press); send_key(s->keysym, s->press);
nkeys++; nkeys++;
} else { } 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; s = s->next;
} }
@ -346,10 +353,10 @@ int
check_incr(translation *tr, int chan, int data) check_incr(translation *tr, int chan, int data)
{ {
if (tr->ccs[chan][data][0] || tr->ccs[chan][data][1]) if (tr->ccs[chan][data][0] || tr->ccs[chan][data][1])
return tr->is_incr; return tr->is_incr[chan][data];
tr = default_translation; tr = default_translation;
if (tr->ccs[chan][data][0] || tr->ccs[chan][data][1]) if (tr->ccs[chan][data][0] || tr->ccs[chan][data][1])
return tr->is_incr; return tr->is_incr[chan][data];
return 0; return 0;
} }
@ -408,7 +415,7 @@ handle_event(uint8_t *msg)
} }
} }
if (check_incr(tr, chan, msg[1])) { 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 // representation (values above 0x40 indicate counter-clockwise
// rotation), which seems to be what most DAWs expect nowadays. // 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 // 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]) { } 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]) { while (inccvalue[chan][msg[1]] != msg[2]) {
send_strokes(tr, status, chan, msg[1], 0, incr); send_strokes(tr, status, chan, msg[1], 0, dir);
inccvalue[chan][msg[1]] += incr; inccvalue[chan][msg[1]] += dir;
} }
} }
break; break;
@ -450,15 +457,15 @@ handle_event(uint8_t *msg)
} }
} }
if (check_pbs(tr, chan) && inpbvalue[chan] - 8192 != bend) { if (check_pbs(tr, chan) && inpbvalue[chan] - 8192 != bend) {
int incr = inpbvalue[chan] - 8192 > bend ? -1 : 1; int dir = inpbvalue[chan] - 8192 > bend ? -1 : 1;
int step = tr->step[chan][incr>0]; int step = tr->step[chan][dir>0];
if (step) { if (step) {
while (inpbvalue[chan] - 8192 != bend) { while (inpbvalue[chan] - 8192 != bend) {
int d = abs(inpbvalue[chan] - 8192 - bend); int d = abs(inpbvalue[chan] - 8192 - bend);
if (d > step) d = step; if (d > step) d = step;
if (d < step) break; if (d < step) break;
send_strokes(tr, status, chan, 0, 0, incr); send_strokes(tr, status, chan, 0, 0, dir);
inpbvalue[chan] += incr*d; inpbvalue[chan] += dir*d;
} }
} }
} }

View File

@ -50,6 +50,9 @@ typedef struct _stroke {
// keysym == 0 => MIDI event // keysym == 0 => MIDI event
int status, data; // status and, if applicable, first data byte int status, data; // status and, if applicable, first data byte
int step; // step size for pitch bends (1 by default) 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 // the dirty bit indicates a MIDI event for which a release event still
// needs to be generated in key events // needs to be generated in key events
int dirty; int dirty;
@ -61,7 +64,7 @@ typedef struct _stroke {
typedef struct _translation { typedef struct _translation {
struct _translation *next; struct _translation *next;
char *name; char *name;
int is_default, is_incr; int is_default, is_incr[NUM_CHAN][NUM_KEYS];
regex_t regex; regex_t regex;
// XXXFIXME: This way of storing the translation tables is easy to // XXXFIXME: This way of storing the translation tables is easy to
// construct, but wastes quite a lot of memory (needs some 128 KB per // 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); printf("%s%d-%d ", note_names[s->data % 12], s->data / 12, channel);
break; break;
case 0xb0: case 0xb0:
printf("CC%d-%d ", s->data, channel); printf("CC%d-%d%s ", s->data, channel, s->incr?"~":"");
break; break;
case 0xc0: case 0xc0:
printf("PC%d-%d ", s->data, channel); printf("PC%d-%d ", s->data, channel);
@ -556,7 +556,7 @@ append_stroke(KeySym sym, int press)
s->next = NULL; s->next = NULL;
s->keysym = sym; s->keysym = sym;
s->press = press; 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) { if (*first_stroke) {
last_stroke->next = s; last_stroke->next = s;
} else { } else {
@ -566,7 +566,7 @@ append_stroke(KeySym sym, int press)
} }
void 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)); stroke *s = (stroke *)allocate(sizeof(stroke));
@ -576,6 +576,7 @@ append_midi(int status, int data, int step)
s->status = status; s->status = status;
s->data = data; s->data = data;
s->step = step; s->step = step;
s->incr = incr;
// if this is a keystroke event, for all messages but program change (which // 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 // has no "on" and "off" states), mark the event as "dirty" so that the
// corresponding "off" event gets added later to the "release" strokes // 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] tok ::= ( note | msg ) [number] [ "-" number] [incr]
note ::= ( "a" | ... | "g" ) [ "#" | "b" ] note ::= ( "a" | ... | "g" ) [ "#" | "b" ]
msg ::= "ch" | "pb" [ "[" number "]" ] | "pc" | "cc" msg ::= "ch" | "pb" [ "[" number "]" ] | "pc" | "cc"
incr ::= "-" | "+" | "<" | ">" incr ::= "-" | "+" | "<" | ">" | "~"
Numbers are always in decimal. The meaning of the first number depends on 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 the context (octave number for notes, the actual data byte for other
messages). If present, the suffix with the second number (after the dash) messages). If present, the suffix with the second number (after the dash)
denotes the MIDI channel, otherwise the default MIDI channel is used. Note 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 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* byte, but may be followed by a step size in brackets; and "ch" must *not*
have a channel number suffix on it. (In fact, "ch" is no real MIDI message occur as the first token and must *not* have a channel number suffix on
at all; it just sets the default MIDI channel for subsequent messages.) The it. (In fact, "ch" is no real MIDI message at all; it just sets the default
incr flag is only permitted in the first token of a translation, and only MIDI channel for subsequent messages in the output sequence.) The incr flag
in conjunction with "pb" or "cc", whereas "ch" must *not* occur as the is only permitted in conjunction with "pb" or "cc", and it takes on a
first token. */ different form in the first token of a translation. */
static int note_number(char c, char b, int k) 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 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; char *p = tok, *t;
int n, m = -1, k = midi_channel, l; 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; return 0;
} }
} }
if (*p && incr && strchr("+-<>", *p)) { if (*p && strchr("+-<>~", *p)) {
// increment flag ("pb" and "cc" only) // incremental flag ("pb" and "cc" only)
if (strcmp(s, "pb") && strcmp(s, "cc")) return 0; if (strcmp(s, "pb") && strcmp(s, "cc")) return 0;
if ((*p == '<' || *p == '>') && strcmp(s, "cc")) return 0; if ((*p == '<' || *p == '>' || *p == '~') && strcmp(s, "cc")) return 0;
*incr = (*p == '-')?0:(*p == '+')?1:(*p == '<')?2:3; // 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++; p++;
} else if (incr) { } else {
*incr = -1; *incr = lhs?-1:0;
} }
// check for trailing garbage // check for trailing garbage
if (*p) return 0; if (*p) return 0;
if (strcmp(s, "ch") == 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 // 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 // data byte; also check that the MIDI channel is in the proper range
if (m < 1 || m > 16) return 0; 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 int
start_translation(translation *tr, char *which_key) start_translation(translation *tr, char *which_key)
{ {
int status, data, incr, step; int status, data, step, incr;
char buf[100]; char buf[100];
//printf("start_translation(%s)\n", which_key); //printf("start_translation(%s)\n", which_key);
@ -796,7 +808,7 @@ start_translation(translation *tr, char *which_key)
regular_key_down = 0; regular_key_down = 0;
modifier_count = 0; modifier_count = 0;
midi_channel = 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; int chan = status & 0x0f;
switch (status & 0xf0) { switch (status & 0xf0) {
case 0x90: case 0x90:
@ -823,7 +835,7 @@ start_translation(translation *tr, char *which_key)
is_keystroke = 1; is_keystroke = 1;
} else { } else {
// cc (step up, down) // 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]); first_stroke = &(tr->ccs[chan][data][incr%2]);
} }
break; break;
@ -905,7 +917,7 @@ add_release(int all_keys)
stroke *s = *press_first_stroke; stroke *s = *press_first_stroke;
while (s) { while (s) {
if (!s->keysym && s->dirty) { 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->dirty = 0;
} }
s = s->next; s = s->next;
@ -950,16 +962,16 @@ add_string(char *str)
void void
add_midi(char *tok) add_midi(char *tok)
{ {
int status, data, step = 0; int status, data, step, incr = 0;
char buf[100]; 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) { if (status == 0) {
// 'ch' token; this doesn't actually generate any output, it just sets // 'ch' token; this doesn't actually generate any output, it just sets
// the default MIDI channel // the default MIDI channel
midi_channel = data; midi_channel = data;
} else { } else {
if ((status & 0xf0) != 0xe0 || step != 0) if ((status & 0xf0) != 0xe0 || step != 0)
append_midi(status, data, step); append_midi(status, data, step, incr);
else else
fprintf(stderr, "zero step size not permitted: %s\n", tok); fprintf(stderr, "zero step size not permitted: %s\n", tok);
} }