ALSA MIDIプログラム

MIDIを扱うALSAのインターフェースは2種類あります。rawmidiとMIDIシーケンサーです。

 
ALSA rawmidiプログラム

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シーケンサープログラム

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;
}

 
MIDIの受信(パーサー)

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 %4x\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_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