/** @file
* Map velocity values of NOTEON events between ALSA MIDI sequencer i/o ports.
*/
#include <getopt.h>
#include <math.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include "alsa.h"
#define NAME "avelmap"
#define LOGS(fmt) (void)fprintf(stderr, NAME ": " fmt "\n")
#define LOGV(fmt, ...) (void)fprintf(stderr, NAME ": " fmt "\n", __VA_ARGS__)
/** Precomputed or loaded tiny mapping of velocity values. */
static unsigned char VELOCITY_MAP[128];
/** Interrupt has been received, so shutdown on next occasion. */
static volatile int SHUTDOWN = 0;
static void signal_handler(int sig) { SHUTDOWN = sig; }
/** Schedule shutdown upon INT, QUIT, or TERM. */
static int register_handlers() {
signal(SIGHUP, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGINT, &signal_handler);
signal(SIGQUIT, &signal_handler);
signal(SIGTERM, &signal_handler);
return 0;
}
/** Fill in the velocity mapping for cheap repeated lookup lateron. */
static void precompute_map(double power, double factor, double offset) {
for (size_t velocity = 0; velocity < sizeof(VELOCITY_MAP) / sizeof(*VELOCITY_MAP); ++velocity) {
VELOCITY_MAP[velocity] = (unsigned char)round(
fmax(0.0, fmin(127.0, pow((double)velocity / 127.0, power) * 127.0 * factor + offset)));
}
}
/** Apply the desired change on the given sequencer event. */
static snd_seq_event_t* convert(snd_seq_event_t* ev, void* ctx) {
const unsigned char* velocity_map = (unsigned char*)ctx;
if (ev->type == SND_SEQ_EVENT_NOTEON && ev->data.note.velocity > 0 && ev->data.note.velocity < 128) {
ev->data.note.velocity = velocity_map[ev->data.note.velocity];
if (ev->data.note.velocity == 0) {
ev->type = SND_SEQ_EVENT_NOTEOFF;
}
}
return ev;
}
static int usage() {
LOGS(
"usage: [-h] [-d] [-p power] [-f factor] [-c offset]\n\n"
"map velocity values of NOTEON events between ALSA MIDI sequencer i/o ports:\n"
"velocity = (((velocity / 127)^power * 127) * factor) + offset\n\n"
"optional arguments:\n"
" -h show this help and exit\n"
" -d debug dump computed velocity mapping: ]0, 127] -> [0, 127]\n"
" -p power velocity power function, for example 0.5 or 2 (default: 1.0)\n"
" -f factor linear scaling factor (default: 1.0)\n"
" -c offset constant offset (default: 0.0)");
return EXIT_FAILURE;
}
int main(int argc, char* const* argv) {
unsigned debug = 0;
double power = 1.0;
double factor = 1.0;
double offset = 0.0;
int opt;
while ((opt = getopt(argc, argv, "hdp:f:c:")) != -1) {
switch (opt) {
case 'd':
debug++;
break;
case 'p':
power = atof(optarg);
break;
case 'f':
factor = atof(optarg);
break;
case 'c':
offset = atof(optarg);
break;
case 'h':
default:
return usage();
}
}
if (optind != argc) {
return usage();
}
LOGV("applying power %f, factor %f, offset %f", power, factor, offset);
precompute_map(power, factor, offset);
if (debug) {
for (int i = 1; i < 128; ++i) {
LOGV("%3d -> %3d", i, VELOCITY_MAP[i]);
}
}
if (register_handlers() != 0) {
return EXIT_FAILURE;
}
alsa_seq_client_t client;
if (client_open(&client, NAME) != 0) {
LOGS("cannot open alsa sequencer client");
return EXIT_FAILURE;
}
const int npfd = snd_seq_poll_descriptors_count(client.handle, POLLIN);
struct pollfd* const pfd = (struct pollfd*)calloc((size_t)npfd, sizeof(struct pollfd));
(void)snd_seq_poll_descriptors(client.handle, pfd, (unsigned)npfd, POLLIN);
LOGV("running as client %d", client.client_id);
while (!SHUTDOWN) {
if (poll(pfd, (unsigned long)npfd, 1000) > 0) {
client_input_loop(&client, &convert, VELOCITY_MAP);
}
}
LOGS("shutting down");
client_close(&client);
free(pfd);
return EXIT_SUCCESS;
}