ALSA MIDIプログラム

ALSA のRAW MIDIインターフェースを使った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 RAW MIDIインターフェースとは別なインターフェースのようです。 こちらは、後で記載しますaconnect というコマンドでMIDIの入出力をつなぐことができます。

//
// MIDI through using sequencer interface
// midiseqinout.c
//
#include  /* for alsa interface   */
#include 
#include 
#include 

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