せっかくのリアルタイム歌唱合成ということなので、インタラクティブなシステムで使いたい。となると、レイテンシーの問題がでてくるのでJavaではなくObjective-Cで使いたい。Javaでプログラム上から操作する実験は成功したので、いよいよObjective-CからCoreMIDIを使って操作してみます。ちなみに、Objective-Cとはいえ今回はiOS系ではなくMacで実験してます。
CoreMIDI.frameworkについて
CoreMIDIを使うのは初めてだったけど、概要を理解するには下記の記事がわかりやすかった。
NSX-39の検出
MIDIGetNumberOfDevices()とMIDIGetDevice(i)の二つを使って、利用可能なデバイスの一覧を取得できるので、そこから名前がNSX-39からはじまる(なぜかデバイス名の最後に半角スペースがついてるので、完全一致だと見つからない)デバイスを探す。(わかりやすさ優先のため、オンラインかどうかの判定は省略)
+ (MIDIDeviceRef)connectedNSX39Device { ItemCount deviceCount = MIDIGetNumberOfDevices(); for (ItemCount i = 0 ; i < deviceCount ; i++) { MIDIDeviceRef deviceRef = MIDIGetDevice(i); CFStringRef deviceNameCF = nil; if (noErr == MIDIObjectGetStringProperty(deviceRef, kMIDIPropertyName, &deviceNameCF)) { NSString *deviceName = [NSString stringWithString:(__bridge NSString *)deviceNameCF]; CFRelease(deviceNameCF); NSLog(@"Device: %@", deviceName); if ([deviceName hasPrefix:@"NSX-39"]) { return deviceRef; } } } return -1; }
CoreMIDIで取得できるデバイスのリストはJavaで取得した場合とまた異なり、こっちではNSX-39は一つしかリストに登場しない。
Device: IAC ドライバ Device: ネットワーク Device: NSX-39
出力先エンドポイント(NSX-39側のMIDIメッセージ受信ポート)の取得
NSX-39デバイスを取得できたら、そこから出力先エンドポイント(NSX-39側のMIDIメッセージ受信ポート)を取得する。
+ (MIDIEndpointRef)endpointOfDevice:(MIDIDeviceRef)deviceRef { MIDIEntityRef entityRef = MIDIDeviceGetEntity(deviceRef, 0); return MIDIEntityGetDestination(entityRef, 0); }
エラーハンドリングを省略し、かつNSX-39に限った話では上のコードで出力先エンドポイントを取得できる。
Entityというものがなかなかピンとこなかったのだけど、どうやらこれは複数のIN/OUTポートを持つようなデバイスを使うときに意味を持ってくる要素のようだ。2IN / 2OUTのデバイスでも、系統AのIN/OUT、系統BのIN/OUTというような系統構成になっていると、Entityの数が2となり、それぞれのグループをわけて操作できるのだけど、幸いNSX-39は1IN/1OUTでEntityは一つしかないので意識する必要なし。なので、直接最初のEntityを取得し、そのEntityの中の最初の出力エンドポイント(Destination)を取得するだけでOK。
出力用ポートの作成
CoreMIDIではMIDISend()関数を使って実際にMIDIメッセージを送る。このとき、メッセージの送信先は上で取得したNSX-39のエンドポイントを指定すればよいのだけど、送信元となるポートも指定してやる必要がある。そこで、アプリ側で出力用ポートを作る。
MIDIClientRef clientRef; MIDIPortRef outputPortRef; MIDIClientCreate((__bridge CFStringRef)@"client", NULL, NULL, &clientRef); MIDIOutputPortCreate(clientRef, (__bridge CFStringRef)@"outputPort", &outputPortRef);
エラーハンドリングは省略。出力ポートの作成はMIDIOutputPortCreate()関数を使う。
CoreMIDIでは、デバイスの出力先ポートへのメッセージ送信は一つのOutputPort経由で送信するような使い方が想定されているようだ。複数のメッセージを送ろうとしたような場合に必要に応じてマージしてから実際のデバイスに送信したりしてくれるらしい。なので、OutputPortはメッセージ送信のたびに毎回生成したりせず、一度作ったものをアプリ内で使い回す。
OutputPortを作るときにはMIDIClientを指定しなければいけないので、同時にMIDIClientも用意する。正直なところMIDIClientオブジェクトに関しては、ここで生成した後プログラム内でほぼ使うことがなく、リファレンスにもあまりわかりやすい説明がなかったのでよくわからないのだけど、どうやらOutputPort(あるいはInputPort)を通じて接続したデバイスに関しての状況の変化などがあった際にMIDIClient経由で状況を把握できるらしい。今回はややこしくなるので、その通知部分の設定は省略した。(2個目と3個目のパラメータのNULL)
MIDIClient, OutputPortともに作成時に名前を指定できるが、今回は特に後から使うことはないので、適当に指定しておく。
これでいよいよ
MIDISend(outputPortRef, destinationEndPointRef, 送信したいメッセージのパケットリスト);
の形でメッセージを送れるのだけど、メッセージ部分についてはまた長いので次のページで。
「NSX-39(ポケットミク)で遊んでみる2(Objective-C準備編)」への1件のフィードバック