Optimize memory usage of the translation tables, using sorted arrays and binary search. Still runs reasonably fast, and memory usage is much lower know (in the KB range).

master
Albert Graef 2018-08-15 00:06:08 +02:00
parent 9d6cc38171
commit ade9aa3db8
3 changed files with 320 additions and 102 deletions

184
midizap.c
View File

@ -151,26 +151,112 @@ send_midi(uint8_t portno, int status, int data, int step, int incr, int index, i
queue_midi(&seq, msg, portno);
}
static int stroke_data_cmp(const void *a, const void *b)
{
const stroke_data *ad = (const stroke_data*)a;
const stroke_data *bd = (const stroke_data*)b;
if (ad->chan == bd->chan)
return ad->data - bd->data;
else
return ad->chan - bd->chan;
}
static stroke *find_stroke_data(stroke_data *sd,
int chan, int data, int index,
int *step, int *incr,
uint16_t n)
{
if (n < 16) {
// Linear search is presumably faster for small arrays, and we also avoid
// function calls for doing the comparisons here. Not sure where it breaks
// even with glibc's bsearch(), though (TODO: measure).
uint16_t i;
for (i = 0; i < n; i++) {
if (sd[i].chan == chan && sd[i].data == data) {
if (step) *step = sd[i].step[index];
if (incr) *incr = sd[i].is_incr;
return sd[i].s[index];
} else if (sd[i].chan > chan ||
(sd[i].chan == chan && sd[i].data > data))
return NULL;
}
return NULL;
} else {
// binary search from libc
stroke_data *ret, key;
key.chan = chan; key.data = data;
ret = bsearch(&key, sd, n, sizeof(stroke_data), stroke_data_cmp);
if (ret) {
if (step) *step = ret->step[index];
if (incr) *incr = ret->is_incr;
return ret->s[index];
} else
return NULL;
}
}
static stroke *find_note(translation *tr, int shift,
int chan, int data, int index)
{
return find_stroke_data(tr->note[shift], chan, data, index, 0, 0,
tr->n_note[shift]);
}
static stroke *find_pc(translation *tr, int shift,
int chan, int data, int index)
{
return find_stroke_data(tr->pc[shift], chan, data, index, 0, 0,
tr->n_pc[shift]);
}
static stroke *find_cc(translation *tr, int shift,
int chan, int data, int index)
{
return find_stroke_data(tr->cc[shift], chan, data, index, 0, 0,
tr->n_cc[shift]);
}
static stroke *find_ccs(translation *tr, int shift,
int chan, int data, int index, int *step, int *incr)
{
return find_stroke_data(tr->ccs[shift], chan, data, index, step, incr,
tr->n_ccs[shift]);
}
static stroke *find_pb(translation *tr, int shift,
int chan, int index)
{
return find_stroke_data(tr->pb[shift], chan, 0, index, 0, 0,
tr->n_pb[shift]);
}
static stroke *find_pbs(translation *tr, int shift,
int chan, int index, int *step)
{
return find_stroke_data(tr->pbs[shift], chan, 0, index, step, 0,
tr->n_pbs[shift]);
}
stroke *
fetch_stroke(translation *tr, uint8_t portno, int status, int chan, int data,
int index, int dir)
int index, int dir, int *step, int *incr)
{
if (tr && tr->portno == portno) {
switch (status) {
case 0x90:
return tr->note[shift][chan][data][index];
return find_note(tr, shift, chan, data, index);
case 0xc0:
return tr->pc[shift][chan][data][index];
return find_pc(tr, shift, chan, data, index);
case 0xb0:
if (dir)
return tr->ccs[shift][chan][data][dir>0];
return find_ccs(tr, shift, chan, data, dir>0, step, incr);
else
return tr->cc[shift][chan][data][index];
return find_cc(tr, shift, chan, data, index);
case 0xe0:
if (dir)
return tr->pbs[shift][chan][dir>0];
return find_pbs(tr, shift, chan, dir>0, step);
else
return tr->pb[shift][chan][index];
return find_pb(tr, shift, chan, index);
default:
return NULL;
}
@ -221,10 +307,11 @@ static char *debug_key(translation *tr, char *name,
data / 12 + midi_octave, chan+1);
break;
case 0xb0: {
int step = tr->cc_step[shift][chan][data][dir>0];
int step = 1, is_incr = 0;
if (tr) (void)find_ccs(tr, shift, chan, data, dir>0, &step, &is_incr);
if (!dir)
suffix = "";
else if (tr->is_incr[shift][chan][data])
else if (is_incr)
suffix = (dir<0)?"<":">";
else
suffix = (dir<0)?"-":"+";
@ -238,7 +325,8 @@ static char *debug_key(translation *tr, char *name,
sprintf(name, "%sPC%d-%d", prefix, data, chan+1);
break;
case 0xe0: {
int step = tr->pb_step[shift][chan][dir>0];
int step = 1;
if (tr) (void)find_pbs(tr, shift, chan, dir>0, &step);
if (!dir)
suffix = "";
else
@ -255,7 +343,7 @@ static char *debug_key(translation *tr, char *name,
return name;
}
static void debug_input(translation *tr, int portno,
static void debug_input(int portno,
int status, int chan, int data, int data2)
{
char name[100];
@ -264,10 +352,10 @@ static void debug_input(translation *tr, int portno,
data2 = ((data2 << 7) | data) - 8192;
if (status == 0xc0)
printf("[%d] %s\n", portno,
debug_key(tr, name, status, chan, data, 0));
debug_key(0, name, status, chan, data, 0));
else
printf("[%d] %s value = %d\n", portno,
debug_key(tr, name, status, chan, data, 0), data2);
debug_key(0, name, status, chan, data, 0), data2);
}
// Some machinery to handle the debugging of section matches. This is
@ -300,21 +388,23 @@ void
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, portno, status, chan, data, index, dir);
int nkeys = 0, step = 0, is_incr = 0;
stroke *s = fetch_stroke(tr, portno, status, chan, data, index, dir,
&step, &is_incr);
// If there's no press/release translation, check whether we have got at
// least the corresponding release/press translation, in order to prevent
// spurious error messages if either the press or release translation just
// happens to be empty.
int chk = s ||
(!dir && fetch_stroke(tr, portno, status, chan, data, !index, dir));
(!dir && fetch_stroke(tr, portno, status, chan, data, !index, dir, 0, 0));
if (!s && jack_num_outputs) {
// fall back to default MIDI translation
tr = default_midi_translation[portno];
s = fetch_stroke(tr, portno, status, chan, data, index, dir);
s = fetch_stroke(tr, portno, status, chan, data, index, dir,
&step, &is_incr);
chk = chk || s ||
(!dir && fetch_stroke(tr, portno, status, chan, data, !index, dir));
(!dir && fetch_stroke(tr, portno, status, chan, data, !index, dir, 0, 0));
// 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;
@ -323,9 +413,10 @@ send_strokes(translation *tr, uint8_t portno, int status, int chan, int data,
if (!s) {
// fall back to the default translation
tr = default_translation;
s = fetch_stroke(tr, portno, status, chan, data, index, dir);
s = fetch_stroke(tr, portno, status, chan, data, index, dir,
&step, &is_incr);
chk = chk || s ||
(!dir && fetch_stroke(tr, portno, status, chan, data, !index, dir));
(!dir && fetch_stroke(tr, portno, status, chan, data, !index, dir, 0, 0));
}
if (debug_regex) {
@ -486,34 +577,39 @@ static uint8_t inpbdown[2][16];
int
check_incr(translation *tr, uint8_t portno, int chan, int data)
{
int is_incr;
if (tr && tr->portno == portno &&
(tr->ccs[shift][chan][data][0] || tr->ccs[shift][chan][data][1]))
return tr->is_incr[shift][chan][data];
(find_ccs(tr, shift, chan, data, 0, 0, &is_incr) ||
find_ccs(tr, shift, chan, data, 1, 0, &is_incr)))
return is_incr;
tr = default_midi_translation[portno];
if (tr && tr->portno == portno &&
(tr->ccs[shift][chan][data][0] || tr->ccs[shift][chan][data][1]))
return tr->is_incr[shift][chan][data];
(find_ccs(tr, shift, chan, data, 0, 0, &is_incr) ||
find_ccs(tr, shift, chan, data, 1, 0, &is_incr)))
return is_incr;
tr = default_translation;
if (tr && tr->portno == portno &&
(tr->ccs[shift][chan][data][0] || tr->ccs[shift][chan][data][1]))
return tr->is_incr[shift][chan][data];
(find_ccs(tr, shift, chan, data, 0, 0, &is_incr) ||
find_ccs(tr, shift, chan, data, 1, 0, &is_incr)))
return is_incr;
return 0;
}
int
get_cc_step(translation *tr, uint8_t portno, int chan, int data, int dir)
{
int step;
if (tr && tr->portno == portno &&
tr->ccs[shift][chan][data][dir>0])
return tr->cc_step[shift][chan][data][dir>0];
find_ccs(tr, shift, chan, data, dir>0, &step, 0))
return step;
tr = default_midi_translation[portno];
if (tr && tr->portno == portno &&
tr->ccs[shift][chan][data][dir>0])
return tr->cc_step[shift][chan][data][dir>0];
find_ccs(tr, shift, chan, data, dir>0, &step, 0))
return step;
tr = default_translation;
if (tr && tr->portno == portno &&
tr->ccs[shift][chan][data][dir>0])
return tr->cc_step[shift][chan][data][dir>0];
find_ccs(tr, shift, chan, data, dir>0, &step, 0))
return step;
return 1;
}
@ -521,15 +617,18 @@ int
check_pbs(translation *tr, uint8_t portno, int chan)
{
if (tr && tr->portno == portno &&
(tr->pbs[shift][chan][0] || tr->pbs[shift][chan][1]))
(find_pbs(tr, shift, chan, 0, 0) ||
find_pbs(tr, shift, chan, 1, 0)))
return 1;
tr = default_midi_translation[portno];
if (tr && tr->portno == portno &&
(tr->pbs[shift][chan][0] || tr->pbs[shift][chan][1]))
(find_pbs(tr, shift, chan, 0, 0) ||
find_pbs(tr, shift, chan, 1, 0)))
return 1;
tr = default_translation;
if (tr && tr->portno == portno &&
(tr->pbs[shift][chan][0] || tr->pbs[shift][chan][1]))
(find_pbs(tr, shift, chan, 0, 0) ||
find_pbs(tr, shift, chan, 1, 0)))
return 1;
return 0;
}
@ -537,17 +636,18 @@ check_pbs(translation *tr, uint8_t portno, int chan)
int
get_pb_step(translation *tr, uint8_t portno, int chan, int dir)
{
int step;
if (tr && tr->portno == portno &&
tr->pbs[shift][chan][dir>0])
return tr->pb_step[shift][chan][dir>0];
find_pbs(tr, shift, chan, dir>0, &step))
return step;
tr = default_midi_translation[portno];
if (tr && tr->portno == portno &&
tr->pbs[shift][chan][dir>0])
return tr->pb_step[shift][chan][dir>0];
find_pbs(tr, shift, chan, dir>0, &step))
return step;
tr = default_translation;
if (tr && tr->portno == portno &&
tr->pbs[shift][chan][dir>0])
return tr->pb_step[shift][chan][dir>0];
find_pbs(tr, shift, chan, dir>0, &step))
return step;
return 1;
}
@ -563,7 +663,7 @@ handle_event(uint8_t *msg, uint8_t portno)
msg[0] = status | chan;
msg[2] = 0;
}
if (debug_midi) debug_input(tr, portno, status, chan, msg[1], msg[2]);
if (debug_midi) debug_input(portno, status, chan, msg[1], msg[2]);
switch (status) {
case 0xc0:
start_debug();

View File

@ -64,25 +64,33 @@ typedef struct _stroke {
#define NUM_KEYS 128
#define NUM_CHAN 16
typedef struct _stroke_data {
// key (MIDI channel and, for note/CC/PB, data byte)
uint8_t chan, data;
// stroke data, indexed by press/release or up/down index
stroke *s[2];
// step size (CC and PB only)
int step[2];
// incr flag (CC only)
uint8_t is_incr;
} stroke_data;
typedef struct _translation {
struct _translation *next;
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. We should rather use some
// kind of dictionary here.
uint8_t is_incr[2][NUM_CHAN][NUM_KEYS];
stroke *pc[2][NUM_CHAN][NUM_KEYS][2];
stroke *note[2][NUM_CHAN][NUM_KEYS][2];
stroke *cc[2][NUM_CHAN][NUM_KEYS][2];
stroke *ccs[2][NUM_CHAN][NUM_KEYS][2];
stroke *pb[2][NUM_CHAN][2];
stroke *pbs[2][NUM_CHAN][2];
// step size for control changes and pitch bend (1 by default)
int cc_step[2][NUM_CHAN][NUM_KEYS][2];
int pb_step[2][NUM_CHAN][2];
// these are indexed by shift status
stroke_data *note[2];
stroke_data *pc[2];
stroke_data *cc[2];
stroke_data *ccs[2];
stroke_data *pb[2];
stroke_data *pbs[2];
// actual and allocated sizes (can be at most 16*128)
uint16_t n_note[2], n_pc[2], n_cc[2], n_ccs[2], n_pb[2], n_pbs[2];
uint16_t a_note[2], a_pc[2], a_cc[2], a_ccs[2], a_pb[2], a_pbs[2];
} translation;
extern void reload_callback(void);

View File

@ -371,10 +371,129 @@ free_strokes(stroke *s)
}
}
static int stroke_data_cmp(const void *a, const void *b)
{
const stroke_data *ad = (const stroke_data*)a;
const stroke_data *bd = (const stroke_data*)b;
if (ad->chan == bd->chan)
return ad->data - bd->data;
else
return ad->chan - bd->chan;
}
static void finish_stroke_data(stroke_data **sd,
uint16_t *n, uint16_t *a)
{
if (*a && *a > *n) {
// realloc to needed size
*sd = realloc(*sd, (*n)*sizeof(stroke_data));
*a = *n;
}
// sort by chan/data for faster access
qsort(*sd, *n, sizeof(stroke_data), stroke_data_cmp);
}
static void free_stroke_data(stroke_data *sd, uint16_t n)
{
uint16_t i;
for (i = 0; i < n; i++) {
free_strokes(sd[i].s[0]);
free_strokes(sd[i].s[1]);
}
free(sd);
}
static stroke **find_stroke_data(stroke_data **sd,
int chan, int data, int index,
int step, int incr,
uint16_t *n, uint16_t *a)
{
uint16_t i;
for (i = 0; i < *n; i++) {
if ((*sd)[i].chan == chan && (*sd)[i].data == data) {
// existing entry
(*sd)[i].step[index] = step;
(*sd)[i].is_incr = incr;
return &(*sd)[i].s[index];
}
}
// add a new entry
if (*n >= *a) {
// make some room
*a = (*a)?2*(*a):8;
*sd = realloc(*sd, (*a)*sizeof(stroke_data));
}
memset(&(*sd)[*n], 0, sizeof(stroke_data));
(*sd)[*n].chan = chan;
(*sd)[*n].data = data;
(*sd)[*n].step[index] = step;
(*sd)[*n].is_incr = incr;
return &(*sd)[(*n)++].s[index];
}
static stroke **find_note(translation *tr, int shift,
int chan, int data, int index)
{
return find_stroke_data(&tr->note[shift], chan, data, index, 0, 0,
&tr->n_note[shift], &tr->a_note[shift]);
}
static stroke **find_pc(translation *tr, int shift,
int chan, int data, int index)
{
return find_stroke_data(&tr->pc[shift], chan, data, index, 0, 0,
&tr->n_pc[shift], &tr->a_pc[shift]);
}
static stroke **find_cc(translation *tr, int shift,
int chan, int data, int index)
{
return find_stroke_data(&tr->cc[shift], chan, data, index, 0, 0,
&tr->n_cc[shift], &tr->a_cc[shift]);
}
static stroke **find_ccs(translation *tr, int shift,
int chan, int data, int index, int step, int incr)
{
return find_stroke_data(&tr->ccs[shift], chan, data, index, step, incr,
&tr->n_ccs[shift], &tr->a_ccs[shift]);
}
static stroke **find_pb(translation *tr, int shift,
int chan, int index)
{
return find_stroke_data(&tr->pb[shift], chan, 0, index, 0, 0,
&tr->n_pb[shift], &tr->a_pb[shift]);
}
static stroke **find_pbs(translation *tr, int shift,
int chan, int index, int step)
{
return find_stroke_data(&tr->pbs[shift], chan, 0, index, step, 0,
&tr->n_pbs[shift], &tr->a_pbs[shift]);
}
void
finish_translation_section(translation *tr)
{
int k;
if (tr) {
for (k=0; k<2; k++) {
finish_stroke_data(&tr->pc[k], &tr->n_pc[k], &tr->a_pc[k]);
finish_stroke_data(&tr->note[k], &tr->n_note[k], &tr->a_note[k]);
finish_stroke_data(&tr->cc[k], &tr->n_cc[k], &tr->a_cc[k]);
finish_stroke_data(&tr->ccs[k], &tr->n_ccs[k], &tr->a_ccs[k]);
finish_stroke_data(&tr->pb[k], &tr->n_pb[k], &tr->a_pb[k]);
finish_stroke_data(&tr->pbs[k], &tr->n_pbs[k], &tr->a_pbs[k]);
}
}
}
void
free_translation_section(translation *tr)
{
int i, j, k;
int k;
if (tr != NULL) {
free(tr->name);
@ -382,22 +501,12 @@ free_translation_section(translation *tr)
regfree(&tr->regex);
}
for (k=0; k<2; k++) {
for (i=0; i<NUM_CHAN; i++) {
for (j=0; j<NUM_KEYS; j++) {
free_strokes(tr->pc[k][i][j][0]);
free_strokes(tr->pc[k][i][j][1]);
free_strokes(tr->note[k][i][j][0]);
free_strokes(tr->note[k][i][j][1]);
free_strokes(tr->cc[k][i][j][0]);
free_strokes(tr->cc[k][i][j][1]);
free_strokes(tr->ccs[k][i][j][0]);
free_strokes(tr->ccs[k][i][j][1]);
}
free_strokes(tr->pb[k][i][0]);
free_strokes(tr->pb[k][i][1]);
free_strokes(tr->pbs[k][i][0]);
free_strokes(tr->pbs[k][i][1]);
}
free_stroke_data(tr->pc[k], tr->n_pc[k]);
free_stroke_data(tr->note[k], tr->n_note[k]);
free_stroke_data(tr->cc[k], tr->n_cc[k]);
free_stroke_data(tr->ccs[k], tr->n_ccs[k]);
free_stroke_data(tr->pb[k], tr->n_pb[k]);
free_stroke_data(tr->pbs[k], tr->n_pbs[k]);
}
free(tr);
}
@ -934,11 +1043,11 @@ start_translation(translation *tr, char *which_key)
switch (status & 0xf0) {
case 0x90:
// note on/off
first_stroke = &(tr->note[k][chan][data][0]);
release_first_stroke = &(tr->note[k][chan][data][1]);
first_stroke = find_note(tr, k, chan, data, 0);
release_first_stroke = find_note(tr, k, chan, data, 1);
if (is_anyshift) {
alt_press_stroke = &(tr->note[0][chan][data][0]);
alt_release_stroke = &(tr->note[0][chan][data][1]);
alt_press_stroke = find_note(tr, 0, chan, data, 0);
alt_release_stroke = find_note(tr, 0, chan, data, 1);
}
is_keystroke = 1;
break;
@ -948,32 +1057,34 @@ start_translation(translation *tr, char *which_key)
// this message has no off state. Thus, when we receive a pc, it's
// supposed to be treated as a "press" sequence immediately followed by
// the corresponding "release" sequence.
first_stroke = &(tr->pc[k][chan][data][0]);
release_first_stroke = &(tr->pc[k][chan][data][1]);
first_stroke = find_pc(tr, k, chan, data, 0);
release_first_stroke = find_pc(tr, k, chan, data, 1);
if (is_anyshift) {
alt_press_stroke = &(tr->pc[0][chan][data][0]);
alt_release_stroke = &(tr->pc[0][chan][data][1]);
alt_press_stroke = find_pc(tr, 0, chan, data, 0);
alt_release_stroke = find_pc(tr, 0, chan, data, 1);
}
is_keystroke = 1;
break;
case 0xb0:
if (!incr) {
// cc on/off
first_stroke = &(tr->cc[k][chan][data][0]);
release_first_stroke = &(tr->cc[k][chan][data][1]);
first_stroke = find_cc(tr, k, chan, data, 0);
release_first_stroke = find_cc(tr, k, chan, data, 1);
if (is_anyshift) {
alt_press_stroke = &(tr->cc[0][chan][data][0]);
alt_release_stroke = &(tr->cc[0][chan][data][1]);
alt_press_stroke = find_cc(tr, 0, chan, data, 0);
alt_release_stroke = find_cc(tr, 0, chan, data, 1);
}
is_keystroke = 1;
} else {
// cc (step up, down)
tr->is_incr[k][chan][data] = incr>1;
first_stroke = &(tr->ccs[k][chan][data][dir>0]);
if (is_anyshift) {
alt_press_stroke = &(tr->ccs[0][chan][data][0]);
if (step <= 0) {
fprintf(stderr, "zero or negative step size not permitted here: [%s]%s\n", current_translation, which_key);
return 1;
}
first_stroke = find_ccs(tr, k, chan, data, dir>0, step, incr>1);
if (is_anyshift) {
alt_press_stroke = find_ccs(tr, 0, chan, data, dir>0, step, incr>1);
}
tr->cc_step[k][chan][data][dir>0] = step;
if (!dir) {
// This is a bidirectional translation (=, ~). We first fill in the
// "down" part (pointed to by first_stroke). When finishing off the
@ -983,22 +1094,21 @@ start_translation(translation *tr, char *which_key)
// translation, here to remember the "up" part of the translation,
// so that we can fill in that part later.
is_bidirectional = 1;
release_first_stroke = &(tr->ccs[k][chan][data][1]);
release_first_stroke = find_ccs(tr, k, chan, data, 1, step, incr>1);
if (is_anyshift) {
alt_release_stroke = &(tr->ccs[0][chan][data][1]);
alt_release_stroke = find_ccs(tr, 0, chan, data, 1, step, incr>1);
}
tr->cc_step[k][chan][data][1] = step;
}
}
break;
case 0xe0:
if (!incr) {
// pb on/off
first_stroke = &(tr->pb[k][chan][0]);
release_first_stroke = &(tr->pb[k][chan][1]);
first_stroke = find_pb(tr, k, chan, 0);
release_first_stroke = find_pb(tr, k, chan, 1);
if (is_anyshift) {
alt_press_stroke = &(tr->pb[0][chan][0]);
alt_release_stroke = &(tr->pb[0][chan][1]);
alt_press_stroke = find_pb(tr, 0, chan, 0);
alt_release_stroke = find_pb(tr, 0, chan, 1);
}
is_keystroke = 1;
} else {
@ -1007,18 +1117,16 @@ start_translation(translation *tr, char *which_key)
fprintf(stderr, "zero or negative step size not permitted here: [%s]%s\n", current_translation, which_key);
return 1;
}
first_stroke = &(tr->pbs[k][chan][dir>0]);
first_stroke = find_pbs(tr, k, chan, dir>0, step);
if (is_anyshift) {
alt_press_stroke = &(tr->pbs[0][chan][0]);
alt_press_stroke = find_pbs(tr, 0, chan, dir>0, step);
}
tr->pb_step[k][chan][dir>0] = step;
if (!dir) {
is_bidirectional = 1;
release_first_stroke = &(tr->pbs[k][chan][1]);
release_first_stroke = find_pbs(tr, k, chan, 1, step);
if (is_anyshift) {
alt_release_stroke = &(tr->pbs[0][chan][1]);
alt_release_stroke = find_pbs(tr, 0, chan, 1, step);
}
tr->pb_step[k][chan][1] = step;
}
}
break;
@ -1324,6 +1432,7 @@ read_config_file(void)
}
s[1] = '\0';
}
finish_translation_section(tr);
tr = new_translation_section(name, regex);
continue;
}
@ -1432,6 +1541,7 @@ read_config_file(void)
}
finish_translation();
}
finish_translation_section(tr);
fclose(f);
return 1;