[Swift] Music Sequenceで再生しているMIDIデータの情報を表示
Music Sequenceを使ってMIDIデータを再生している時に、なっている音を鍵盤で表示したい!
ということで、色々調べてたのですが、多いのは、MIDIのInputPortまたはOutputPortにCallbackを仕込んで通知する方法でしたが、この方法だと、バックグランドモードを有効化しないと動作してくれない。
試行錯誤の結果、バックグランドモードを無効にしたまま、MIDIの再生データを表示することが出来たのでご紹介。
ターゲットOS iOS 9.0
クラスの関数として実装
クラスのinit()内で実装
Music Sequence -> seqCallback関数 -> musicSequenceCallback関数
と、わざわざ2段階で定義する必要もないかもしれないですが、
musicSequenceCallback内では、呼び出し元がどこかを気にせず処理を書けるので、わかりやすいかな〜 ってことで。。。
これで、MIDIデータ内にユーザイベントがあれば、musicSequenceCallback関数が呼ばれるようになります。
あとは、MIDIデータを作成する際に、ユーザイベントを仕込んであげれば良いです。
これで、MIDIのノートオンイベントと同じタイミングでユーザイベントがトリガされます。
ユーザイベントの種類を識別するために、userDataを設定できるので、
musicSequenceCallback関数の中でdataの値を判別して処理を振り分けることも出来ます。
ただ、私のスキル不足で、userDataに複数のデータをセットする方法が分からず、UInt8型のデータを1つしかセットできませんでした。
実際に鳴ってる音のデータは、trackのイベントを全て取得して、ユーザイベントと同じタイミング(EventTime)のノートオンデータを取得する方法で回避しました。
userDataに鳴ってる音のデータ(和音なら複数データ)をセットして引き渡しができれば、もっと楽できるのですけどね。。。
これは、のんびり調査していきます。
musicSequenceCallback関数の処理は、メインスレッドではなく、別スレッドで実行されるので、画面描写の更新は、メインスレッドで行うようにする必要がありそうです。
そうしないと、シミュレータで実行した時に、メッセージが大量に表示されて、やばい感じが伝わってきます。(笑
ということで、色々調べてたのですが、多いのは、MIDIのInputPortまたはOutputPortにCallbackを仕込んで通知する方法でしたが、この方法だと、バックグランドモードを有効化しないと動作してくれない。
試行錯誤の結果、バックグランドモードを無効にしたまま、MIDIの再生データを表示することが出来たのでご紹介。
環境
Xcode 10.2ターゲットOS iOS 9.0
コールバック用の関数定義
クラスの関数として実装
func musicSeqenceCallback(sequence: MusicSequence, track: MusicTrack, eventTime: MusicTimeStamp, data: UnsafePointer<musiceventuserdata>, startSliceBeat: MusicTimeStamp, endSliceBeat: MusicTimeStamp) -> Void { // 処理したい内容 let dat = data.pointee.data let idx = Int(eventTime) print(String(format: "%d - %d",dat, idx)) }
コールバック関数の割り当て
クラスのinit()内で実装
func seqCallback(inClientData: UnsafeMutableRawPointer?, inSequence: MusicSequence, inTrack: MusicTrack, inEventTime: MusicTimeStamp, inEventData: UnsafePointer, inStartSliceBeat: MusicTimeStamp, inEndSliceBeat: MusicTimeStamp) -> Void { let client = unsafeBitCast(inClientData, to: MIDIPlayer5.self) client.musicSeqenceCallback(sequence: inSequence, track: inTrack, eventTime: inEventTime, data: inEventData, startSliceBeat: inStartSliceBeat, endSliceBeat: inEndSliceBeat) } MusicSequenceSetUserCallback(musicSequence!,seqCallback as MusicSequenceUserCallback, unsafeBitCast(self, to: UnsafeMutableRawPointer.self))
Music Sequence -> seqCallback関数 -> musicSequenceCallback関数
と、わざわざ2段階で定義する必要もないかもしれないですが、
musicSequenceCallback内では、呼び出し元がどこかを気にせず処理を書けるので、わかりやすいかな〜 ってことで。。。
これで、MIDIデータ内にユーザイベントがあれば、musicSequenceCallback関数が呼ばれるようになります。
あとは、MIDIデータを作成する際に、ユーザイベントを仕込んであげれば良いです。
var userData = MusicEventUserData(length: 1, data: (0x01) ) if let track = musicTrack { let step = MusicTimeStamp(beat) MusicTrackNewMIDINoteEvent(track, step, &mess) MusicTrackNewUserEvent(track, step, &userData) }
これで、MIDIのノートオンイベントと同じタイミングでユーザイベントがトリガされます。
ユーザイベントの種類を識別するために、userDataを設定できるので、
musicSequenceCallback関数の中でdataの値を判別して処理を振り分けることも出来ます。
ただ、私のスキル不足で、userDataに複数のデータをセットする方法が分からず、UInt8型のデータを1つしかセットできませんでした。
実際に鳴ってる音のデータは、trackのイベントを全て取得して、ユーザイベントと同じタイミング(EventTime)のノートオンデータを取得する方法で回避しました。
userDataに鳴ってる音のデータ(和音なら複数データ)をセットして引き渡しができれば、もっと楽できるのですけどね。。。
これは、のんびり調査していきます。
musicSequenceCallback関数の処理は、メインスレッドではなく、別スレッドで実行されるので、画面描写の更新は、メインスレッドで行うようにする必要がありそうです。
DispatchQueue.main.async { // 画面表示更新 // : }
そうしないと、シミュレータで実行した時に、メッセージが大量に表示されて、やばい感じが伝わってきます。(笑
コメント
コメントを投稿