diff --git a/midizap.c b/midizap.c index d56c2bc..d781531 100644 --- a/midizap.c +++ b/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,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; } } } diff --git a/midizap.h b/midizap.h index c9748ee..28d73e8 100644 --- a/midizap.h +++ b/midizap.h @@ -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 diff --git a/readconfig.c b/readconfig.c index db85559..4c49358 100644 --- a/readconfig.c +++ b/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; - *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); }