Refactoring and bugfixes. Also added the capability to add another pair of MIDI ports (-o2).

master
Albert Graef 2018-08-11 21:04:09 +02:00
parent eb5ce16cb7
commit 6f0bec4b74
5 changed files with 465 additions and 287 deletions

View File

@ -140,16 +140,19 @@ queue_message(jack_ringbuffer_t* ringbuffer, MidiMessage *ev)
void void
process_midi_input(JACK_SEQ* seq,jack_nframes_t nframes) process_midi_input(JACK_SEQ* seq,jack_nframes_t nframes)
{ {
int k;
for (k = 0; k < seq->n_in; k++) {
int read, events, i; int read, events, i;
void *port_buffer;
MidiMessage rev; MidiMessage rev;
jack_midi_event_t event; 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) if (port_buffer == NULL)
{ {
fprintf(stderr, "jack_port_get_buffer failed, cannot receive anything.\n"); fprintf(stderr, "jack_port_get_buffer failed, cannot receive anything.\n");
return; return;
} }
#ifdef JACK_MIDI_NEEDS_NFRAMES #ifdef JACK_MIDI_NEEDS_NFRAMES
@ -162,47 +165,49 @@ process_midi_input(JACK_SEQ* seq,jack_nframes_t nframes)
{ {
#ifdef JACK_MIDI_NEEDS_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 #else
read = jack_midi_event_get(&event, port_buffer, i); read = jack_midi_event_get(&event, port_buffer, i);
#endif #endif
if (!read) if (!read)
{ {
//successful event get //successful event get
if (event.size <= 3 && event.size >=1) if (event.size <= 3 && event.size >= 1)
{ {
//not sysex or something //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);
}
}
//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 void
process_midi_output(JACK_SEQ* seq,jack_nframes_t nframes) 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; int read, t;
uint8_t *buffer; uint8_t *buffer;
void *port_buffer; void *port_buffer;
jack_nframes_t last_frame_time;
MidiMessage ev; MidiMessage ev;
last_frame_time = jack_last_frame_time(seq->jack_client); port_buffer = jack_port_get_buffer(seq->output_port[k], nframes);
port_buffer = jack_port_get_buffer(seq->output_port, nframes);
if (port_buffer == NULL) if (port_buffer == NULL)
{ {
fprintf(stderr, "jack_port_get_buffer failed, cannot send anything.\n"); fprintf(stderr, "jack_port_get_buffer failed, cannot send anything.\n");
return; return;
} }
#ifdef JACK_MIDI_NEEDS_NFRAMES #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); jack_midi_clear_buffer(port_buffer);
#endif #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)) if (read != sizeof(ev))
{ {
//warn_from_jack_thread_context("Short read from the ringbuffer, possible note loss."); //warn_from_jack_thread_context("Short read from the ringbuffer, possible note loss.");
jack_ringbuffer_read_advance(seq->ringbuffer_out, read); jack_ringbuffer_read_advance(seq->ringbuffer_out[k], read);
continue; 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 /* If computed time is too much into the future, we'll need
to send it later. */ to send it later. */
if (t >= (int)nframes) if (t >= (int)nframes)
break; break;
/* If computed time is < 0, we missed a cycle because of xrun. */ /* If computed time is < 0, we missed a cycle because of xrun. */
if (t < 0) if (t < 0)
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 #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 #else
buffer = jack_midi_event_reserve(port_buffer, t, ev.len); buffer = jack_midi_event_reserve(port_buffer, t, ev.len);
#endif #endif
if (buffer == NULL) if (buffer == NULL)
{ {
//warn_from_jack_thread_context("jack_midi_event_reserve failed, NOTE LOST."); //warn_from_jack_thread_context("jack_midi_event_reserve failed, NOTE LOST.");
break; break;
} }
memcpy(buffer, ev.data, ev.len); memcpy(buffer, ev.data, ev.len);
} }
}
} }
int 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"); fprintf(stderr, "Had to wait too long for JACK callback; scheduling problem?\n");
#endif #endif
if(seq->usein) if(seq->n_in)
process_midi_input( seq,nframes ); process_midi_input( seq,nframes );
if(seq->useout) if(seq->n_out)
process_midi_output( seq,nframes ); process_midi_output( seq,nframes );
#ifdef MEASURE_TIME #ifdef MEASURE_TIME
@ -276,7 +282,7 @@ process_callback(jack_nframes_t nframes, void *seqq)
/////////////////////////////////////////////// ///////////////////////////////////////////////
//these functions are executed in other threads //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; MidiMessage ev;
JACK_SEQ* seq = (JACK_SEQ*)seqq; JACK_SEQ* seq = (JACK_SEQ*)seqq;
@ -330,34 +336,37 @@ void queue_midi(void* seqq, uint8_t msg[])
ev.data[2] = msg[2]; ev.data[2] = msg[2];
ev.time = jack_frame_time(seq->jack_client); 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; int read, k;
MidiMessage ev; MidiMessage ev;
JACK_SEQ* seq = (JACK_SEQ*)seqq; 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)) if (read != sizeof(ev))
{ {
//warn_from_jack_thread_context("Short read from the ringbuffer, possible note loss."); //warn_from_jack_thread_context("Short read from the ringbuffer, possible note loss.");
jack_ringbuffer_read_advance(seq->ringbuffer_in, read); jack_ringbuffer_read_advance(seq->ringbuffer_in[k], read);
return -1; 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 int
init_jack(JACK_SEQ* seq, uint8_t verbose) init_jack(JACK_SEQ* seq, uint8_t verbose)
{ {
int err; int err, k;
char portname[100];
if(verbose)printf("opening client...\n"); if(verbose)printf("opening client...\n");
seq->jack_client = jack_client_open("midizap", JackNullOption, NULL); 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"); if(verbose)printf("initializing JACK input: \ncreating ringbuffer...\n");
seq->ringbuffer_in = jack_ringbuffer_create(RINGBUFFER_SIZE); 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 == NULL) 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; 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", if (seq->ringbuffer_in[k] == NULL)
JACK_DEFAULT_MIDI_TYPE, {
JackPortIsInput, 0); fprintf(stderr, "Cannot create JACK ringbuffer.\n");
return 0;
}
if (seq->input_port == NULL) jack_ringbuffer_mlock(seq->ringbuffer_in[k]);
{
fprintf(stderr, "Could not register JACK port.\n"); if (k)
return 0; 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"); if(verbose)printf("initializing JACK output: \ncreating ringbuffer...\n");
seq->ringbuffer_out = jack_ringbuffer_create(RINGBUFFER_SIZE); 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 == NULL) if (!seq->ringbuffer_out || !seq->output_port)
{ {
fprintf(stderr, "Cannot create JACK ringbuffer.\n"); fprintf(stderr, "Cannot allocate memory for ports and ringbuffers.\n");
return 0; 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", if (seq->ringbuffer_out[k] == NULL)
JACK_DEFAULT_MIDI_TYPE, {
JackPortIsOutput, 0); fprintf(stderr, "Cannot create JACK ringbuffer.\n");
return 0;
}
if (seq->output_port == NULL) jack_ringbuffer_mlock(seq->ringbuffer_out[k]);
{
fprintf(stderr, "Could not register JACK port.\n"); if (k)
return 0; 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) void close_jack(JACK_SEQ* seq)
{ {
if(seq->useout)jack_ringbuffer_free(seq->ringbuffer_out); int k;
if(seq->usein)jack_ringbuffer_free(seq->ringbuffer_in); 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]);
}
} }

View File

@ -5,18 +5,18 @@
typedef struct _jseq typedef struct _jseq
{ {
jack_ringbuffer_t *ringbuffer_out; jack_ringbuffer_t **ringbuffer_out;
jack_ringbuffer_t *ringbuffer_in; jack_ringbuffer_t **ringbuffer_in;
jack_client_t *jack_client; jack_client_t *jack_client;
jack_port_t *output_port; jack_port_t **output_port;
jack_port_t *input_port; jack_port_t **input_port;
uint8_t usein; uint8_t n_in;
uint8_t useout; uint8_t n_out;
} JACK_SEQ; } JACK_SEQ;
int init_jack(JACK_SEQ* seq, uint8_t verbose); int init_jack(JACK_SEQ* seq, uint8_t verbose);
void close_jack(JACK_SEQ* seq); void close_jack(JACK_SEQ* seq);
void queue_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[]); int pop_midi(void* seqq, uint8_t msg[], uint8_t *port_no);
#endif #endif

385
midizap.c
View File

@ -20,13 +20,6 @@
typedef struct input_event EV; 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; Display *display;
JACK_SEQ seq; JACK_SEQ seq;
@ -75,7 +68,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 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 if (!enable_jack_output) return; // MIDI output not enabled
uint8_t msg[3]; uint8_t msg[3];
@ -153,14 +146,14 @@ send_midi(int status, int data, int step, int incr, int index, int dir)
default: default:
return; return;
} }
queue_midi(&seq, msg); queue_midi(&seq, msg, portno);
} }
stroke * 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) int index, int dir)
{ {
if (tr != NULL) { if (tr && tr->portno == portno) {
switch (status) { switch (status) {
case 0x90: case 0x90:
return tr->note[chan][data][index]; return tr->note[chan][data][index];
@ -183,18 +176,54 @@ fetch_stroke(translation *tr, int status, int chan, int data,
return NULL; 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" }; 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, send_strokes(translation *tr, uint8_t portno, int status, int chan, int data,
int index, int dir) int index, int dir)
{ {
int nkeys = 0; 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; 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) { if (debug_keys && s) {
@ -242,7 +271,7 @@ send_strokes(translation *tr, int status, int chan, int data,
send_key(s->keysym, s->press); send_key(s->keysym, s->press);
nkeys++; nkeys++;
} else { } 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; s = s->next;
} }
@ -316,34 +345,29 @@ walk_window_tree(Window win, char **window_class)
return NULL; return NULL;
} }
static Window last_focused_window = 0;
static translation *last_window_translation = NULL;
translation * translation *
get_focused_window_translation() get_focused_window_translation()
{ {
Window focus; Window focus;
int revert_to; int revert_to;
char *window_name = NULL, *window_class = NULL; char *window_name = NULL, *window_class = NULL;
char *name;
XGetInputFocus(display, &focus, &revert_to); XGetInputFocus(display, &focus, &revert_to);
if (focus != last_focused_window) { if (focus != last_focused_window) {
last_focused_window = focus; last_focused_window = focus;
window_name = walk_window_tree(focus, &window_class); window_name = walk_window_tree(focus, &window_class);
if (window_name == NULL) { last_window_translation = get_translation(window_name, window_class);
name = "-- Unlabeled Window --"; if (window_name && *window_name) {
strncpy(last_window_name, window_name, MAX_WINNAME_SIZE);
last_window_name[MAX_WINNAME_SIZE-1] = 0;
} else { } else {
name = window_name; strcpy(last_window_name, "Unnamed");;
} }
last_window_translation = get_translation(name, window_class); if (window_class && *window_class) {
if (debug_regex) { strncpy(last_window_class, window_class, MAX_WINNAME_SIZE);
if (last_window_translation != NULL) { last_window_class[MAX_WINNAME_SIZE-1] = 0;
printf("translation: %s for %s (class %s)\n", } else {
last_window_translation->name, name, window_class); strcpy(last_window_name, "Unnamed");;
} else {
printf("no translation found for %s (class %s)\n", name, window_class);
}
} }
if (window_name != NULL) { if (window_name != NULL) {
XFree(window_name); XFree(window_name);
@ -355,155 +379,201 @@ get_focused_window_translation()
return last_window_translation; return last_window_translation;
} }
static int8_t inccvalue[16][128]; static int8_t inccvalue[2][16][128];
static int16_t inpbvalue[16] = 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},
{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 notedown[2][16][128];
static uint8_t inccdown[16][128]; static uint8_t inccdown[2][16][128];
static uint8_t inpbdown[16]; static uint8_t inpbdown[2][16];
int 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]; 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 && tr->portno == portno &&
(tr->ccs[chan][data][0] || tr->ccs[chan][data][1]))
return tr->is_incr[chan][data]; return tr->is_incr[chan][data];
return 0; return 0;
} }
int 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; return 1;
tr = default_translation; 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 1;
return 0; 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 void
handle_event(uint8_t *msg) handle_event(uint8_t *msg, uint8_t portno)
{ {
translation *tr = get_focused_window_translation(); translation *tr = get_focused_window_translation();
//fprintf(stderr, "midi: %0x %0x %0x\n", msg[0], msg[1], msg[2]); //fprintf(stderr, "midi [%d]: %0x %0x %0x\n", portno, msg[0], msg[1], msg[2]);
if (tr != NULL) { int status = msg[0] & 0xf0, chan = msg[0] & 0x0f;
int status = msg[0] & 0xf0, chan = msg[0] & 0x0f; if (status == 0x80) {
if (status == 0x80) { status = 0x90;
status = 0x90; msg[0] = status | chan;
msg[0] = status | chan; msg[2] = 0;
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) { break;
case 0xc0: case 0xb0:
send_strokes(tr, status, chan, msg[1], 0, 0); if (msg[2]) {
send_strokes(tr, status, chan, msg[1], 1, 0); if (!inccdown[portno][chan][msg[1]]) {
break; send_strokes(tr, portno, status, chan, msg[1], 0, 0);
case 0x90: inccdown[portno][chan][msg[1]] = 1;
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; } else {
case 0xb0: if (inccdown[portno][chan][msg[1]]) {
if (msg[2]) { send_strokes(tr, portno, status, chan, msg[1], 1, 0);
if (!inccdown[chan][msg[1]]) { inccdown[portno][chan][msg[1]] = 0;
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;
}
} }
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: if (check_incr(tr, portno, chan, msg[1])) {
// ignore everything else // Incremental controller a la MCU. NB: This assumes a signed bit
break; // 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) 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, "-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, "-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"); 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 CONF_FREQ 1
#define MAX_COUNT (1000000/CONF_FREQ/POLL_INTERVAL) #define MAX_COUNT (1000000/CONF_FREQ/POLL_INTERVAL)
int n_ports = 1;
int int
main(int argc, char **argv) main(int argc, char **argv)
{ {
uint8_t msg[3]; uint8_t msg[3];
int opt, count = 0; int opt, count = 0;
while ((opt = getopt(argc, argv, "hod::r:")) != -1) { while ((opt = getopt(argc, argv, "ho::d::r:")) != -1) {
switch (opt) { switch (opt) {
case 'h': case 'h':
help(argv[0]); help(argv[0]);
exit(0); exit(0);
case 'o': case 'o':
enable_jack_output = 1; 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; break;
case 'd': case 'd':
if (optarg && *optarg) { if (optarg && *optarg) {
@ -580,7 +662,7 @@ main(int argc, char **argv)
initdisplay(); 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)) { if (!init_jack(&seq, debug_jack)) {
exit(1); exit(1);
} }
@ -589,8 +671,9 @@ main(int argc, char **argv)
// force the config file to be loaded initially // force the config file to be loaded initially
count = MAX_COUNT; count = MAX_COUNT;
while (!quit) { while (!quit) {
while (pop_midi(&seq, msg)) { uint8_t portno;
handle_event(msg); while (pop_midi(&seq, msg, &portno)) {
handle_event(msg, portno);
count = 0; count = 0;
} }
usleep(POLL_INTERVAL); usleep(POLL_INTERVAL);

View File

@ -67,6 +67,7 @@ typedef struct _translation {
char *name; char *name;
int is_default; int is_default;
regex_t regex; regex_t regex;
uint8_t portno;
// 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
// translation section even if most of the entries are NULL pointers). We // 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]; int pb_step[NUM_CHAN][2];
} translation; } translation;
extern void reload_callback(void);
extern int read_config_file(void); extern int read_config_file(void);
extern translation *get_translation(char *win_title, char *win_class); extern translation *get_translation(char *win_title, char *win_class);
extern void print_stroke_sequence(char *name, char *up_or_down, stroke *s); 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 debug_regex, debug_strokes, debug_keys;
extern int default_debug_regex, default_debug_strokes, default_debug_keys; extern int default_debug_regex, default_debug_strokes, default_debug_keys;
extern char *config_file_name; extern char *config_file_name;
extern int enable_jack_output;

View File

@ -33,15 +33,39 @@
handle most common use cases. (In any case, adding more message types handle most common use cases. (In any case, adding more message types
should be a piece of cake.) 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 Note messages are specified using the cutomary notation (note name
A..G, optionally followed by an accidental, # or b, followed by a A..G, optionally followed by an accidental, # or b, followed by a
(zero-based) MIDI octave number. Note that all MIDI octaves start at (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 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 *NOTE:* There are various different standards for numbering octaves,
-<1..16> can be used to specify a different MIDI channel. E.g., C3-10 and different programs use different standards, which can be rather
denotes note C3 on MIDI channel 10. 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 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" computer keyboard, i.e., they can be "on" ("pressed") or "off"
@ -134,11 +158,21 @@
CC1< CC7 CC1< CC7
CC1> CC7 CC1> CC7
Furthermore, PB (pitch bends) can have a step size associated with Furthermore, incremental CC and PB messages can have a step size
them. The default step size is 1. To indicate a different step size, associated with them, which enable you to scale controller and pitch
the notation PB[<step size>] is used. E.g., PB[1170] will give you bend changes. The default step size is 1 (no scaling). To change it,
about 7 steps up and down, which is useful to emulate a shuttle wheel the desired step size is written in brackets immediately after the
such as those on the Contour Design devices. Example: 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]- "j"
PB[1170]+ "l" PB[1170]+ "l"
@ -159,7 +193,9 @@
*input* message determines whether it is a key press or value change *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 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 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 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, 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 *first_translation_section = NULL;
static translation *last_translation_section = NULL; static translation *last_translation_section = NULL;
translation *default_translation, *default_midi_translation[2];
translation *default_translation;
translation * translation *
new_translation_section(char *name, char *regex) new_translation_section(char *name, char *regex)
@ -284,7 +319,13 @@ new_translation_section(char *name, char *regex)
ret->name = alloc_strcat(name, NULL); ret->name = alloc_strcat(name, NULL);
if (regex == NULL || *regex == '\0') { if (regex == NULL || *regex == '\0') {
ret->is_default = 1; 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 { } else {
ret->is_default = 0; ret->is_default = 0;
err = regcomp(&ret->regex, regex, REG_NOSUB); err = regcomp(&ret->regex, regex, REG_NOSUB);
@ -361,6 +402,8 @@ free_all_translations(void)
} }
first_translation_section = NULL; first_translation_section = NULL;
last_translation_section = NULL; last_translation_section = NULL;
default_translation = default_midi_translation[0] =
default_midi_translation[1] = NULL;
} }
char *config_file_name = NULL; char *config_file_name = NULL;
@ -656,12 +699,12 @@ re_press_temp_modifiers(void)
msg ::= "ch" | "pb" | "pc" | "cc" msg ::= "ch" | "pb" | "pc" | "cc"
incr ::= "-" | "+" | "=" | "<" | ">" | "~" incr ::= "-" | "+" | "=" | "<" | ">" | "~"
Numbers are always in decimal. The meaning of the first number depends on Case is insignificant. Numbers are always in decimal. The meaning of
the context (octave number for notes, the actual data byte for other the first number depends on the context (octave number for notes, the
messages). This can optionally be followed by a number in brackets, actual data byte for other messages). This can optionally be followed
denoting a step size. Also optionally, the suffix with the third number by a number in brackets, denoting a step size. Also optionally, the
(after the dash) denotes the MIDI channel; otherwise the default MIDI suffix with the third number (after the dash) denotes the MIDI
channel is used. channel; otherwise the default MIDI channel is used.
Note that not all combinations are possible -- "pb" has no data byte; only 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 "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) { if (!dir) {
is_bidirectional = 1; is_bidirectional = 1;
release_first_stroke = &(tr->pbs[chan][1]); release_first_stroke = &(tr->pbs[chan][1]);
tr->pb_step[chan][1] = step;
} }
} }
break; break;
@ -1115,6 +1159,7 @@ read_config_file(void)
} }
free_all_translations(); free_all_translations();
reload_callback();
debug_regex = default_debug_regex; debug_regex = default_debug_regex;
debug_strokes = default_debug_strokes; debug_strokes = default_debug_strokes;
debug_keys = default_debug_keys; debug_keys = default_debug_keys;
@ -1235,18 +1280,15 @@ get_translation(char *win_title, char *win_class)
read_config_file(); read_config_file();
tr = first_translation_section; tr = first_translation_section;
while (tr != NULL) { while (tr != NULL) {
extern int enable_jack_output; if (!tr->is_default) {
if (tr->is_default &&
(strcmp(tr->name, "MIDI") || enable_jack_output)) {
return tr;
} else if (!tr->is_default) {
// AG: We first try to match the class name, since it usually provides // AG: We first try to match the class name, since it usually provides
// better identification clues. // better identification clues.
if (win_class && *win_class && if (win_class && *win_class &&
regexec(&tr->regex, win_class, 0, NULL, 0) == 0) { regexec(&tr->regex, win_class, 0, NULL, 0) == 0) {
return tr; 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; return tr;
} }
} }