[Swift] Music Sequenceで再生しているMIDIデータの情報を表示

Music Sequenceを使ってMIDIデータを再生している時に、なっている音を鍵盤で表示したい!
ということで、色々調べてたのですが、多いのは、MIDIのInputPortまたはOutputPortにCallbackを仕込んで通知する方法でしたが、この方法だと、バックグランドモードを有効化しないと動作してくれない。

試行錯誤の結果、バックグランドモードを無効にしたまま、MIDIの再生データを表示することが出来たのでご紹介。

環境

Xcode 10.2
ターゲットOS iOS 9.0

コールバック用の関数定義


クラスの関数として実装
    func musicSeqenceCallback(sequence: MusicSequence,
                              track: MusicTrack,
                              eventTime: MusicTimeStamp,
                              data: UnsafePointer&ltmusiceventuserdata&gt,
                              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 {
              // 画面表示更新
              //    :
            }

そうしないと、シミュレータで実行した時に、メッセージが大量に表示されて、やばい感じが伝わってきます。(笑

コメント

このブログの人気の投稿

[Music] DTM初心者のためのドラム打ち込み その2

[Swift] StoryBoardを使用しない - UITextFieldで編集不可にする方法

[Swift] UISliderをカスタマイズしてみる