ここまでの流れ
前回の投稿で、Standard MIDI Fileを選択するためのファイルブラウザについて書きました。
書いた記事はiOSデバイスのものですが、macOSはNSOutlineView
を使えば簡単に作れるため、他のネット記事や、Qiitaメンバーの記事を参考にしてもらうとして...
MIDIに関連する機能はmacOSとiOSで共通なので、まずは私の開発環境でUSBで常にシンセと繋がっているmacOSでMIDIの送受信をやってみます。
MIDIハードウェア環境
超シンセマニアの私でしたが(笑)、住環境などの問題から徐々に機材が減り、現在はRolandのFANTOM 8だけが鎮座しています。
MacはiMac (Apple Silicon M1)を使っています。
MacとはUSB直結で使っています。
デバイスドライバのタイプはVenderで、MIDIだけでなく、オーディオインターフェースとしても利用できる様になっています。
動作確認の結果、デバイスドライバはVenderでもGenericのどちらでも動作しました。
GenericはmacOS標準ドライバで動作するモードです。
サンプルプロジェクト
GitHubで公開しています。
こちらからダウンロードできます。
MIDIFramework
サンプルプロジェクトにはMIDIFramework
というフレームワークを取り込んでいます。
これは私がC++, Objective-Cの時代から最新のSwiftまで、長年育ててきたMIDI関連処理をフレームワーク化したものです。
ソースコードで公開するより、このほうがシンプルに使えると思いました。
MIDIの処理はLMIDI
というクラスが担当します。
Macに繋がっているMIDIデバイスを調べる
以下の二つのLMIDIクラスプロパティで調べられます。
public static var sourceNames: [String]
public static var destinationNames: [String]
サンプルプログラムでは、
print(LMIDI.sourceNames)
print(LMIDI.destinationNames)
と記述していて、デバッグコンソールにMacに接続されているMIDIデバイスを表示します。
MIDI処理のインスタンスを作る
上記のプロパティでMIDIデバイスを調べ、接続したいデバイスを決めます。
サンプルプログラムでは、MIDI処理のインスタンス名はmidiDevice
という名前にし、ひとまずリスト上一番上のデバイスに接続します。
LMIDIのイニシャライザは送信デバイス、受信デバイスを個別に指定できる様になっています。
private var midiDevice: LMIDI?
midiDevice = LMIDI(sourceName: LMIDI.sourceNames.first!,
destinationName: LMIDI.destinationNames.first!)
これですぐに送受信できる状態になっています!
受信データの受け取り
サンプルではNotificationを使っています。
MIDIFrameworkは、MIDIデータを受信するとNotificationで通知します。
サンプルでは、
- すべてのメッセージの受信通知
- コントロールチェンジの受信通知
を受け取るようにobserverを指定しています。
NotificationCenter.default.addObserver(self,
selector: #selector(messageReceived(_:)),
name: LMIDI.messageReceivedNotification,
object: midiDevice)
NotificationCenter.default.addObserver(self,
selector: #selector(controlChangeReceived(_:)),
name: LMIDI.controlChangeReceivedNotification,
object: midiDevice)
この通知で受け取ったuserInfoの中に、MIDIで受信したデータが格納されています。
サンプルでは、print文を使ってその中身をデバッグコンソールに表示しています。
もう一つの受信方法は、LMIDIDelegate
というプロトコルを適用すると、
public protocol LMIDIDelegate {
func messageReceived(receiveData: Data, MIDITimeStamp: MIDITimeStamp)
}
というデリゲートメソッドで受信データを受け取ることができます。
このデリゲートメソッドは受信した時に呼ばれるので、MIDIレコーダーのような受信データを素早く処理したいときに便利です。
タイミング的にはデリゲートメソッドの方がNotificationより早く呼ばれますが、NotificationのUserInfoにもMIDITimeStampを入れてあるので、MIDIデータ受信タイミングはどちらの方法でも正しく得ることができまs。
送信
send
メソッドを使います。
sendメソッドは二つあります。
public func send(packet: Data, timeStamp: MIDITimeStamp)
public func send(packet: Data)
引数にMIDITimeStampがあるメソッドは、指定したタイムスタンプのタイミングでMIDIデータが送信されます。
引数がないメソッドは即時送信されます。
1秒後にノートオフを送る
という処理を考えた場合、
- 引数にタイムスタンプあり〜1秒後のタイムスタンプを計算して、引数に与える
- 引数にタイムスタンプなし〜タイマーを使って1秒後に送信
という方法が考えられます。
単発でMIDIデータを送信したい場合はタイマーがわかりやすいでしょう。
大量のデータを決まった時間に送るなら、MIDITimeStampを検討するのもいいでしょう。
使う側はMIDITimeStampを指定して送信処理するだけで、あとはmacOSがそのタイミングが来たら外部へ送信してくれます。
送受信ができたら、次はいよいよSMF再生
Standard MIDI Fileを再生するには、基本的に送信処理ができればOKです。
受信処理は必須ではないのですが、受信処理も組み込んでみます。
後日談
iOSのサンプルプロジェクトも追加しました。
macOSとiOSのサンプルは同じフォルダに入れましたので、一緒にダウンロードできます。