Core MIDI その3 MIDIPacketListの送信

MIDIデータを送信する方法です。まず、MIDIPacketListの作り方を見てみます。

ひとつの普通のメッセージのMIDIPacketを含んだMIDIPacketListを作るだけであればMIDIPacketList構造体を作ってしまえばいいだけですが、複数のMIDIPacketを含んだMIDIPacketListを作りこんでいくには以下の関数を使います。

extern MIDIPacket *
MIDIPacketListInit(MIDIPacketList *pktlist)

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

作り方の順番としては、

・MIDIPacketListとして作成するのに十分なメモリ領域を確保する
・MIDIPacketListInitでMIDIPacketListを初期化し、1つめのMIDIPacketのポインタを取得する
・MIDIPacketListAddでMIDIPacketを書き込む(必要な分だけMIDIPacketListAddを繰り返す)

という感じになります。この方法でMIDIPacketListを作るコードはこんな感じです。とりあえずMIDIPacketは1つだけですけど。

ByteCount bufferSize = 1024;
Byte packetListBuffer[bufferSize];
MIDIPacketList *packetListPtr = (MIDIPacketList *)packetListBuffer;
    
MIDITimeStamp time = AudioGetCurrentHostTime();
    
ByteCount dataSize = 3;
Byte data[dataSize];
data[0] = 0x90;
data[1] = 0x60;
data[2] = 0x10;
    
MIDIPacket *packet = MIDIPacketListInit(packetListPtr);
    
if (packet != NULL) {
    packet = MIDIPacketListAdd(
        packetListPtr, bufferSize, packet, time, dataSize, data);
}

実際に複数のMIDIPacketを追加する場合、MIDIPacketListAddでMIDIPacketの書き込みが成功すれば、次の書き込み位置となるMIDIPacketのポインタが返ってきますので、それをまたMIDIPacketListAddに渡してという風に繰り返してMIDIPacketを追加して行きます。ただ、いろいろ試した感じでは、MIDITimeStampを同じ時間に指定したMIDIPacketを追加しようとしても追加されないようです。他のソフトからは、同じ時間のMIDIPacketがいくつも入ったMIDIPacketListが来たりするんですが…。

MIDIPacketListが作成できたら、MIDISend関数で送信します。

extern OSStatus
MIDISend(MIDIPortRef            port, 
         MIDIEndpointRef        dest, 
         const MIDIPacketList *	pktlist )

ちなみに、MIDIReceivedという関数もあって、最初見たときに、受信なのにメッセージを送るとは何ぞや?と思ったのですが、これはソースとなっているMIDIエンドポイントにMIDIPacketListを送信して、アプリケーション内部で受信するためにというもののようです。

extern OSStatus 
MIDIReceived(MIDIEndpointRef        src, 
             const MIDIPacketList * pktlist )

これを使うと、受信したMIDIPacketListの中に先の時間のMIDIPacketがあったら、その時間に再び受信されるように再送信させられたり、あるいはアプリケーション内の音源をならすような場合、一度にたくさんのMIDIデータを時間を指定して送信しておけばあとは勝手にシーケンスしてくれる、という感じでしょうか。

Core MIDIを調べてると良く出てくるバーチャルソースってのが、そんなとき役立つのではないかと思います。アプリケーション内部にMIDIEndpointをバーチャルソースとして作るのがMIDISourceCreate関数です。

extern OSStatus
MIDISourceCreate(MIDIClientRef      client, 
                 CFStringRef        name, 
                 MIDIEndpointRef *  outSrc )

これでMIDIEndpointを作成してMIDIPortConnectSourceでMIDIPortとつなげたら、外部のソースと同じく受信する事が出来ます。

といったところで実際の送信の方法ですが、アプリケーション外部にMIDIデータを送信する順番としては、

・MIDIClientを作成する
・MIDIOutputPortCreateでMIDIPortを作成する
・出力するMIDIEndpointを取得する
・MIDIPacketListを作成する
・MIDISendで送信する

と、なります。

以下がMIDIデータ送信のサンプルになります。DestinationのMIDIEndpointを1つだけ取得して送信していますので、そこから何かソフトなりシンセなりで受けるようにすれば音が鳴ると思います。ちなみに今回のサンプルくらい各MIDIPacketの間隔が開いていれば、MIDIPacketListのデータが勝手にバラされてMIDIEndpointに送信されるようです。間隔が短かったり、MIDIReceivedの場合は、そのまんまMIDIPacketListに複数のMIDIPacketが入った状態で送信されます。

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

@interface MIDIOutTest : NSObject {
    
    MIDIClientRef clientRef;
    MIDIPortRef outPortRef;
    MIDIEndpointRef destPointRef;
}

- (void)setup;
- (void)sendNoteOn;

@end

@implementation MIDIOutTest

- (void)awakeFromNib
{
    [self setup];
    [self sendNoteOn];
}

- (void)setup
{
    OSStatus err;
    
    //MIDIクライアントを作成する
    NSString *clientName = @"inputClient";
    err = MIDIClientCreate((CFStringRef)clientName, NULL, NULL, &clientRef);
    if (err != noErr) {
        NSLog(@"MIDIClientCreate err = %d", err);
        return;
    }
    
    //MIDIアウトプットポートを作成する
    NSString *outputPortName = @"outputPort";
    err = MIDIOutputPortCreate(
        clientRef, (CFStringRef)outputPortName, &outPortRef);
    if (err != noErr) {
        NSLog(@"MIDIOutputPortCreate err = %d", err);
        return;
    }
    
    //送信先のMIDIエンドポイントを取得する
    destPointRef = MIDIGetDestination(0);
    
    //送信先のMIDIエンドポイントの名前を取得してログに表示
    CFStringRef strRef;
    err = MIDIObjectGetStringProperty(
        destPointRef, kMIDIPropertyDisplayName, &strRef);
    if (err != noErr) {
        NSLog(@"MIDIObjectGetStringProperty err = %d", err);
        return;
    }
    NSLog(@"connect = %@", strRef);
    CFRelease(strRef);
}

- (void)sendNoteOn
{
    OSStatus err;
    
    //MIDIPacketListを作成する
    ByteCount bufferSize = 1024;
    Byte packetListBuffer[bufferSize];
    MIDIPacketList *packetListPtr = (MIDIPacketList *)packetListBuffer;
    
    //現在のHostTimeを取得する
    MIDITimeStamp time = AudioGetCurrentHostTime();
    
    //ノートオンのMIDIデータを作成する
    Byte noteOnData[3];
    noteOnData[0] = 0x90;
    noteOnData[1] = 0x3C;
    noteOnData[2] = 100;
    
    //ノートオフのMIDIデータを作成する
    Byte noteOffData[3];
    noteOffData[0] = 0x80;
    noteOffData[1] = 0x3C;
    noteOffData[2] = 0x00;
    
    //MIDIPacketListの初期化をする
    MIDIPacket *packet = MIDIPacketListInit(packetListPtr);
    
    for (NSInteger i = 0; i < 12; i++) {
        
        //MIDIPacketListにノートオンのMIDIPacketを追加する
        if (packet != NULL) {
            packet = MIDIPacketListAdd(
                packetListPtr, bufferSize, packet, time, 3, noteOnData);
        } else {
            break;
        }
        
        time = AudioConvertHostTimeToNanos(time) + kSecondScale * 0.25;
        time = AudioConvertNanosToHostTime(time);
        
        //MIDIPacketListにノートオフのMIDIPacketを追加する
        if (packet != NULL) {
            packet = MIDIPacketListAdd(
                packetListPtr, bufferSize, packet, time, 3, noteOffData);
        } else {
            break;
        }
        
        time = AudioConvertHostTimeToNanos(time) + kSecondScale * 0.75;
        time = AudioConvertNanosToHostTime(time);
        
        noteOnData[1] += 1;
        noteOffData[1] += 1;
    }
    
    err = MIDISend(outPortRef, destPointRef, packetListPtr);
    if (err != noErr) {
        NSLog(@"MIDISend err = %d", err);
    }
}

/*
 - (void)dispose
 {
     OSStatus err;
 
     err = MIDIPortDispose(outPortRef);
     if (err != noErr) NSLog(@"MIDIPortDispose err = %d", err);
 
     err = MIDIClientDispose(clientRef);
     if (err != noErr) NSLog(@"MIDIClientDispose err = %d", err);
 }
 */

@end

コメントを残す

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