Programming and Using Linux Sound - in depth - MIDI ALSA
2013-12-17 23:48
645 查看
MIDI ALSA
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.skip table of contents
Show table of contents
Resources
Introduction
aconnect
seqdemo
aplaymidi
Resources
ALSA Sequencer- design document
ALSA Programming HOWTO
has Writing a sequencer client, a MIDI router, combining PCM and MIDI: miniFMsynth and scheduling MIDI events: miniArp
MIDI Sequencer API
Sequencer interface
ALSA Sequencer System
is an in-depth viewof the sequencer system by Takashi Iwai
Introduction
ALSA suplies a sequencer that can receive MIDI events and play them according to the timing information in the events. The clients taht can send such events are file readers such asaplaymidior other sequencers. Clients can also read events as they should be played. Possible clients include splitters, routers or soft synthesizers such as Timidity.
Timidity can be run as ALSA sequencer client. From
The TiMidity Howto - Using TiMidity as the ALSA sequencer client
[code] timidity -iA -B2,8 -Os -EFreverb=0
[/code]
On my computer this produced
[code] 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
[/code]
and then sat there waiting for a connection to be made.
FluidSynth can also be used as a server ( Ted's Linux MIDI Guide
):
[code] fluidsynth --server --audio-driver=alsa -C0 -R1 -l /usr/share/soundfonts/FluidR3_GM.sf2
[/code]
The ALSA sequencer seds MIDI "wire" events. This does not include MIDI file events such as Text or Lyric Meta-events. This makes it pretty useless for a MIDI player. It is possible to modify the file reader
aplaymidto 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
pykaraokemake 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.
aconnect
the programaconnect.ccan 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
[code] aconnect -o
[/code]
shows
[code] 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'
[/code]
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; }
seqdemo
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); } } }
aplaymidi
The programaplaymidiwill play to a backend MIDI synthesizer such as
TiMidity. It requires a port name, which can be found by
[code] aplaymidi -l
[/code]
with output such as
[code] 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
[/code]
It can then play a MIDI file to one of these ports as in
[code] aplaymidi -p 128:0 54154.mid
[/code]
The code can be found from
SourceArchive.com
相关文章推荐
- Programming and Using Linux Sound - in depth - Chapter 5 ALSA
- ALSA docs - Programming and Using Linux Sound - in depth
- Analysing the ASM code of a simple sample of C programming language at Linux Platform by GCC and GDB
- Installing and using Net-SNMP On Linux
- Linux System Programming: Talking Directly to the Kernel and, C Library [ILLUSTRATED] (Paperback) Sep.2007.eBook-BBL
- Programming: Principles and Practice Using C++
- How to install LAMP (Apache, PHP and MySQL in Linux) using Yum
- HTTP Live Streaming with a Webcam on Linux using VLC and a Segmenter
- Oracle 10g Release 2 RAC On RHEL4 Linux Using VMware ESX Server and NFS
- 《Shell Programming in Unix, Linux and OS X, 4th Edition 》 笔记之Can I Quote You On That?
- How To Improve Laptop Battery Life And Usage In Linux Using TLP
- How To Install Or Upgrade LAMP: Linux, Apache, MySQL and PHP Stack Using Yum
- iOS Programming 101: Record and Play Audio using AVFoundation Framework
- Read and Write to a Keyboard device in Linux using C++
- 《Shell Programming in Unix, Linux and OS X, 4th Edition 》 笔记之Decisions,Decisions
- Asynchronous Programming in C# 5.0 using async and await
- Adding Linux VLAN and bridge interfaces using libvirt
- Installing and using Net-SNMP On Linux
- How To Setup Partitioned Linux Block Devices Using UDEV (Non-ASMLIB) And Assign Them To ASM
- C Socket Programming for Linux with a Server and Client Example Code