From 6f0bec4b74e19afb781abded6fb1f7d36614366f Mon Sep 17 00:00:00 2001 From: Albert Graef Date: Sat, 11 Aug 2018 21:04:09 +0200 Subject: [PATCH] Refactoring and bugfixes. Also added the capability to add another pair of MIDI ports (-o2). --- jackdriver.c | 255 ++++++++++++++++++++-------------- jackdriver.h | 16 +-- midizap.c | 385 +++++++++++++++++++++++++++++++-------------------- midizap.h | 4 + readconfig.c | 92 ++++++++---- 5 files changed, 465 insertions(+), 287 deletions(-) diff --git a/jackdriver.c b/jackdriver.c index aec0e39..a42f979 100644 --- a/jackdriver.c +++ b/jackdriver.c @@ -140,16 +140,19 @@ queue_message(jack_ringbuffer_t* ringbuffer, MidiMessage *ev) void process_midi_input(JACK_SEQ* seq,jack_nframes_t nframes) { + int k; + + for (k = 0; k < seq->n_in; k++) { + int read, events, i; - void *port_buffer; MidiMessage rev; jack_midi_event_t event; - port_buffer = jack_port_get_buffer(seq->input_port, nframes); + void *port_buffer = jack_port_get_buffer(seq->input_port[k], nframes); if (port_buffer == NULL) { - fprintf(stderr, "jack_port_get_buffer failed, cannot receive anything.\n"); - return; + fprintf(stderr, "jack_port_get_buffer failed, cannot receive anything.\n"); + return; } #ifdef JACK_MIDI_NEEDS_NFRAMES @@ -162,47 +165,49 @@ process_midi_input(JACK_SEQ* seq,jack_nframes_t nframes) { #ifdef JACK_MIDI_NEEDS_NFRAMES - read = jack_midi_event_get(&event, port_buffer, i, nframes); + read = jack_midi_event_get(&event, port_buffer, i, nframes); #else - read = jack_midi_event_get(&event, port_buffer, i); + read = jack_midi_event_get(&event, port_buffer, i); #endif - if (!read) - { - //successful event get + if (!read) + { + //successful event get - if (event.size <= 3 && event.size >=1) - { - //not sysex or something - - //PUSH ONTO CIRCULAR BUFFER - //not sure if its a true copy onto buffer, if not this won't work - rev.len = event.size; - rev.time = event.time; - memcpy(rev.data, event.buffer, rev.len); - queue_message(seq->ringbuffer_in,&rev); - } - } + if (event.size <= 3 && event.size >= 1) + { + //not sysex or something + //PUSH ONTO CIRCULAR BUFFER + //not sure if its a true copy onto buffer, if not this won't work + rev.len = event.size; + rev.time = event.time; + memcpy(rev.data, event.buffer, rev.len); + queue_message(seq->ringbuffer_in[k],&rev); + } + } } + } } void process_midi_output(JACK_SEQ* seq,jack_nframes_t nframes) { + jack_nframes_t last_frame_time = jack_last_frame_time(seq->jack_client); + int k; + + for (k = 0; k < seq->n_out; k++) { + int read, t; uint8_t *buffer; void *port_buffer; - jack_nframes_t last_frame_time; MidiMessage ev; - last_frame_time = jack_last_frame_time(seq->jack_client); - - port_buffer = jack_port_get_buffer(seq->output_port, nframes); + port_buffer = jack_port_get_buffer(seq->output_port[k], nframes); if (port_buffer == NULL) { - fprintf(stderr, "jack_port_get_buffer failed, cannot send anything.\n"); - return; + fprintf(stderr, "jack_port_get_buffer failed, cannot send anything.\n"); + return; } #ifdef JACK_MIDI_NEEDS_NFRAMES @@ -211,44 +216,45 @@ process_midi_output(JACK_SEQ* seq,jack_nframes_t nframes) jack_midi_clear_buffer(port_buffer); #endif - while (jack_ringbuffer_read_space(seq->ringbuffer_out)) + while (jack_ringbuffer_read_space(seq->ringbuffer_out[k])) { - read = jack_ringbuffer_peek(seq->ringbuffer_out, (char *)&ev, sizeof(ev)); + read = jack_ringbuffer_peek(seq->ringbuffer_out[k], (char *)&ev, sizeof(ev)); - if (read != sizeof(ev)) - { - //warn_from_jack_thread_context("Short read from the ringbuffer, possible note loss."); - jack_ringbuffer_read_advance(seq->ringbuffer_out, read); - continue; - } + if (read != sizeof(ev)) + { + //warn_from_jack_thread_context("Short read from the ringbuffer, possible note loss."); + jack_ringbuffer_read_advance(seq->ringbuffer_out[k], read); + continue; + } - t = ev.time + nframes - last_frame_time; + t = ev.time + nframes - last_frame_time; - /* If computed time is too much into the future, we'll need - to send it later. */ - if (t >= (int)nframes) - break; + /* If computed time is too much into the future, we'll need + to send it later. */ + if (t >= (int)nframes) + break; - /* If computed time is < 0, we missed a cycle because of xrun. */ - if (t < 0) - t = 0; + /* If computed time is < 0, we missed a cycle because of xrun. */ + if (t < 0) + t = 0; - jack_ringbuffer_read_advance(seq->ringbuffer_out, sizeof(ev)); + jack_ringbuffer_read_advance(seq->ringbuffer_out[k], sizeof(ev)); #ifdef JACK_MIDI_NEEDS_NFRAMES - buffer = jack_midi_event_reserve(port_buffer, t, ev.len, nframes); + buffer = jack_midi_event_reserve(port_buffer, t, ev.len, nframes); #else - buffer = jack_midi_event_reserve(port_buffer, t, ev.len); + buffer = jack_midi_event_reserve(port_buffer, t, ev.len); #endif - if (buffer == NULL) - { - //warn_from_jack_thread_context("jack_midi_event_reserve failed, NOTE LOST."); - break; - } + if (buffer == NULL) + { + //warn_from_jack_thread_context("jack_midi_event_reserve failed, NOTE LOST."); + break; + } - memcpy(buffer, ev.data, ev.len); + memcpy(buffer, ev.data, ev.len); } + } } int @@ -260,9 +266,9 @@ process_callback(jack_nframes_t nframes, void *seqq) fprintf(stderr, "Had to wait too long for JACK callback; scheduling problem?\n"); #endif - if(seq->usein) + if(seq->n_in) process_midi_input( seq,nframes ); - if(seq->useout) + if(seq->n_out) process_midi_output( seq,nframes ); #ifdef MEASURE_TIME @@ -276,7 +282,7 @@ process_callback(jack_nframes_t nframes, void *seqq) /////////////////////////////////////////////// //these functions are executed in other threads /////////////////////////////////////////////// -void queue_midi(void* seqq, uint8_t msg[]) +void queue_midi(void* seqq, uint8_t msg[], uint8_t port_no) { MidiMessage ev; JACK_SEQ* seq = (JACK_SEQ*)seqq; @@ -330,34 +336,37 @@ void queue_midi(void* seqq, uint8_t msg[]) ev.data[2] = msg[2]; ev.time = jack_frame_time(seq->jack_client); - queue_message(seq->ringbuffer_out,&ev); + queue_message(seq->ringbuffer_out[port_no],&ev); } -int pop_midi(void* seqq, uint8_t msg[]) +int pop_midi(void* seqq, uint8_t msg[], uint8_t *port_no) { - int read; - MidiMessage ev; - JACK_SEQ* seq = (JACK_SEQ*)seqq; + int read, k; + MidiMessage ev; + JACK_SEQ* seq = (JACK_SEQ*)seqq; - if (jack_ringbuffer_read_space(seq->ringbuffer_in)) + for (k = 0; k < seq->n_in; k++) { + + if (jack_ringbuffer_read_space(seq->ringbuffer_in[k])) { - read = jack_ringbuffer_peek(seq->ringbuffer_in, (char *)&ev, sizeof(ev)); + read = jack_ringbuffer_peek(seq->ringbuffer_in[k], (char *)&ev, sizeof(ev)); - if (read != sizeof(ev)) - { - //warn_from_jack_thread_context("Short read from the ringbuffer, possible note loss."); - jack_ringbuffer_read_advance(seq->ringbuffer_in, read); - return -1; - } + if (read != sizeof(ev)) + { + //warn_from_jack_thread_context("Short read from the ringbuffer, possible note loss."); + jack_ringbuffer_read_advance(seq->ringbuffer_in[k], read); + return -1; + } - jack_ringbuffer_read_advance(seq->ringbuffer_in, sizeof(ev)); + jack_ringbuffer_read_advance(seq->ringbuffer_in[k], sizeof(ev)); - memcpy(msg,ev.data,ev.len); + memcpy(msg,ev.data,ev.len); + *port_no = k; - return ev.len; + return ev.len; } - else - return 0; + } + return 0; } //////////////////////////////// @@ -366,7 +375,8 @@ int pop_midi(void* seqq, uint8_t msg[]) int init_jack(JACK_SEQ* seq, uint8_t verbose) { - int err; + int err, k; + char portname[100]; if(verbose)printf("opening client...\n"); seq->jack_client = jack_client_open("midizap", JackNullOption, NULL); @@ -386,53 +396,85 @@ init_jack(JACK_SEQ* seq, uint8_t verbose) } - if(seq->usein) + seq->ringbuffer_in = NULL; + seq->input_port = NULL; + if(seq->n_in) { if(verbose)printf("initializing JACK input: \ncreating ringbuffer...\n"); - seq->ringbuffer_in = jack_ringbuffer_create(RINGBUFFER_SIZE); - - if (seq->ringbuffer_in == NULL) + seq->ringbuffer_in = calloc(seq->n_in, sizeof(jack_ringbuffer_t*)); + seq->input_port = calloc(seq->n_in, sizeof(jack_port_t*)); + if (!seq->ringbuffer_in || !seq->input_port) { - fprintf(stderr, "Cannot create JACK ringbuffer.\n"); + fprintf(stderr, "Cannot allocate memory for ports and ringbuffers.\n"); return 0; } - jack_ringbuffer_mlock(seq->ringbuffer_in); + for (k = 0; k < seq->n_in; k++) { + seq->ringbuffer_in[k] = jack_ringbuffer_create(RINGBUFFER_SIZE); - seq->input_port = jack_port_register(seq->jack_client, "midi_in", - JACK_DEFAULT_MIDI_TYPE, - JackPortIsInput, 0); + if (seq->ringbuffer_in[k] == NULL) + { + fprintf(stderr, "Cannot create JACK ringbuffer.\n"); + return 0; + } - if (seq->input_port == NULL) - { - fprintf(stderr, "Could not register JACK port.\n"); - return 0; + jack_ringbuffer_mlock(seq->ringbuffer_in[k]); + + if (k) + sprintf(portname, "midi_in%d", k+1); + else + strcpy(portname, "midi_in"); + seq->input_port[k] = jack_port_register(seq->jack_client, portname, + JACK_DEFAULT_MIDI_TYPE, + JackPortIsInput, 0); + + if (seq->input_port[k] == NULL) + { + fprintf(stderr, "Could not register JACK port.\n"); + return 0; + } } } - if(seq->useout) + seq->ringbuffer_out = NULL; + seq->output_port = NULL; + if(seq->n_out) { if(verbose)printf("initializing JACK output: \ncreating ringbuffer...\n"); - seq->ringbuffer_out = jack_ringbuffer_create(RINGBUFFER_SIZE); - - if (seq->ringbuffer_out == NULL) + seq->ringbuffer_out = calloc(seq->n_out, sizeof(jack_ringbuffer_t*)); + seq->output_port = calloc(seq->n_out, sizeof(jack_port_t*)); + if (!seq->ringbuffer_out || !seq->output_port) { - fprintf(stderr, "Cannot create JACK ringbuffer.\n"); - return 0; + fprintf(stderr, "Cannot allocate memory for ports and ringbuffers.\n"); + return 0; } - jack_ringbuffer_mlock(seq->ringbuffer_out); + for (k = 0; k < seq->n_out; k++) { + seq->ringbuffer_out[k] = jack_ringbuffer_create(RINGBUFFER_SIZE); - seq->output_port = jack_port_register(seq->jack_client, "midi_out", - JACK_DEFAULT_MIDI_TYPE, - JackPortIsOutput, 0); + if (seq->ringbuffer_out[k] == NULL) + { + fprintf(stderr, "Cannot create JACK ringbuffer.\n"); + return 0; + } - if (seq->output_port == NULL) - { - fprintf(stderr, "Could not register JACK port.\n"); - return 0; + jack_ringbuffer_mlock(seq->ringbuffer_out[k]); + + if (k) + sprintf(portname, "midi_out%d", k+1); + else + strcpy(portname, "midi_out"); + seq->output_port[k] = jack_port_register(seq->jack_client, portname, + JACK_DEFAULT_MIDI_TYPE, + JackPortIsOutput, 0); + + if (seq->output_port[k] == NULL) + { + fprintf(stderr, "Could not register JACK port.\n"); + return 0; + } } } @@ -446,6 +488,13 @@ init_jack(JACK_SEQ* seq, uint8_t verbose) void close_jack(JACK_SEQ* seq) { - if(seq->useout)jack_ringbuffer_free(seq->ringbuffer_out); - if(seq->usein)jack_ringbuffer_free(seq->ringbuffer_in); + int k; + if(seq->n_out) { + for (k = 0; k < seq->n_out; k++) + jack_ringbuffer_free(seq->ringbuffer_out[k]); + } + if(seq->n_in) { + for (k = 0; k < seq->n_in; k++) + jack_ringbuffer_free(seq->ringbuffer_in[k]); + } } diff --git a/jackdriver.h b/jackdriver.h index 7e66ca8..0278fd2 100644 --- a/jackdriver.h +++ b/jackdriver.h @@ -5,18 +5,18 @@ typedef struct _jseq { - jack_ringbuffer_t *ringbuffer_out; - jack_ringbuffer_t *ringbuffer_in; + jack_ringbuffer_t **ringbuffer_out; + jack_ringbuffer_t **ringbuffer_in; jack_client_t *jack_client; - jack_port_t *output_port; - jack_port_t *input_port; - uint8_t usein; - uint8_t useout; + jack_port_t **output_port; + jack_port_t **input_port; + uint8_t n_in; + uint8_t n_out; } JACK_SEQ; int init_jack(JACK_SEQ* seq, uint8_t verbose); void close_jack(JACK_SEQ* seq); -void queue_midi(void* seqq, uint8_t msg[]); -int pop_midi(void* seqq, uint8_t msg[]); +void queue_midi(void* seqq, uint8_t msg[], uint8_t port_no); +int pop_midi(void* seqq, uint8_t msg[], uint8_t *port_no); #endif diff --git a/midizap.c b/midizap.c index d078eb7..6df3e1f 100644 --- a/midizap.c +++ b/midizap.c @@ -20,13 +20,6 @@ typedef struct input_event EV; -extern int debug_regex; -extern translation *default_translation; - -unsigned short jogvalue = 0xffff; -int shuttlevalue = 0xffff; -struct timeval last_shuttle; -int need_synthetic_shuttle; Display *display; JACK_SEQ seq; @@ -75,7 +68,7 @@ static int16_t pbvalue[16] = 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192}; void -send_midi(int status, int data, int step, int incr, int index, int dir) +send_midi(uint8_t portno, 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]; @@ -153,14 +146,14 @@ send_midi(int status, int data, int step, int incr, int index, int dir) default: return; } - queue_midi(&seq, msg); + queue_midi(&seq, msg, portno); } stroke * -fetch_stroke(translation *tr, int status, int chan, int data, +fetch_stroke(translation *tr, uint8_t portno, int status, int chan, int data, int index, int dir) { - if (tr != NULL) { + if (tr && tr->portno == portno) { switch (status) { case 0x90: return tr->note[chan][data][index]; @@ -183,18 +176,54 @@ fetch_stroke(translation *tr, int status, int chan, int data, return NULL; } +#define MAX_WINNAME_SIZE 1024 +static char last_window_name[MAX_WINNAME_SIZE]; +static char last_window_class[MAX_WINNAME_SIZE]; +static Window last_focused_window = 0; +static translation *last_window_translation = NULL, *last_translation = NULL; +static int have_window = 0; + +void reload_callback(void) +{ + last_focused_window = 0; + last_window_translation = last_translation = NULL; + have_window = 0; +} + 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, +send_strokes(translation *tr, uint8_t portno, int status, int chan, int data, int index, int dir) { int nkeys = 0; - stroke *s = fetch_stroke(tr, status, chan, data, index, dir); + stroke *s = fetch_stroke(tr, portno, status, chan, data, index, dir); - if (s == NULL) { + if (!s && enable_jack_output) { + // fall back to default MIDI translation + tr = default_midi_translation[portno]; + s = fetch_stroke(tr, portno, status, chan, data, index, dir); + // Ignore all MIDI input on the second port if no translation was found in + // the [MIDI2] section (or the section is missing altogether). + if (portno && !s) return; + } + + if (!s) { + // fall back to the default translation tr = default_translation; - s = fetch_stroke(tr, status, chan, data, index, dir); + s = fetch_stroke(tr, portno, status, chan, data, index, dir); + } + + if (s && debug_regex && (!have_window || tr != last_translation)) { + last_translation = tr; + have_window = 1; + if (tr != NULL) { + printf("translation: %s for %s (class %s)\n", + tr->name, last_window_name, last_window_class); + } else { + printf("no translation found for %s (class %s)\n", + last_window_name, last_window_class); + } } if (debug_keys && s) { @@ -242,7 +271,7 @@ send_strokes(translation *tr, int status, int chan, int data, send_key(s->keysym, s->press); nkeys++; } else { - send_midi(s->status, s->data, s->step, s->incr, index, dir); + send_midi(portno, s->status, s->data, s->step, s->incr, index, dir); } s = s->next; } @@ -316,34 +345,29 @@ walk_window_tree(Window win, char **window_class) return NULL; } -static Window last_focused_window = 0; -static translation *last_window_translation = NULL; - translation * get_focused_window_translation() { Window focus; int revert_to; char *window_name = NULL, *window_class = NULL; - char *name; XGetInputFocus(display, &focus, &revert_to); if (focus != last_focused_window) { last_focused_window = focus; window_name = walk_window_tree(focus, &window_class); - if (window_name == NULL) { - name = "-- Unlabeled Window --"; + last_window_translation = get_translation(window_name, window_class); + if (window_name && *window_name) { + strncpy(last_window_name, window_name, MAX_WINNAME_SIZE); + last_window_name[MAX_WINNAME_SIZE-1] = 0; } else { - name = window_name; + strcpy(last_window_name, "Unnamed");; } - last_window_translation = get_translation(name, window_class); - if (debug_regex) { - if (last_window_translation != NULL) { - printf("translation: %s for %s (class %s)\n", - last_window_translation->name, name, window_class); - } else { - printf("no translation found for %s (class %s)\n", name, window_class); - } + if (window_class && *window_class) { + strncpy(last_window_class, window_class, MAX_WINNAME_SIZE); + last_window_class[MAX_WINNAME_SIZE-1] = 0; + } else { + strcpy(last_window_name, "Unnamed");; } if (window_name != NULL) { XFree(window_name); @@ -355,155 +379,201 @@ get_focused_window_translation() return last_window_translation; } -static int8_t inccvalue[16][128]; -static int16_t inpbvalue[16] = - {8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, - 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192}; +static int8_t inccvalue[2][16][128]; +static int16_t inpbvalue[2][16] = + {{8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, + 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192}, + {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]; +static uint8_t notedown[2][16][128]; +static uint8_t inccdown[2][16][128]; +static uint8_t inpbdown[2][16]; int -check_incr(translation *tr, int chan, int data) +check_incr(translation *tr, uint8_t portno, int chan, int data) { - if (tr->ccs[chan][data][0] || tr->ccs[chan][data][1]) + if (tr && tr->portno == portno && + (tr->ccs[chan][data][0] || tr->ccs[chan][data][1])) + return tr->is_incr[chan][data]; + tr = default_midi_translation[portno]; + if (tr && tr->portno == portno && + (tr->ccs[chan][data][0] || tr->ccs[chan][data][1])) return tr->is_incr[chan][data]; tr = default_translation; - if (tr->ccs[chan][data][0] || tr->ccs[chan][data][1]) + if (tr && tr->portno == portno && + (tr->ccs[chan][data][0] || tr->ccs[chan][data][1])) return tr->is_incr[chan][data]; return 0; } int -check_pbs(translation *tr, int chan) +get_cc_step(translation *tr, uint8_t portno, int chan, int data, int dir) { - if (tr->pbs[chan][0] || tr->pbs[chan][1]) + if (tr && tr->portno == portno && + tr->ccs[chan][data][dir>0]) + return tr->cc_step[chan][data][dir>0]; + tr = default_midi_translation[portno]; + if (tr && tr->portno == portno && + tr->ccs[chan][data][dir>0]) + return tr->cc_step[chan][data][dir>0]; + tr = default_translation; + if (tr && tr->portno == portno && + tr->ccs[chan][data][dir>0]) + return tr->cc_step[chan][data][dir>0]; + return 1; +} + +int +check_pbs(translation *tr, uint8_t portno, int chan) +{ + if (tr && tr->portno == portno && + (tr->pbs[chan][0] || tr->pbs[chan][1])) + return 1; + tr = default_midi_translation[portno]; + if (tr && tr->portno == portno && + (tr->pbs[chan][0] || tr->pbs[chan][1])) return 1; tr = default_translation; - if (tr->pbs[chan][0] || tr->pbs[chan][1]) + if (tr && tr->portno == portno && + (tr->pbs[chan][0] || tr->pbs[chan][1])) return 1; return 0; } +int +get_pb_step(translation *tr, uint8_t portno, int chan, int dir) +{ + if (tr && tr->portno == portno && + tr->pbs[chan][dir>0]) + return tr->pb_step[chan][dir>0]; + tr = default_midi_translation[portno]; + if (tr && tr->portno == portno && + tr->pbs[chan][dir>0]) + return tr->pb_step[chan][dir>0]; + tr = default_translation; + if (tr && tr->portno == portno && + tr->pbs[chan][dir>0]) + return tr->pb_step[chan][dir>0]; + return 1; +} + void -handle_event(uint8_t *msg) +handle_event(uint8_t *msg, uint8_t portno) { translation *tr = get_focused_window_translation(); - //fprintf(stderr, "midi: %0x %0x %0x\n", msg[0], msg[1], msg[2]); - if (tr != NULL) { - int status = msg[0] & 0xf0, chan = msg[0] & 0x0f; - if (status == 0x80) { - status = 0x90; - msg[0] = status | chan; - msg[2] = 0; + //fprintf(stderr, "midi [%d]: %0x %0x %0x\n", portno, msg[0], msg[1], msg[2]); + int status = msg[0] & 0xf0, chan = msg[0] & 0x0f; + if (status == 0x80) { + status = 0x90; + msg[0] = status | chan; + msg[2] = 0; + } + switch (status) { + case 0xc0: + send_strokes(tr, portno, status, chan, msg[1], 0, 0); + send_strokes(tr, portno, status, chan, msg[1], 1, 0); + break; + case 0x90: + if (msg[2]) { + if (!notedown[portno][chan][msg[1]]) { + send_strokes(tr, portno, status, chan, msg[1], 0, 0); + notedown[portno][chan][msg[1]] = 1; + } + } else { + if (notedown[portno][chan][msg[1]]) { + send_strokes(tr, portno, status, chan, msg[1], 1, 0); + notedown[portno][chan][msg[1]] = 0; + } } - switch (status) { - case 0xc0: - send_strokes(tr, status, chan, msg[1], 0, 0); - send_strokes(tr, status, chan, msg[1], 1, 0); - break; - case 0x90: - 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]) { + if (!inccdown[portno][chan][msg[1]]) { + send_strokes(tr, portno, status, chan, msg[1], 0, 0); + inccdown[portno][chan][msg[1]] = 1; } - break; - case 0xb0: - if (msg[2]) { - if (!inccdown[chan][msg[1]]) { - send_strokes(tr, status, chan, msg[1], 0, 0); - inccdown[chan][msg[1]] = 1; - } - } else { - if (inccdown[chan][msg[1]]) { - send_strokes(tr, status, chan, msg[1], 1, 0); - inccdown[chan][msg[1]] = 0; - } + } else { + if (inccdown[portno][chan][msg[1]]) { + send_strokes(tr, portno, status, chan, msg[1], 1, 0); + inccdown[portno][chan][msg[1]] = 0; } - if (check_incr(tr, chan, msg[1])) { - // 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 - // have to swap the actions for increment and decrement. XXXTODO: - // Maybe the encoding should be a configurable parameter? - if (msg[2] < 64) { - int d = msg[2]; - while (d) { - send_strokes(tr, status, chan, msg[1], 0, 1); - d--; - } - } else if (msg[2] > 64) { - 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 dir = inccvalue[chan][msg[1]] > msg[2] ? -1 : 1; - int step = tr->cc_step[chan][msg[1]][dir>0]; - if (step) { - while (inccvalue[chan][msg[1]] != msg[2]) { - int d = abs(inccvalue[chan][msg[1]] - msg[2]); - if (d > step) d = step; - if (d < step) break; - send_strokes(tr, status, chan, msg[1], 0, dir); - inccvalue[chan][msg[1]] += dir*d; - } - } - } - break; - case 0xe0: { - int bend = ((msg[2] << 7) | msg[1]) - 8192; - //fprintf(stderr, "pb %d\n", bend); - if (bend) { - if (!inpbdown[chan]) { - send_strokes(tr, status, chan, 0, 0, 0); - inpbdown[chan] = 1; - } - } else { - if (inpbdown[chan]) { - send_strokes(tr, status, chan, 0, 1, 0); - inpbdown[chan] = 0; - } - } - if (check_pbs(tr, chan) && inpbvalue[chan] - 8192 != bend) { - int dir = inpbvalue[chan] - 8192 > bend ? -1 : 1; - int step = tr->pb_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, dir); - inpbvalue[chan] += dir*d; - } - } - } - break; } - default: - // ignore everything else - break; + if (check_incr(tr, portno, chan, msg[1])) { + // 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 + // have to swap the actions for increment and decrement. XXXTODO: + // Maybe the encoding should be a configurable parameter? + if (msg[2] < 64) { + int d = msg[2]; + while (d) { + send_strokes(tr, portno, status, chan, msg[1], 0, 1); + d--; + } + } else if (msg[2] > 64) { + int d = msg[2]-64; + while (d) { + send_strokes(tr, portno, status, chan, msg[1], 0, -1); + d--; + } + } + } else if (inccvalue[portno][chan][msg[1]] != msg[2]) { + int dir = inccvalue[portno][chan][msg[1]] > msg[2] ? -1 : 1; + int step = get_cc_step(tr, portno, chan, msg[1], dir); + if (step) { + while (inccvalue[portno][chan][msg[1]] != msg[2]) { + int d = abs(inccvalue[portno][chan][msg[1]] - msg[2]); + if (d > step) d = step; + if (d < step) break; + send_strokes(tr, portno, status, chan, msg[1], 0, dir); + inccvalue[portno][chan][msg[1]] += dir*d; + } + } } + break; + case 0xe0: { + int bend = ((msg[2] << 7) | msg[1]) - 8192; + //fprintf(stderr, "pb %d\n", bend); + if (bend) { + if (!inpbdown[portno][chan]) { + send_strokes(tr, portno, status, chan, 0, 0, 0); + inpbdown[portno][chan] = 1; + } + } else { + if (inpbdown[portno][chan]) { + send_strokes(tr, portno, status, chan, 0, 1, 0); + inpbdown[portno][chan] = 0; + } + } + if (check_pbs(tr, portno, chan) && inpbvalue[portno][chan] - 8192 != bend) { + int dir = inpbvalue[portno][chan] - 8192 > bend ? -1 : 1; + int step = get_pb_step(tr, portno, chan, dir); + if (step) { + while (inpbvalue[portno][chan] - 8192 != bend) { + int d = abs(inpbvalue[portno][chan] - 8192 - bend); + if (d > step) d = step; + if (d < step) break; + send_strokes(tr, portno, status, chan, 0, 0, dir); + inpbvalue[portno][chan] += dir*d; + } + } + } + break; + } + default: + // ignore everything else + break; } } void help(char *progname) { - fprintf(stderr, "Usage: %s [-h] [-o] [-r rcfile] [-d[rskj]]\n", progname); + fprintf(stderr, "Usage: %s [-h] [-o[2]] [-r rcfile] [-d[rskj]]\n", progname); fprintf(stderr, "-h print this message\n"); - fprintf(stderr, "-o enable MIDI output\n"); + fprintf(stderr, "-o enable MIDI output (add 2 for a second pair of ports)\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"); } @@ -521,19 +591,31 @@ void quitter() #define CONF_FREQ 1 #define MAX_COUNT (1000000/CONF_FREQ/POLL_INTERVAL) +int n_ports = 1; + int main(int argc, char **argv) { uint8_t msg[3]; int opt, count = 0; - while ((opt = getopt(argc, argv, "hod::r:")) != -1) { + while ((opt = getopt(argc, argv, "ho::d::r:")) != -1) { switch (opt) { case 'h': help(argv[0]); exit(0); case 'o': enable_jack_output = 1; + if (optarg && *optarg) { + const char *a = optarg; + if (*a == '2') { + n_ports = 2; + } else if (*a && *a != '1') { + fprintf(stderr, "%s: wrong port number (-o), must be 1 or 2\n", argv[0]); + fprintf(stderr, "Try -h for help.\n"); + exit(1); + } + } break; case 'd': if (optarg && *optarg) { @@ -580,7 +662,7 @@ main(int argc, char **argv) initdisplay(); - seq.usein = 1; seq.useout = enable_jack_output; + seq.n_in = n_ports; seq.n_out = enable_jack_output?n_ports:0; if (!init_jack(&seq, debug_jack)) { exit(1); } @@ -589,8 +671,9 @@ main(int argc, char **argv) // force the config file to be loaded initially count = MAX_COUNT; while (!quit) { - while (pop_midi(&seq, msg)) { - handle_event(msg); + uint8_t portno; + while (pop_midi(&seq, msg, &portno)) { + handle_event(msg, portno); count = 0; } usleep(POLL_INTERVAL); diff --git a/midizap.h b/midizap.h index 91549aa..0e6ee32 100644 --- a/midizap.h +++ b/midizap.h @@ -67,6 +67,7 @@ typedef struct _translation { char *name; int is_default; regex_t regex; + uint8_t portno; // XXXFIXME: This way of storing the translation tables is easy to // construct, but wastes quite a lot of memory (needs some 128 KB per // translation section even if most of the entries are NULL pointers). We @@ -83,9 +84,12 @@ typedef struct _translation { int pb_step[NUM_CHAN][2]; } translation; +extern void reload_callback(void); extern int read_config_file(void); extern translation *get_translation(char *win_title, char *win_class); extern void print_stroke_sequence(char *name, char *up_or_down, stroke *s); +extern translation *default_translation, *default_midi_translation[2]; extern int debug_regex, debug_strokes, debug_keys; extern int default_debug_regex, default_debug_strokes, default_debug_keys; extern char *config_file_name; +extern int enable_jack_output; diff --git a/readconfig.c b/readconfig.c index fc82547..7a0dc39 100644 --- a/readconfig.c +++ b/readconfig.c @@ -33,15 +33,39 @@ handle most common use cases. (In any case, adding more message types should be a piece of cake.) + MIDI messages are on channel 1 by default; a suffix of the form + -<1..16> can be used to specify a different MIDI channel. E.g., C3-10 + denotes note C3 on MIDI channel 10. + Note messages are specified using the cutomary notation (note name A..G, optionally followed by an accidental, # or b, followed by a (zero-based) MIDI octave number. Note that all MIDI octaves start at the note C, so B0 comes before C1. C5 denotes middle C, A5 is the - chamber pitch (usually at 440 Hz). + chamber pitch (usually at 440 Hz). Enharmonic spellings are + equivalent, so, e.g., D# and Eb denote exactly the same MIDI note. - MIDI messages are on channel 1 by default; a suffix of the form - -<1..16> can be used to specify a different MIDI channel. E.g., C3-10 - denotes note C3 on MIDI channel 10. + *NOTE:* There are various different standards for numbering octaves, + and different programs use different standards, which can be rather + confusing. There's the Helmholtz standard, which is still widely + used, but only in German-speaking countries, and the ASA standard + where middle C is C4 (one less than zero-based octave numbers, so the + sub-contra octave starts at C-1). At least two standards exist for + MIDI octave numbering, one in which middle C is C3 (so the sub-contra + octave starts at C-2), and zero-based octave numbers, where the + sub-contra octave starts at C0 a.k.a. MIDI note 0. The latter is what + we use here, as it probably appeals most to mathematically-inclined + and computer-science people like myself. It also relates nicely to + MIDI note numbers, since the octave number is just the MIDI note + number divided by 12, with the remainder of the division telling you + which note in the octave it is (0 = C, 1 = C#, ..., 10 = Bb, 11 = B). + + Thus, if you use some MIDI monitoring software to figure out which + notes to put in your midizaprc file, first check how the program + prints middle C, so that you know how to adjust the octave numbers + reported by the monitoring program. + + More details on the syntax of MIDI messages can be found in the + comments preceding the parse_midi() routine below. By default, all messages are interpreted in the same way as keys on a computer keyboard, i.e., they can be "on" ("pressed") or "off" @@ -134,11 +158,21 @@ CC1< CC7 CC1> CC7 - Furthermore, PB (pitch bends) can have a step size associated with - them. The default step size is 1. To indicate a different step size, - the notation PB[] is used. E.g., PB[1170] will give you - about 7 steps up and down, which is useful to emulate a shuttle wheel - such as those on the Contour Design devices. Example: + Furthermore, incremental CC and PB messages can have a step size + associated with them, which enable you to scale controller and pitch + bend changes. The default step size is 1 (no scaling). To change it, + the desired step size is written in brackets immediately after the + message token, but before the increment suffix. Thus, e.g., CC1[2]= + denotes a sequence to be executed once whenever the controller changes + by an amount of 2. For instance, the following translation scales + down the values of a controller, effectively dividing them by 2, so + that the output range becomes 0..63 (127/2, rounded down): + + CC1[2]= CC1 + + As another example, PB[1170] will give you 7 steps up and down, which + is useful to emulate a shuttle wheel such as those on the Contour + Design devices. Example: PB[1170]- "j" PB[1170]+ "l" @@ -159,7 +193,9 @@ *input* message determines whether it is a key press or value change type of event, and which direction it goes in the latter case. Only the "~" suffix can be used to indicate an incremental CC message in - sign bit encoding. + sign bit encoding. Specifying step sizes with incremental CC and PB + messages works as well, but scales the values *up* rather than down on + the output side. Finally, on the output side there's a special token of the form CH<1..16>, which doesn't actually generate any MIDI message. Rather, @@ -267,8 +303,7 @@ read_line(FILE *f, char *name) static translation *first_translation_section = NULL; static translation *last_translation_section = NULL; - -translation *default_translation; +translation *default_translation, *default_midi_translation[2]; translation * new_translation_section(char *name, char *regex) @@ -284,7 +319,13 @@ new_translation_section(char *name, char *regex) ret->name = alloc_strcat(name, NULL); if (regex == NULL || *regex == '\0') { ret->is_default = 1; - default_translation = ret; + if (!strcmp(name, "MIDI")) + default_midi_translation[0] = ret; + else if (!strcmp(name, "MIDI2")) { + default_midi_translation[1] = ret; + ret->portno = 1; + } else + default_translation = ret; } else { ret->is_default = 0; err = regcomp(&ret->regex, regex, REG_NOSUB); @@ -361,6 +402,8 @@ free_all_translations(void) } first_translation_section = NULL; last_translation_section = NULL; + default_translation = default_midi_translation[0] = + default_midi_translation[1] = NULL; } char *config_file_name = NULL; @@ -656,12 +699,12 @@ re_press_temp_modifiers(void) msg ::= "ch" | "pb" | "pc" | "cc" 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). This can optionally be followed by a number in brackets, - denoting a step size. Also optionally, the suffix with the third number - (after the dash) denotes the MIDI channel; otherwise the default MIDI - channel is used. + Case is insignificant. 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). This can optionally be followed + by a number in brackets, denoting a step size. Also optionally, the + suffix with the third number (after the dash) denotes the MIDI + channel; otherwise the default MIDI channel is used. Note that not all combinations are possible -- "pb" has no data byte; only "cc" and "pb" may be followed by a step size in brackets; and "ch" must @@ -885,6 +928,7 @@ start_translation(translation *tr, char *which_key) if (!dir) { is_bidirectional = 1; release_first_stroke = &(tr->pbs[chan][1]); + tr->pb_step[chan][1] = step; } } break; @@ -1115,6 +1159,7 @@ read_config_file(void) } free_all_translations(); + reload_callback(); debug_regex = default_debug_regex; debug_strokes = default_debug_strokes; debug_keys = default_debug_keys; @@ -1235,18 +1280,15 @@ get_translation(char *win_title, char *win_class) read_config_file(); tr = first_translation_section; while (tr != NULL) { - extern int enable_jack_output; - if (tr->is_default && - (strcmp(tr->name, "MIDI") || enable_jack_output)) { - return tr; - } else if (!tr->is_default) { + if (!tr->is_default) { // AG: We first try to match the class name, since it usually provides // better identification clues. if (win_class && *win_class && regexec(&tr->regex, win_class, 0, NULL, 0) == 0) { return tr; } - if (regexec(&tr->regex, win_title, 0, NULL, 0) == 0) { + if (win_title && *win_title && + regexec(&tr->regex, win_title, 0, NULL, 0) == 0) { return tr; } }