ALSA rend très facile l'implementation de n'importe quel routeur MIDI que vous pouvez imaginer. midiroute.c fournit un exemple simple.
/* midiroute.c by Matthias Nagorni */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <alsa/asoundlib.h> #define MAX_MIDI_PORTS 4 int open_seq(snd_seq_t **seq_handle, int in_ports[], int out_ports[], int num_in, int num_out); void midi_route(snd_seq_t *seq_handle, int out_ports[], int split_point); /* Open ALSA sequencer wit num_in writeable ports and num_out readable ports. */ /* The sequencer handle and the port IDs are returned. */ int open_seq(snd_seq_t **seq_handle, int in_ports[], int out_ports[], int num_in, int num_out) { int l1; char portname[64]; if (snd_seq_open(seq_handle, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0) { fprintf(stderr, "Error opening ALSA sequencer.\n"); return(-1); } snd_seq_set_client_name(*seq_handle, "MIDI Router"); for (l1 = 0; l1 < num_in; l1++) { sprintf(portname, "MIDI Router IN %d", l1); if ((in_ports[l1] = snd_seq_create_simple_port(*seq_handle, portname, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, SND_SEQ_PORT_TYPE_APPLICATION)) < 0) { fprintf(stderr, "Error creating sequencer port.\n"); return(-1); } } for (l1 = 0; l1 < num_out; l1++) { sprintf(portname, "MIDI Router OUT %d", l1); if ((out_ports[l1] = snd_seq_create_simple_port(*seq_handle, portname, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, SND_SEQ_PORT_TYPE_APPLICATION)) < 0) { fprintf(stderr, "Error creating sequencer port.\n"); return(-1); } } return(0); } /* Read events from writeable port and route them to readable port 0 */ /* if NOTEON / OFF event with note < split_point. NOTEON / OFF events */ /* with note >= split_point are routed to readable port 1. All other */ /* events are routed to both readable ports. */ void midi_route(snd_seq_t *seq_handle, int out_ports[], int split_point) { snd_seq_event_t *ev; do { snd_seq_event_input(seq_handle, &ev); snd_seq_ev_set_subs(ev); snd_seq_ev_set_direct(ev); if ((ev->type == SND_SEQ_EVENT_NOTEON)||(ev->type == SND_SEQ_EVENT_NOTEOFF)) { if (ev->data.note.note < split_point) { snd_seq_ev_set_source(ev, out_ports[0]); } else { snd_seq_ev_set_source(ev, out_ports[1]); } snd_seq_event_output_direct(seq_handle, ev); } else { snd_seq_ev_set_source(ev, out_ports[0]); snd_seq_event_output_direct(seq_handle, ev); snd_seq_ev_set_source(ev, out_ports[1]); snd_seq_event_output_direct(seq_handle, ev); } snd_seq_free_event(ev); } while (snd_seq_event_input_pending(seq_handle, 0) > 0); } int main(int argc, char *argv[]) { snd_seq_t *seq_handle; int in_ports[MAX_MIDI_PORTS], out_ports[MAX_MIDI_PORTS]; int npfd, split_point; struct pollfd *pfd; if (argc < 2) { fprintf(stderr, "\nmidiroute <split_point>\n\n"); exit(1); } else { split_point = atoi(argv[1]); } if (open_seq(&seq_handle, in_ports, out_ports, 1, 2) < 0) { fprintf(stderr, "ALSA Error.\n"); exit(1); } npfd = snd_seq_poll_descriptors_count(seq_handle, POLLIN); pfd = (struct pollfd *)alloca(npfd * sizeof(struct pollfd)); snd_seq_poll_descriptors(seq_handle, pfd, npfd, POLLIN); while (1) { if (poll(pfd, npfd, 100000) > 0) { midi_route(seq_handle, out_ports, split_point); } } }
Le programme crée un port d'entré et deux ports de sortie, il récupére le paramètre split_point. Les événements d'entré de type note qui ont la note < split_point sont routés au premier port de sortie, tous les autres événements de note sont conduits au deuxième port de sortie. Tous les autres événements (les événements de controle, de pitch,...) sont routés simultanément vers les deux ports de sortie.
Naturellement vous pouvez facilement modifier ceci de diverses manières en changeant seulement la condition "if" de la fonction midi_route. Vous pouvez avoir différent instrument joué en fonction de la vélocité, regardons comment cela fonctionne.
A)
int open_seq(snd_seq_t **seq_handle, int in_ports[], int out_ports[], int num_in, int num_out)
Ici, nous ouvrons le séquenceur ALSA avec
snd_seq_open_duplex
Puisque nous voulons lire et écrire des événements MIDI, nous avons besoin des ID des ports pour pouvoir distinguer les différents ports de sortie, et nous devons stocker la valeur de retour de snd_seq_create_simple_port, dans cette partie les ports de sortie sont également créés .
Pour que d'autres clients puissent lire à partir de ces sorties, nous devons permettre l'accès read et l'accés read subscription. Il n'est pas nécessaire de permettre l'accès write, bien que l'application elle-même écrive dans le port de sortie. Actuellement, un client peut toujours accéder aux ports sans permission explicite.
void midi_route(snd_seq_t *seq_handle, int out_ports[], int pick_channel)
Comme dans l'exemple précédent le snd_seq_event_input est utilisé pour lire des événements à partir de l'entrée du buffer. Maintenant nous voudrions router l'événement à un des ports de sortie. Puisqu'il est plus flexible de permettre arbitrairement aux clients de souscrire aux ports de sortie et de recevoir les événements, nous n'indiquons pas explicitement la destination avec
snd_seq_ev_set_dest
Au lieu de cela, nous utilisons
snd_seq_ev_set_source
pour créer l'événement qui provient du port de sortie désiré. La fonction
snd_seq_ev_set_subs
défini tous les "abonnés" des port de sortie. Nous voulons produire l'événement aussi rapidement que possible, par conséquent, nous utiliserons
snd_seq_ev_set_direct
pour contourner la file d'attente d'événements, ceci défini la file d'attente pour l'événement snd_seq_queue_direct. Puis nous utilisons snd_seq_event_output_direct pour la sortie unbuffered de l'événement.
En modifiant les arguments de la condition "if", vous pouvez facilement mettre en application une routeur différent, par exemple
if (ev->data.control.channel < split_channel) /*channel splitting*/ if (ev->data.note.velocity < threshold) velocity dependence*/ if (ev->type == SND_SEQ_EVENT_CONTROLLER) /*extract controller events*/