LinuxでMIDIを扱うALSAのインターフェースは2種類あります。rawmidiとMIDIシーケンサーです。 これらのインターフェースを使ってMIDIの送信、受信するプログラムを作ってみます。 Raspberry Pi でも使えます。
ALSA のrawmidiインターフェースを使ったC言語のプログラムです。Raspberry Piで試しています。 情報源があまりなく、ALSA ProjectのExampleを参照して作成しました。 (MIDIシーケンサーを使ったプログラムは後で記載します。)
下のプログラムは、もっとも単純にamidi -lで取得した2つのMIDIハードウェア、midiinportから受け取ったMIDIデータを、midioutportに出力します。このプログラムではMIDIのデータを一切解析していません。受け取ったバイトデータをそのまま出力しています。
// // Description: Read ALSA Raw MIDI input and write Raw MIDI output // rawinout.c // #include <alsa/asoundlib.h> /* for alsa interface */ #include <unistd.h> /* for sleep() function */ #include <sys/types.h> #include <sys/select.h> #include <sys/stat.h> void rawmidithrough(); int npfds; struct pollfd *pfds; struct pollfd qfds; snd_rawmidi_t *midiinport = NULL; snd_rawmidi_t *midioutport = NULL; int main(int ac, char *av[]) { int status; int mode = SND_RAWMIDI_SYNC; const char *inportname = "hw:2,0,0"; const char *outportname = "hw:3,0,0"; if(ac>3){ inportname = av[1]; outportname = av[1]; } fprintf(stderr,"input poart=%s\n",inportname); fprintf(stderr,"output poart=%s\n",outportname); if ((status = snd_rawmidi_open(&midiinport, NULL, inportname, mode)) < 0) { fprintf(stderr, "Problem opening MIDI input: %s", snd_strerror(status)); exit(1); } if ((status = snd_rawmidi_open(NULL, &midioutport, outportname, mode)) < 0) { fprintf(stderr, "Problem opening MIDI input: %s", snd_strerror(status)); exit(1); } /* デバイスがすでに別のアプリケーションで占有されている場合に -EBUSYエラーを返します */ snd_rawmidi_nonblock(midiinport, 1); snd_rawmidi_nonblock(midioutport, 1); /* RawMidiハンドルのポーリング記述子の数を取得します */ npfds = snd_rawmidi_poll_descriptors_count(midiinport); pfds = (struct pollfd *)alloca(npfds * sizeof(struct pollfd)); /* ポーリング記述子を取得します */ status = snd_rawmidi_poll_descriptors(midiinport, pfds, POLLIN); while (1) { rawmidithrough(); } return 0; } /* RAW MIDI IN -> RAW MIDI OUT */ void rawmidithrough() { unsigned char buffer[16]; unsigned short reevents; int err; while (1) { /* ポーリング記述子から返されたイベントを取得します */ /* 成功した場合はゼロ、負の場合はエラーコード */ if ((err = snd_rawmidi_poll_descriptors_revents(midiinport, &qfds, npfds, &reevents)) < 0) { fprintf(stderr, "polling error =%d\n", err); exit(1); } /* MIDIストリームからMIDIバイトを読み取る */ /* 成功した場合はゼロ、負の場合はエラーコード */ /* 受信バッファーが空だと-11が返ってくる */ if ((err = snd_rawmidi_read(midiinport, buffer, 1)) < 0) { return; } /* MIDIバイトをMIDIストリームに書き込みます */ if ((err = snd_rawmidi_write(midioutport,buffer,1)) < 0) { fprintf(stderr, "raw midi write error =%d\n", err); exit(1); } else { fprintf(stderr,"%02x ",buffer[0]); } } /* rawmidi I/O のリングバッファのすべてのバイトを排出します */ snd_rawmidi_drain(midioutport); return; }
amidiを打ち込むとRAW MIDIデバイスが表示されたとします。
> amidi -l Dir Device Name IO hw:2,0,0 nanoKEY2 MIDI 1 IO hw:3,0,0 NSX-39 MIDI 1
コンパイルして実行する時は以下のようにコマンドを打ち込みます。以下の例ではhw:2,0,0から受け取ったMIDIを、hw:3,0,0へ出力します。
>gcc -o rawinout rawinout.c -lasound >./rawinout hw:2,0,0 hw:3,0,0
ALSA MIDIシーケンサーを使ったMIDI スルーのプログラムです。ALSA MIDIシーケンサーは、 上のALSA rawmidiインターフェースとは別なインターフェースのようです。 こちらは、後で記載しますaconnect というコマンドでMIDIの入出力をつなぐことができます。
// // MIDI through using sequencer interface // midiseqinout.c // #include <alsa/asoundlib.h> /* for alsa interface */ #include <unistd.h> #include <sys/types.h> #include <sys/select.h> static snd_seq_t *seq_handle; /* sequencer handle */ static int iportid; /* input port */ static int oportid; /* output ports */ int main() { snd_seq_event_t *ev; int npfd; struct pollfd *pfd; int i; if (snd_seq_open(&seq_handle, "hw", SND_SEQ_OPEN_DUPLEX, 0) $lt; 0) { fprintf(stderr, "Error opening ALSA sequencer.\n"); exit(1); } /* set client name */ snd_seq_set_client_name(seq_handle, "MIDI Client"); /* open input port */ if ((iportid = snd_seq_create_simple_port(seq_handle, "TEST IN", SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE, SND_SEQ_PORT_TYPE_APPLICATION)) $lt; 0) { fprintf(stderr, "fatal error: could not open input port.\n"); exit(1); } /* open output ports */ if ((oportid = snd_seq_create_simple_port(seq_handle, "TEST OUT", SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, SND_SEQ_PORT_TYPE_APPLICATION)) $lt; 0) { fprintf(stderr, "fatal error: could not open output port.\n"); exit(1); } /* ポーリング記述子の数を取得します。チェックするポーリングイベントは、 2番目の引数で指定できます。入力と出力の両方をチェックする場合は、 POLLIN | POLLOUTを渡します。ここでは入力をチェックしています。*/ npfd = snd_seq_poll_descriptors_count(seq_handle, POLLIN); fprintf(stderr,"poll_descriptors=%d\n",npfd); pfd = (struct pollfd *)alloca(npfd * sizeof(struct pollfd)); /* シーケンサーハンドルに割り当てられたポーリング記述子を取得します。 シーケンサーハンドルはストリームを二重化できるため、イベント引数で ポーリングする方向を設定する必要があります。POLLINビットを指定すると、 ポートへの着信イベントがチェックされます。*/ snd_seq_poll_descriptors(seq_handle, pfd, npfd, POLLIN); while (1) /* main loop */ /* poll関数はファイルディスクリプタ集合のいずれか一つがI/O を実行可能な 状態になるのを待待ちます。監視するファイルディスクリプタ集合はpfd引き数で 指定します。1000000 はtimeout引き数で、poll ()が停止する時間の上限を設定します。 ミリ秒単位で指定します。*/ if (poll(pfd, npfd, 1000000) > 0) { do { /* シーケンサーから入力イベントを取得します。*/ snd_seq_event_input(seq_handle, &ev); /* このマクロは、送信元ポートID番号を設定します。*/ snd_seq_ev_set_source(ev, oportid); /*このマクロは、宛先をサブスクライバーとして設定します。*/ snd_seq_ev_set_subs(ev); /*このマクロは、イベントを直接通過モードに設定して、 キューに入れずにすぐに配信されるようにします。 */ snd_seq_ev_set_direct(ev); /* この関数は、出力バッファーを介さずに直接シーケンサーにイベントを送信します。 イベントが可変長イベントの場合、alsa-lib内に一時バッファーが割り当てられ、 実際に送信される前にデータがそこにコピーされます。*/ snd_seq_event_output_direct(seq_handle, ev); /*以前のバージョンでは、この関数はsnd_seq_event_input()によって 割り当てられたイベントポインタを解放するために使用されていました。 現在のバージョンでは、イベントレコードが割り当てられていないため、 この関数を呼び出す必要はありません。*/ snd_seq_free_event(ev); } while (snd_seq_event_input_pending(seq_handle, 0) > 0); } return 0; }
コンパイルして実行する時は以下のようにコマンドを打ち込みます。
>gcc -o midiseqinout midiseqinout.c -lasound >./midiseqinout
接続しているMIDI Sequencerをリストアップします。 以下の場合は5つのクライアントが接続されていることがわかります。
>aconnect -l client 0: 'System' [type=kernel] 0 'Timer ' 1 'Announce ' client 14: 'Midi Through' [type=kernel] 0 'Midi Through Port-0' client 24: 'nanoKEY2' [type=kernel,card=2] 0 'nanoKEY2 MIDI 1 ' client 28: 'NSX-39' [type=kernel,card=3] 0 'NSX-39 MIDI 1 ' client 128: 'MIDI Client' [type=user,pid=9080] 0 'TEST IN ' 1 'TEST OUT '
MIDI Clientが今回実行しているmidiseqinoutが生成しているクライアントです。 client 24のnanoKEY2を弾いてclient 28のNSX-39を鳴らす設定はMIDI Clientを使わない場合は
>aconnect 24:0 28:0で音が鳴ります。以下のコマンドで一旦接続を切ります。
>aconnect -x
nanoKEY2 -> MIDI Client -> NSX-39 とMIDIを渡したい場合は次のように設定します。
>aconnect 24:0 128:0 >aconnect 128:1 28:0
ALSA MIDI シーケンサープログラムでシステムエクスクルーシブを送信するためのプログラムです。 何かキーを押すことでシステムエクスクルーシブが出力されます。 肝はイベント(ev)をクリアすることとフラグをクリア&セットすることです。
#include <alsa/asoundlib.h> /* for alsa interface */ #include <unistd.h> /* for sleep() function */ #include <sys/types.h> #include <sys/select.h> static snd_seq_t *seq_handle; /* input port */ static int oportid; /* output ports */ static int portid; /* input port */ int main(int ac, char *av[]) { snd_seq_event_t ev; int npfd; struct pollfd *pfd; char ch; unsigned char sysexdata[10]={0xF0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xF7}; if (snd_seq_open(&seq_handle, "hw", SND_SEQ_OPEN_DUPLEX, 0) < 0) { fprintf(stderr, "Error opening ALSA sequencer.\n"); exit(1); } snd_seq_set_client_name(seq_handle, "SYSEXOUT"); /* open one input port */ if ((portid = snd_seq_create_simple_port(seq_handle, "Input", SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE, SND_SEQ_PORT_TYPE_APPLICATION)) < 0) { fprintf(stderr, "fatal error: could not open input port.\n"); exit(1); } if ((oportid = snd_seq_create_simple_port(seq_handle, "Output", SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, SND_SEQ_PORT_TYPE_APPLICATION)) < 0) { fprintf(stderr, "fatal error: could not open output port.\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(ch = getchar()){ snd_seq_ev_clear(&ev); ev.flags &= ~SND_SEQ_EVENT_LENGTH_MASK; ev.flags |= SND_SEQ_EVENT_LENGTH_VARIABLE; ev.type=SND_SEQ_EVENT_SYSEX; ev.data.ext.len=10; ev.data.ext.ptr=(void*)sysexdata; snd_seq_ev_set_source(&ev, oportid); snd_seq_ev_set_subs(&ev); snd_seq_ev_set_direct(&ev); snd_seq_event_output_direct(seq_handle, &ev); } } return 0; }
ALSA MIDIシーケンサーのプログラムです。 パーサーはMIDIを受信した時、その内容(イベント)を分析してそれぞれの処理を選びたい時に使う機能です。 ここでは、ALSA MIDIシーケンサーのイベントの構造体snd_seq_event_tを分析しています。 snd_seq_event_tの説明は、 「ALSA project - the C library reference」に記載があります。 若干、構造が複雑なので後に構造体の構造を記載しておきます。
snd_seq_event_t *ev; do { snd_seq_event_input(seq_handle, &ev); switch (ev->type) { case SND_SEQ_EVENT_NOTEOFF: //0x80 fprintf(stderr, "%02x %02x %02x\n", 0x80+ev->data.control.channel, ev->data.note.note, ev->data.note.velocity); break; case SND_SEQ_EVENT_NOTEON: //0x90 fprintf(stderr, "%02x %02x %02x\n", 0x90+ev->data.control.channel, ev->data.note.note, ev->data.note.velocity); break; case SND_SEQ_EVENT_KEYPRESS: //0xA0 fprintf(stderr, "%02x %02x %02x\n", 0xA0+ev->data.control.channel, ev->data.note.note, ev->data.note.velocity); break; case SND_SEQ_EVENT_CONTROLLER: //0xB0 fprintf(stderr, "%02x %02x %02x\n", 0xB0+ev->data.control.channel, ev->data.control.param, ev->data.control.value); break; case SND_SEQ_EVENT_PGMCHANGE: //0xC0 fprintf(stderr, "%02x %02x\n", 0xC0+ev->data.control.channel, ev->data.control.value); break; case SND_SEQ_EVENT_CHANPRESS: //0xD0 fprintf(stderr, "%02x %02x\n", 0xD0+ev->data.control.channel, ev->data.control.value); break; case SND_SEQ_EVENT_PITCHBEND: //0xE0 fprintf(stderr, "%02x %04x\n", 0xE0+ev->data.control.channel, ev->data.control.value); break; case SND_SEQ_EVENT_SYSEX: //0xF0 unsigned char *p = (unsigned char *)ev->data.ext.ptr; for(int i=0; i<ev->data.ext.len; i++, p++){ fprintf(stderr,"%02x ",*p); } fprintf(stderr,"\n"); break; } snd_seq_free_event(ev); } while (snd_seq_event_input_pending(seq_handle, 0) > 0);
snd_seq_event_t ├ snd_seq_event_type_t type ├ unsigned char flags ├ unsigned char tag ├ unsigned char queue ├ snd_seq_timestamp_t time ├ snd_seq_addr_t source ├ snd_seq_addr_t dest └ snd_seq_event_data_t data ├ snd_seq_ev_note_t note │ ├ unsigned char channel │ ├ unsigned char note │ ├ unsigned char velocity │ ├ unsigned char off_velocity │ └ unsigned int duration ├ snd_seq_ev_ctrl_t control │ ├ unsigned char channel │ ├ unsigned char unused [3] │ ├ unsigned int param │ └ signed int value ├ snd_seq_ev_raw8_t raw8 ├ snd_seq_ev_raw32_t raw32 ├ snd_seq_ev_ext_t ext │ ├ unsigned int len │ └ void * ptr ├ snd_seq_ev_queue_control_t queue ├ snd_seq_timestamp_t time ├ snd_seq_addr_t addr ├ snd_seq_connect_t connect └ snd_seq_result_t result