Add support for incremental (endless rotary) controls, bugfixes.
This commit is contained in:
parent
03e67992f9
commit
e1468bd401
75
midizap.c
75
midizap.c
|
@ -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,16 +91,21 @@ send_midi(int status, int data, int index, int incr, int step)
|
|||
}
|
||||
break;
|
||||
case 0xb0:
|
||||
if (dir) {
|
||||
if (incr) {
|
||||
// increment (incr==1) or decrement (incr==-1) the current value,
|
||||
// incremental controller, simply spit out a relative sign bit value
|
||||
msg[2] = dir>0?1:65;
|
||||
} else {
|
||||
// increment (dir==1) or decrement (dir==-1) the current value,
|
||||
// clamping it to the 0..127 data byte range
|
||||
if (incr > 0) {
|
||||
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;
|
||||
} else {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
60
readconfig.c
60
readconfig.c
|
@ -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;
|
||||
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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue