Core MIDI その2 MIDIPacketの受信

Core MIDIではMIDIPacketというものがひとつのMIDIデータになります。そのMIDIPacketをMIDIPacketListに複数(あるいはひとつ)まとめて、MIDIエンドポイントを通してMIDIデータのやり取りをします。

MIDIPacketは構造体になっていて、以下のような感じで宣言されています。

struct MIDIPacket
{
    MIDITimeStamp   timeStamp;
    UInt16          length;
    Byte            data[256];
};
typedef struct MIDIPacket MIDIPacket;

timeStampは、0であれば受け取ったらすぐに処理をしろという事で、値が入ってれば処理すべきHosttimeという事のようです。lengthはdataのサイズです。dataはMIDIデータそのもので、すでに256バイトの配列が確保されているように見えますが、実際にいろいろ試してみた感じではlength分しかデータ領域が無いと思っていた方が良さそうです。

MIDIPacketListは以下のように宣言されています。

struct MIDIPacketList
{
    UInt32      numPackets;	
    MIDIPacket  packet[1];
};

packetは一つ分だけ確保されているように見えますが、MIDIPacketListには複数のMIDIPacketが含まれている事があり、その場合はnumPacketsにその数が入っていて、packetはその分の領域が確保されている状態になるようです。

そんな感じで、宣言されているのをそのまま当てにするとちょっと違っていたりするので、MIDIPacketListからのMIDIPacketの読み込みや、MIDPacketListの作成は、直接行うのではなく専用の関数が用意されています。

extern MIDIPacket *
MIDIPacketNext(MIDIPacket *pkt);

extern MIDIPacket *
MIDIPacketListInit(MIDIPacketList *pktlist)

extern MIDIPacket *
MIDIPacketListAdd(MIDIPacketList *  pktlist,
                  ByteCount         listSize,
                  MIDIPacket *      curPacket,
                  MIDITimeStamp     time,
                  ByteCount         nData,
                  const Byte *      data)

MIDIPacketListからの読み込みですが、ヘッダを見ると以下のような感じにするよう書いてあります。このようにMIDIPacketNextを使えばMIDIPacketのサイズに応じてパケットの位置を進めてくるようです。

MIDIPacket *packet = &packetList->packet[0];
  for (int i = 0; i < packetList->numPackets; ++i) {
    //...
    packet = MIDIPacketNext(packet);
  }

MIDIPacketListを作成するときにはMIDIPacketInitとMIDIPacketListAddを使ってMIDIPacketを追加して行くという事になりますがそれは次回やります。

MIDIPacketListを受信するには、MIDIClientCreate関数でMIDIClientを作成し、そのMIDIClientにMIDIInputPortCreate関数で入力用のMIDIPortを作成してMIDIデータを受信するコールバック関数を登録し、MIDIPortConnectSource関数でMIDIPortをMIDIEndpointと接続します。それぞれの関数は以下のように宣言されています。

extern OSStatus
MIDIClientCreate(CFStringRef     name, 
                 MIDINotifyProc	 notifyProc, 
                 void *          notifyRefCon, 
                 MIDIClientRef * outClient )

extern OSStatus 
MIDIInputPortCreate(MIDIClientRef client, 
                    CFStringRef   portName, 
                    MIDIReadProc  readProc, 
                    void *        refCon, 
                    MIDIPortRef * outPort )

extern OSStatus
MIDIPortConnectSource(MIDIPortRef     port, 
                      MIDIEndpointRef source, 
                      void *          connRefCon )

コールバック関数の引数の構成は決まっていて以下のような感じで宣言されています。

typedef void
(*MIDIReadProc)(const MIDIPacketList *pktlist, 
    void *readProcRefCon, void *srcConnRefCon);

readProcRefConにはMIDIPortを作成したときに登録したrefCon、srcConnRefConにはMIDIPortにソースを接続したときに登録したconnRefConがそれぞれ渡ってきます。これらを使ってMIDIPacketListがやってくるポート等を識別したりして処理する事ができると思います。呼ばれるのはメインスレッドではないようです。

それと、MIDIClientやMIDIPortが必要なくなったらMIDIClientDispose関数やMIDIPortDisposeで解放します。

以下が、アプリケーションの外部からMIDIデータを受信するサンプルです。ちょっと今回は気分を変えて、Cocoaアプリケーションのmain.mを以下のような感じで書き換えてみます。CoreMIDI.Frameworkも追加しておいてください。

//
//  main.m
//

#import <Cocoa/Cocoa.h>
#import <CoreMIDI/CoreMIDI.h>

static void 
MIDIInputProc(const MIDIPacketList *pktlist, 
    void *readProcRefCon, void *srcConnRefCon)
{
    //MIDIパケットリストの先頭のMIDIPacketのポインタを取得
    MIDIPacket *packet = (MIDIPacket *)&(pktlist->packet[0]);
    //パケットリストからパケットの数を取得
    UInt32 packetCount = pktlist->numPackets;
    
    for (NSInteger i = 0; i < packetCount; i++) {
        
        //data[0]からメッセージの種類とチャンネルを分けて取得する
        Byte mes = packet->data[0] & 0xF0;
        Byte ch = packet->data[0] & 0x0F;
        
        //メッセージの種類に応じてログに表示
        if ((mes == 0x90) && (packet->data[2] != 0)) {
            NSLog(@"note on number = %2.2x / velocity = %2.2x / channel = %2.2x",
                  packet->data[1], packet->data[2], ch);
        } else if (mes == 0x80 || mes == 0x90) {
            NSLog(@"note off number = %2.2x / velocity = %2.2x / channel = %2.2x", 
                  packet->data[1], packet->data[2], ch);
        } else if (mes == 0xB0) {
            NSLog(@"cc number = %2.2x / data = %2.2x / channel = %2.2x", 
                  packet->data[1], packet->data[2], ch);
        } else {
            NSLog(@"etc");
        }
        
        //次のパケットへ進む
        packet = MIDIPacketNext(packet);
    }
}

int main(int argc, char *argv[])
{
    OSStatus err;
    MIDIClientRef clientRef;
    MIDIPortRef inputPortRef;
    
    //MIDIクライアントを作成する
    NSString *clientName = @"inputClient";
    err = MIDIClientCreate((CFStringRef)clientName, NULL, NULL, &clientRef);
    if (err != noErr) {
        NSLog(@"MIDIClientCreate err = %d", err);
        return 1;
    }
    
    //MIDIポートを作成する
    NSString *inputPortName = @"inputPort";
    err = MIDIInputPortCreate(
        clientRef, (CFStringRef)inputPortName, 
        MIDIInputProc, NULL, &inputPortRef);
    if (err != noErr) {
        NSLog(@"MIDIInputPortCreate err = %d", err);
        return 1;
    }
    
    //MIDIエンドポイントを取得し、MIDIポートに接続する
    ItemCount sourceCount = MIDIGetNumberOfSources();
    for (ItemCount i = 0; i < sourceCount; i++) {
        MIDIEndpointRef sourcePointRef = MIDIGetSource(i);
        err = MIDIPortConnectSource(inputPortRef, sourcePointRef, NULL);
        if (err != noErr) {
            NSLog(@"MIDIPortConnectSource err = %d", err);
            return 1;
        }
    }
    
    return NSApplicationMain(argc,  (const char **) argv);
}

実行すると、オンラインになっているMIDIインプット全てから受信してログに表示します。MIDIPortとかの解放は全然やってませんし、実際にはMIDITimeStampの時間にもちゃんと対応して処理する事も必要になると思います。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です