ALSA has some support for MIDI devices by a sequencer API. Clients can send MIDI events to the sequencer and it will play them according to the timing of the events. Other clients can then receive these sequenced events and, for example, synthesize them.
ALSA suplies a sequencer that can receive MIDI events
from one set of clients and play them
according to the timing information in the events to other clients.
The clients
that can send such events are file readers such as aplaymidi
or other sequencers. Clients can also read events as they should be played.
Possible consuming clients include splitters, routers or soft synthesizers such as
Timidity.
Timidity can be run as ALSA sequencer client which will consume MIDI events and synthesize them. From The TiMidity Howto - Using TiMidity as the ALSA sequencer client
timidity -iA -B2,8 -Os -EFreverb=0
On my computer this produced
Requested buffer size 2048, fragment size 1024
ALSA pcm 'default' set buffer size 2048, period size 680 bytes
TiMidity starting in ALSA server mode
Opening sequencer port: 129:0 129:1 129:2 129:3
and then sat there waiting for a connection to be made.
FluidSynth can also be used as a server ( Ted's Linux MIDI Guide ):
fluidsynth --server --audio-driver=alsa -C0 -R1 -l /usr/share/soundfonts/FluidR3_GM.sf2
The ALSA sequencer sends MIDI "wire" events. This does not include
MIDI file events such as Text or Lyric Meta-events. This makes it
pretty useless for a Karaoke player. It is possible to modify the
file reader aplaymid to send Meta Events to, say,
a listener (like the Java MetaEventListener), but as these come
from the file reader rather than the sequencer they generally arrive well
before the time they will get sequenced to be played. Pity.
Programs such as pykaraoke make use of the ALSA sequencer.
However, in order to get the timing of the lyrics right it includes
a MIDI file parser and basically acts as a second sequencer just to
extract and display the Text/Lyric events.
the program aconnect.c can be used to test for sequencer servers
and clients such as sequencers.
I have set two clients running: Timidity and
seqdemo (discussed later). The command
aconnect -o
shows
client 14: 'Midi Through' [type=kernel]
0 'Midi Through Port-0'
client 128: 'TiMidity' [type=user]
0 'TiMidity port 0 '
1 'TiMidity port 1 '
2 'TiMidity port 2 '
3 'TiMidity port 3 '
client 129: 'ALSA Sequencer Demo' [type=user]
0 'ALSA Sequencer Demo'
When run with the -i option it produces
$aconnect -i
client 0: 'System' [type=kernel]
0 'Timer '
1 'Announce '
client 14: 'Midi Through' [type=kernel]
0 'Midi Through Port-0'
The program aconnect can establish a connection
between input and output clients by
aconnect in out
The code for aconnect.c is from SourceArchive
/*
** connect / disconnect two subscriber ports
** ver.0.1.3
**
** Copyright (C) 1999 Takashi Iwai
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License version 2 as
** published by the Free Software Foundation.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
**/
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdarg.h>
#include <sys/ioctl.h>
#include <alsa/asoundlib.h>
static void error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...)
{
va_list arg;
if (err == ENOENT) /* Ignore those misleading "warnings" */
return;
va_start(arg, fmt);
fprintf(stderr, "ALSA lib %s:%i:(%s) ", file, line, function);
vfprintf(stderr, fmt, arg);
if (err)
fprintf(stderr, ": %s", snd_strerror(err));
putc('\n', stderr);
va_end(arg);
}
static void usage(void)
{
fprintf(stderr, "aconnect - ALSA sequencer connection manager\n");
fprintf(stderr, "Copyright (C) 1999-2000 Takashi Iwai\n");
fprintf(stderr, "Usage:\n");
fprintf(stderr, " * Connection/disconnection between two ports\n");
fprintf(stderr, " aconnect [-options] sender receiver\n");
fprintf(stderr, " sender, receiver = client:port pair\n");
fprintf(stderr, " -d,--disconnect disconnect\n");
fprintf(stderr, " -e,--exclusive exclusive connection\n");
fprintf(stderr, " -r,--real # convert real-time-stamp on queue\n");
fprintf(stderr, " -t,--tick # convert tick-time-stamp on queue\n");
fprintf(stderr, " * List connected ports (no subscription action)\n");
fprintf(stderr, " aconnect -i|-o [-options]\n");
fprintf(stderr, " -i,--input list input (readable) ports\n");
fprintf(stderr, " -o,--output list output (writable) ports\n");
fprintf(stderr, " -l,--list list current connections of each port\n");
fprintf(stderr, " * Remove all exported connections\n");
fprintf(stderr, " -x, --removeall\n");
}
/*
* * check permission (capability) of specified port
* */
#define LIST_INPUT 1
#define LIST_OUTPUT 2
#define perm_ok(pinfo,bits) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits))
static int check_permission(snd_seq_port_info_t *pinfo, int perm)
{
if (perm) {
if (perm & LIST_INPUT) {
if (perm_ok(pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ))
goto __ok;
}
if (perm & LIST_OUTPUT) {
if (perm_ok(pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE))
goto __ok;
}
return 0;
}
__ok:
if (snd_seq_port_info_get_capability(pinfo) & SND_SEQ_PORT_CAP_NO_EXPORT)
return 0;
return 1;
}
/*
* * list subscribers of specified type
* */
static void list_each_subs(snd_seq_t *seq, snd_seq_query_subscribe_t *subs, int type, const char *msg)
{
int count = 0;
snd_seq_query_subscribe_set_type(subs, type);
snd_seq_query_subscribe_set_index(subs, 0);
while (snd_seq_query_port_subscribers(seq, subs) >= 0) {
const snd_seq_addr_t *addr;
if (count++ == 0)
printf("\t%s: ", msg);
else
printf(", ");
addr = snd_seq_query_subscribe_get_addr(subs);
printf("%d:%d", addr->client, addr->port);
if (snd_seq_query_subscribe_get_exclusive(subs))
printf("[ex]");
if (snd_seq_query_subscribe_get_time_update(subs))
printf("[%s:%d]",
(snd_seq_query_subscribe_get_time_real(subs) ? "real" : "tick"),
snd_seq_query_subscribe_get_queue(subs));
snd_seq_query_subscribe_set_index(subs, snd_seq_query_subscribe_get_index(subs) + 1);
}
if (count > 0)
printf("\n");
}
/*
* * list subscribers
* */
static void list_subscribers(snd_seq_t *seq, const snd_seq_addr_t *addr)
{
snd_seq_query_subscribe_t *subs;
snd_seq_query_subscribe_alloca(&subs);
snd_seq_query_subscribe_set_root(subs, addr);
list_each_subs(seq, subs, SND_SEQ_QUERY_SUBS_READ, "Connecting To");
list_each_subs(seq, subs, SND_SEQ_QUERY_SUBS_WRITE, "Connected From");
}
/*
* * search all ports
* */
typedef void (*action_func_t)(snd_seq_t *seq, snd_seq_client_info_t *cinfo, snd_seq_port_info_t *pinfo, int count);
static void do_search_port(snd_seq_t *seq, int perm, action_func_t do_action)
{
snd_seq_client_info_t *cinfo;
snd_seq_port_info_t *pinfo;
int count;
snd_seq_client_info_alloca(&cinfo);
snd_seq_port_info_alloca(&pinfo);
snd_seq_client_info_set_client(cinfo, -1);
while (snd_seq_query_next_client(seq, cinfo) >= 0) {
/* reset query info */
snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
snd_seq_port_info_set_port(pinfo, -1);
count = 0;
while (snd_seq_query_next_port(seq, pinfo) >= 0) {
if (check_permission(pinfo, perm)) {
do_action(seq, cinfo, pinfo, count);
count++;
}
}
}
}
static void print_port(snd_seq_t *seq, snd_seq_client_info_t *cinfo,
snd_seq_port_info_t *pinfo, int count)
{
if (! count) {
printf("client %d: '%s' [type=%s]\n",
snd_seq_client_info_get_client(cinfo),
snd_seq_client_info_get_name(cinfo),
(snd_seq_client_info_get_type(cinfo) == SND_SEQ_USER_CLIENT ? "user" : "kernel"));
}
printf(" %3d '%-16s'\n",
snd_seq_port_info_get_port(pinfo),
snd_seq_port_info_get_name(pinfo));
}
static void print_port_and_subs(snd_seq_t *seq, snd_seq_client_info_t *cinfo,
snd_seq_port_info_t *pinfo, int count)
{
print_port(seq, cinfo, pinfo, count);
list_subscribers(seq, snd_seq_port_info_get_addr(pinfo));
}
/*
* * remove all (exported) connections
* */
static void remove_connection(snd_seq_t *seq, snd_seq_client_info_t *cinfo,
snd_seq_port_info_t *pinfo, int count)
{
snd_seq_query_subscribe_t *query;
snd_seq_query_subscribe_alloca(&query);
snd_seq_query_subscribe_set_root(query, snd_seq_port_info_get_addr(pinfo));
snd_seq_query_subscribe_set_type(query, SND_SEQ_QUERY_SUBS_READ);
snd_seq_query_subscribe_set_index(query, 0);
for (; snd_seq_query_port_subscribers(seq, query) >= 0;
snd_seq_query_subscribe_set_index(query, snd_seq_query_subscribe_get_index(query) + 1)) {
snd_seq_port_info_t *port;
snd_seq_port_subscribe_t *subs;
const snd_seq_addr_t *sender = snd_seq_query_subscribe_get_root(query);
const snd_seq_addr_t *dest = snd_seq_query_subscribe_get_addr(query);
snd_seq_port_info_alloca(&port);
if (snd_seq_get_any_port_info(seq, dest->client, dest->port, port) < 0)
continue;
if (!(snd_seq_port_info_get_capability(port) & SND_SEQ_PORT_CAP_SUBS_WRITE))
continue;
if (snd_seq_port_info_get_capability(port) & SND_SEQ_PORT_CAP_NO_EXPORT)
continue;
snd_seq_port_subscribe_alloca(&subs);
snd_seq_port_subscribe_set_queue(subs, snd_seq_query_subscribe_get_queue(query));
snd_seq_port_subscribe_set_sender(subs, sender);
snd_seq_port_subscribe_set_dest(subs, dest);
snd_seq_unsubscribe_port(seq, subs);
}
snd_seq_query_subscribe_set_type(query, SND_SEQ_QUERY_SUBS_WRITE);
snd_seq_query_subscribe_set_index(query, 0);
for (; snd_seq_query_port_subscribers(seq, query) >= 0;
snd_seq_query_subscribe_set_index(query, snd_seq_query_subscribe_get_index(query) + 1)) {
snd_seq_port_info_t *port;
snd_seq_port_subscribe_t *subs;
const snd_seq_addr_t *dest = snd_seq_query_subscribe_get_root(query);
const snd_seq_addr_t *sender = snd_seq_query_subscribe_get_addr(query);
snd_seq_port_info_alloca(&port);
if (snd_seq_get_any_port_info(seq, sender->client, sender->port, port) < 0)
continue;
if (!(snd_seq_port_info_get_capability(port) & SND_SEQ_PORT_CAP_SUBS_READ))
continue;
if (snd_seq_port_info_get_capability(port) & SND_SEQ_PORT_CAP_NO_EXPORT)
continue;
snd_seq_port_subscribe_alloca(&subs);
snd_seq_port_subscribe_set_queue(subs, snd_seq_query_subscribe_get_queue(query));
snd_seq_port_subscribe_set_sender(subs, sender);
snd_seq_port_subscribe_set_dest(subs, dest);
snd_seq_unsubscribe_port(seq, subs);
}
}
static void remove_all_connections(snd_seq_t *seq)
{
do_search_port(seq, 0, remove_connection);
}
/*
* * main..
* */
enum {
SUBSCRIBE, UNSUBSCRIBE, LIST, REMOVE_ALL
};
static struct option long_option[] = {
{"disconnect", 0, NULL, 'd'},
{"input", 0, NULL, 'i'},
{"output", 0, NULL, 'o'},
{"real", 1, NULL, 'r'},
{"tick", 1, NULL, 't'},
{"exclusive", 0, NULL, 'e'},
{"list", 0, NULL, 'l'},
{"removeall", 0, NULL, 'x'},
{NULL, 0, NULL, 0},
};
int main(int argc, char **argv)
{
int c;
snd_seq_t *seq;
int queue = 0, convert_time = 0, convert_real = 0, exclusive = 0;
int command = SUBSCRIBE;
int list_perm = 0;
int client;
int list_subs = 0;
snd_seq_port_subscribe_t *subs;
snd_seq_addr_t sender, dest;
while ((c = getopt_long(argc, argv, "dior:t:elx", long_option, NULL)) != -1) {
switch (c) {
case 'd':
command = UNSUBSCRIBE;
break;
case 'i':
command = LIST;
list_perm |= LIST_INPUT;
break;
case 'o':
command = LIST;
list_perm |= LIST_OUTPUT;
break;
case 'e':
exclusive = 1;
break;
case 'r':
queue = atoi(optarg);
convert_time = 1;
convert_real = 1;
break;
case 't':
queue = atoi(optarg);
convert_time = 1;
convert_real = 0;
break;
case 'l':
list_subs = 1;
break;
case 'x':
command = REMOVE_ALL;
break;
default:
usage();
exit(1);
}
}
if (snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0) {
fprintf(stderr, "can't open sequencer\n");
return 1;
}
snd_lib_error_set_handler(error_handler);
switch (command) {
case LIST:
do_search_port(seq, list_perm,
list_subs ? print_port_and_subs : print_port);
snd_seq_close(seq);
return 0;
case REMOVE_ALL:
remove_all_connections(seq);
snd_seq_close(seq);
return 0;
}
/* connection or disconnection */
if (optind + 2 > argc) {
snd_seq_close(seq);
usage();
exit(1);
}
if ((client = snd_seq_client_id(seq)) < 0) {
snd_seq_close(seq);
fprintf(stderr, "can't get client id\n");
return 1;
}
/* set client info */
if (snd_seq_set_client_name(seq, "ALSA Connector") < 0) {
snd_seq_close(seq);
fprintf(stderr, "can't set client info\n");
return 1;
}
/* set subscription */
if (snd_seq_parse_address(seq, &sender, argv[optind]) < 0) {
snd_seq_close(seq);
fprintf(stderr, "invalid sender address %s\n", argv[optind]);
return 1;
}
if (snd_seq_parse_address(seq, &dest, argv[optind + 1]) < 0) {
snd_seq_close(seq);
fprintf(stderr, "invalid destination address %s\n", argv[optind + 1]);
return 1;
}
snd_seq_port_subscribe_alloca(&subs);
snd_seq_port_subscribe_set_sender(subs, &sender);
snd_seq_port_subscribe_set_dest(subs, &dest);
snd_seq_port_subscribe_set_queue(subs, queue);
snd_seq_port_subscribe_set_exclusive(subs, exclusive);
snd_seq_port_subscribe_set_time_update(subs, convert_time);
snd_seq_port_subscribe_set_time_real(subs, convert_real);
if (command == UNSUBSCRIBE) {
if (snd_seq_get_port_subscription(seq, subs) < 0) {
snd_seq_close(seq);
fprintf(stderr, "No subscription is found\n");
return 1;
}
if (snd_seq_unsubscribe_port(seq, subs) < 0) {
snd_seq_close(seq);
fprintf(stderr, "Disconnection failed (%s)\n", snd_strerror(errno));
return 1;
}
} else {
if (snd_seq_get_port_subscription(seq, subs) == 0) {
snd_seq_close(seq);
fprintf(stderr, "Connection is already subscribed\n");
return 1;
}
if (snd_seq_subscribe_port(seq, subs) < 0) {
snd_seq_close(seq);
fprintf(stderr, "Connection failed (%s)\n", snd_strerror(errno));
return 1;
}
}
snd_seq_close(seq);
return 0;
}
The code for seqdemo.c is
/* seqdemo.c by Matthias Nagorni */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <alsa/asoundlib.h>
snd_seq_t *open_seq();
void midi_action(snd_seq_t *seq_handle);
snd_seq_t *open_seq() {
snd_seq_t *seq_handle;
int portid;
if (snd_seq_open(&seq_handle, "default", SND_SEQ_OPEN_INPUT, 0) < 0) {
fprintf(stderr, "Error opening ALSA sequencer.\n");
exit(1);
}
snd_seq_set_client_name(seq_handle, "ALSA Sequencer Demo");
if ((portid = snd_seq_create_simple_port(seq_handle, "ALSA Sequencer Demo",
SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE,
SND_SEQ_PORT_TYPE_APPLICATION)) < 0) {
fprintf(stderr, "Error creating sequencer port.\n");
exit(1);
}
return(seq_handle);
}
void midi_action(snd_seq_t *seq_handle) {
snd_seq_event_t *ev;
do {
snd_seq_event_input(seq_handle, &ev);
switch (ev->type) {
case SND_SEQ_EVENT_CONTROLLER:
fprintf(stderr, "Control event on Channel %2d: %5d \r",
ev->data.control.channel, ev->data.control.value);
break;
case SND_SEQ_EVENT_PITCHBEND:
fprintf(stderr, "Pitchbender event on Channel %2d: %5d \r",
ev->data.control.channel, ev->data.control.value);
break;
case SND_SEQ_EVENT_NOTEON:
fprintf(stderr, "Note On event on Channel %2d: %5d \r",
ev->data.control.channel, ev->data.note.note);
break;
case SND_SEQ_EVENT_NOTEOFF:
fprintf(stderr, "Note Off event on Channel %2d: %5d \r",
ev->data.control.channel, ev->data.note.note);
break;
}
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 npfd;
struct pollfd *pfd;
seq_handle = open_seq();
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_action(seq_handle);
}
}
}
The program aplaymidi will play to a backend MIDI synthesizer such as
TiMidity. It requires a port name, which can be found by
aplaymidi -l
with output such as
Port Client name Port name
14:0 Midi Through Midi Through Port-0
128:0 TiMidity TiMidity port 0
128:1 TiMidity TiMidity port 1
128:2 TiMidity TiMidity port 2
128:3 TiMidity TiMidity port 3
131:0 aseqdump aseqdump
The port numbers are the same as those used by aconnect.
These are not the Alsa device names (hw:0, etc) but are special
to the Alsa sequencer API.
It can then play a MIDI file to one of these ports as in
aplaymidi -p 128:0 54154.mid
The code can be found from SourceArchive.com
From RawMidi interface
[The] RawMidi Interface is designed to write or read raw (unchanged) MIDI data over the MIDI line without any timestamps defined in interface.
The raw MIDI interface is typically used to manage hardware
MIDI devices. For example, if I plug in an Edirol SD-20
synthesizer to a USB port, it shows under amidi
as
$amidi -l
Dir Device Name
IO hw:2,0,0 SD-20 Part A
IO hw:2,0,1 SD-20 Part B
I hw:2,0,2 SD-20 MIDI
These names use the same pattern as Alsa playback and record
devices of hw:....
The Linux kernel module snd_virmidi can create
virtual raw MIDI devices. First add the modules
(
Using TiMidity++ with ALSA raw MIDI
and
AlsaMidiOverview)
modprobe snd-seq snd-virmidi
This will bring virtual devices both into the ALSA raw MIDI and into the ALSA sequencer spaces:
$amidi -l
Dir Device Name
IO hw:3,0 Virtual Raw MIDI (16 subdevices)
IO hw:3,1 Virtual Raw MIDI (16 subdevices)
IO hw:3,2 Virtual Raw MIDI (16 subdevices)
IO hw:3,3 Virtual Raw MIDI (16 subdevices)
$aplaymidi -l
Port Client name Port name
14:0 Midi Through Midi Through Port-0
28:0 Virtual Raw MIDI 3-0 VirMIDI 3-0
29:0 Virtual Raw MIDI 3-1 VirMIDI 3-1
30:0 Virtual Raw MIDI 3-2 VirMIDI 3-2
31:0 Virtual Raw MIDI 3-3 VirMIDI 3-3
Some programs/APIs use the Alsa sequencer space; others use the Alsa raw MIDI space. Virtual ports allow a client using one space to use a client from a different space.
For example, TiMidity can be run as a sequencer client by
timidity -iA -B2,8 -Os -EFreverb=0
This only shows in the sequencer space, not in the
raw MIDI space, and
shows to aconnect -o as
$aconnect -o
client 14: 'Midi Through' [type=kernel]
0 'Midi Through Port-0'
client 28: 'Virtual Raw MIDI 3-0' [type=kernel]
0 'VirMIDI 3-0 '
client 29: 'Virtual Raw MIDI 3-1' [type=kernel]
0 'VirMIDI 3-1 '
client 30: 'Virtual Raw MIDI 3-2' [type=kernel]
0 'VirMIDI 3-2 '
client 31: 'Virtual Raw MIDI 3-3' [type=kernel]
0 'VirMIDI 3-3 '
client 128: 'TiMidity' [type=user]
0 'TiMidity port 0 '
1 'TiMidity port 1 '
2 'TiMidity port 2 '
3 'TiMidity port 3 '
while aconnect -i shows the virtual ports as
$aconnect -i
client 0: 'System' [type=kernel]
0 'Timer '
1 'Announce '
client 14: 'Midi Through' [type=kernel]
0 'Midi Through Port-0'
client 28: 'Virtual Raw MIDI 3-0' [type=kernel]
0 'VirMIDI 3-0 '
client 29: 'Virtual Raw MIDI 3-1' [type=kernel]
0 'VirMIDI 3-1 '
client 30: 'Virtual Raw MIDI 3-2' [type=kernel]
0 'VirMIDI 3-2 '
client 31: 'Virtual Raw MIDI 3-3' [type=kernel]
0 'VirMIDI 3-3 '
Virtual Raw MIDI 3-0 can then be connected to TiMidity port 0 by
aconnect 28:0 128:0
Clients can then send MIDI messages to the raw MIDI device
hw:3,0 and TiMidity will synthesize them.
We will use this in the next chapter by showing how
to replace the default
Java synthesizer by TiMidity.
If you have something playing out to a device or softsynth, then if that something gets interrupted, it may not finish playing cleanly. For example, it may have started a NOTE ON on some channel but because of the interrupt will not have sent a note off. The synthesizer will continue to play the note.
To stop it playing, using amidi to send
'raw' MIDI commands. The hexadecimal sequence
"00 B0 7B 00" will send "all notes off on channel zero".
Similarly, the command
"00 B1 7B 00" will send "all notes off on channel one"
and there are only 16 possible channels.
The relevant commands for a raw device on port
hw:1,0 are
amidi -p hw:1,0 -S "00 B0 7B 00"
...
This chapter has briefly discussed the MIDI models available under
Alsa. While there is a substantial programming API behind this,
we have mainly used the commands amidi,
aplaymidi and aconnect.
Copyright © Jan Newmarch, jan@newmarch.name
"Programming and Using Linux Sound - in depth"
by
Jan Newmarch
is licensed under a
Creative Commons Attribution-ShareAlike 4.0 International License
.
Based on a work at
https://jan.newmarch.name/LinuxSound/
.
If you like this book, please contribute using PayPal
Or Flattr me: