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
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]);
}
}

View File

@ -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

385
midizap.c
View File

@ -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);

View File

@ -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;

View File

@ -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[<step size>] 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;
}
}