diff --git a/midizap.c b/midizap.c index 61bb6c0..d56c2bc 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) +send_midi(int status, int data, int index, int incr, int step) { if (!enable_jack_output) return; // MIDI output not enabled uint8_t msg[3]; @@ -112,13 +112,15 @@ send_midi(int status, int data, int index, int incr) // range (0..16383, with 8192 being the center value) int pbval = 0; if (incr) { + if (!step) return; + incr *= step; if (incr > 0) { if (pbvalue[chan] >= 16383) return; - pbvalue[chan] += 1170; + pbvalue[chan] += incr; if (pbvalue[chan] > 16383) pbvalue[chan] = 16383; } else { if (pbvalue[chan] == 0) return; - pbvalue[chan] -= 1170; + pbvalue[chan] += incr; if (pbvalue[chan] < 0) pbvalue[chan] = 0; } pbval = pbvalue[chan]; @@ -218,7 +220,7 @@ send_strokes(translation *tr, int status, int chan, int data, int index, int inc send_key(s->keysym, s->press); nkeys++; } else { - send_midi(s->status, s->data, index, incr); + send_midi(s->status, s->data, index, incr, s->step); } s = s->next; } @@ -336,6 +338,7 @@ static int16_t inpbvalue[16] = {8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192}; +static uint8_t notedown[16][128]; static uint8_t inccdown[16][128]; static uint8_t inpbdown[16]; @@ -380,10 +383,17 @@ handle_event(uint8_t *msg) send_strokes(tr, status, chan, msg[1], 1, 0); break; case 0x90: - if (msg[2]) - send_strokes(tr, status, chan, msg[1], 0, 0); - else - send_strokes(tr, status, chan, msg[1], 1, 0); + if (msg[2]) { + if (!notedown[chan][msg[1]]) { + send_strokes(tr, status, chan, msg[1], 0, 0); + notedown[chan][msg[1]] = 1; + } + } else { + if (notedown[chan][msg[1]]) { + send_strokes(tr, status, chan, msg[1], 1, 0); + notedown[chan][msg[1]] = 0; + } + } break; case 0xb0: if (msg[2]) { @@ -398,12 +408,24 @@ handle_event(uint8_t *msg) } } if (check_incr(tr, chan, msg[1])) { - // incremental controller a la MCU XXXTODO: maybe we should handle - // speed of control changes here? + // Incremental controller a la MCU. NB: This assumed 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 + // have to swap the actions for increment and decrement. XXXTODO: + // Maybe the encoding should be a configurable parameter? if (msg[2] < 64) { - send_strokes(tr, status, chan, msg[1], 0, 1); + int d = msg[2]; + while (d) { + send_strokes(tr, status, chan, msg[1], 0, 1); + d--; + } } else if (msg[2] > 64) { - send_strokes(tr, status, chan, msg[1], 0, -1); + int d = msg[2]-64; + while (d) { + send_strokes(tr, status, chan, msg[1], 0, -1); + d--; + } } } else if (inccvalue[chan][msg[1]] != msg[2]) { int incr = inccvalue[chan][msg[1]] > msg[2] ? -1 : 1; @@ -429,13 +451,15 @@ handle_event(uint8_t *msg) } if (check_pbs(tr, chan) && inpbvalue[chan] - 8192 != bend) { int incr = inpbvalue[chan] - 8192 > bend ? -1 : 1; - while (inpbvalue[chan] - 8192 != bend) { - int d = abs(inpbvalue[chan] - 8192 - bend); - // scaled to ca. 7 steps in either direction, like on output - if (d > 1170) d = 1170; - if (d < 1170) break; - send_strokes(tr, status, chan, 0, 0, incr); - inpbvalue[chan] += incr*d; + int step = tr->step[chan][incr>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; + } } } break; @@ -449,10 +473,10 @@ handle_event(uint8_t *msg) void help(char *progname) { - fprintf(stderr, "Usage: %s [-h] [-j] [-r rcfile] [-d[rsk]]\n", progname); + fprintf(stderr, "Usage: %s [-h] [-t] [-r rcfile] [-d[rskj]]\n", progname); fprintf(stderr, "-h print this message\n"); - fprintf(stderr, "-j enable Jack MIDI output\n"); - fprintf(stderr, "-r config file name (default: SHUTTLE_CONFIG_FILE variable or ~/.shuttlerc)\n"); + fprintf(stderr, "-t enable MIDI output\n"); + fprintf(stderr, "-r config file name (default: MIDIZAP_CONFIG_FILE variable or ~/.midizaprc)\n"); fprintf(stderr, "-d debug (r = regex, s = strokes, k = keys, j = jack; default: all)\n"); } @@ -469,12 +493,12 @@ main(int argc, char **argv) uint8_t msg[3]; int opt; - while ((opt = getopt(argc, argv, "hjd::r:")) != -1) { + while ((opt = getopt(argc, argv, "htd::r:")) != -1) { switch (opt) { case 'h': help(argv[0]); exit(0); - case 'j': + case 't': enable_jack_output = 1; break; case 'd': diff --git a/midizap.h b/midizap.h index 77ffdb4..bdd2488 100644 --- a/midizap.h +++ b/midizap.h @@ -49,6 +49,7 @@ typedef struct _stroke { int press; // zero -> release, non-zero -> press // 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 dirty bit indicates a MIDI event for which a release event still // needs to be generated in key events int dirty; @@ -70,6 +71,7 @@ typedef struct _translation { stroke *ccs[NUM_CHAN][NUM_KEYS][2]; stroke *pb[NUM_CHAN][2]; stroke *pbs[NUM_CHAN][2]; + int step[NUM_CHAN][2]; // step size for pitch bends (1 by default) } translation; extern translation *get_translation(char *win_title, char *win_class); diff --git a/readconfig.c b/readconfig.c index cf8fe86..db85559 100644 --- a/readconfig.c +++ b/readconfig.c @@ -556,7 +556,7 @@ append_stroke(KeySym sym, int press) s->next = NULL; s->keysym = sym; s->press = press; - s->status = s->data = s->dirty = 0; + s->status = s->data = s->step = 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) +append_midi(int status, int data, int step) { stroke *s = (stroke *)allocate(sizeof(stroke)); @@ -575,6 +575,7 @@ append_midi(int status, int data) s->press = 0; s->status = status; s->data = data; + s->step = step; // 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 @@ -660,7 +661,7 @@ re_press_temp_modifiers(void) tok ::= ( note | msg ) [number] [ "-" number] [incr] note ::= ( "a" | ... | "g" ) [ "#" | "b" ] - msg ::= "ch" | "pb" | "pc" | "cc" + msg ::= "ch" | "pb" [ "[" number "]" ] | "pc" | "cc" incr ::= "-" | "+" | "<" | ">" Numbers are always in decimal. The meaning of the first number depends on @@ -668,11 +669,12 @@ re_press_temp_modifiers(void) 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, 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" 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. */ static int note_number(char c, char b, int k) { @@ -688,12 +690,12 @@ static int note_number(char c, char b, int k) } int -parse_midi(char *tok, char *s, int *incr, int *status, int *data) +parse_midi(char *tok, char *s, int *incr, int *step, int *status, int *data) { char *p = tok, *t; - int n, m = -1, k = midi_channel; + int n, m = -1, k = midi_channel, l; s[0] = 0; - while (*p && !isdigit(*p) && !strchr("+-<>", *p)) p++; + while (*p && !isdigit(*p) && !strchr("+-<>[", *p)) p++; if (p == tok || p-tok > 10) return 0; // no valid token // the token by itself strncpy(s, tok, p-tok); s[p-tok] = 0; @@ -706,6 +708,20 @@ parse_midi(char *tok, char *s, int *incr, int *status, int *data) } else if (strcmp(s, "pb")) { return 0; } + // step size ('pb' only) + if (*p == '[') { + if (strcmp(s, "pb")) return 0; + if (sscanf(++p, "%d%n", &l, &n) == 1) { + p += n; + if (*p != ']') return 0; + p++; + *step = l; + } else { + return 0; + } + } else if (strcmp(s, "pb") == 0) { + *step = 1; + } if (p[0] == '-' && isdigit(p[1])) { // suffix with MIDI channel (not permitted with 'ch') if (strcmp(s, "ch") == 0) return 0; @@ -764,7 +780,7 @@ parse_midi(char *tok, char *s, int *incr, int *status, int *data) int start_translation(translation *tr, char *which_key) { - int status, data, incr; + int status, data, incr, step; char buf[100]; //printf("start_translation(%s)\n", which_key); @@ -780,7 +796,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, &status, &data)) { + if (parse_midi(which_key, buf, &incr, &step, &status, &data)) { int chan = status & 0x0f; switch (status & 0xf0) { case 0x90: @@ -819,13 +835,18 @@ start_translation(translation *tr, char *which_key) is_keystroke = 1; } else { // pb (step up, down) + if (step <= 0) { + fprintf(stderr, "zero or negative step size not permitted here: [%s]%s\n", current_translation, which_key); + return 1; + } first_stroke = &(tr->pbs[chan][incr]); + tr->step[chan][incr] = step; } break; default: // this can't happen fprintf(stderr, "bad message name: [%s]%s\n", current_translation, which_key); - return 1; + return 1; } } else { fprintf(stderr, "bad message name: [%s]%s\n", current_translation, which_key); @@ -884,7 +905,7 @@ add_release(int all_keys) stroke *s = *press_first_stroke; while (s) { if (!s->keysym && s->dirty) { - append_midi(s->status, s->data); + append_midi(s->status, s->data, s->step); s->dirty = 0; } s = s->next; @@ -929,15 +950,18 @@ add_string(char *str) void add_midi(char *tok) { - int status, data; + int status, data, step = 0; char buf[100]; - if (parse_midi(tok, buf, NULL, &status, &data)) { + if (parse_midi(tok, buf, NULL, &step, &status, &data)) { if (status == 0) { // 'ch' token; this doesn't actually generate any output, it just sets // the default MIDI channel midi_channel = data; } else { - append_midi(status, data); + if ((status & 0xf0) != 0xe0 || step != 0) + append_midi(status, data, step); + else + fprintf(stderr, "zero step size not permitted: %s\n", tok); } } else { // inspect the token that was actually recognized (if any) to give some