AVAudioSequencerでMIDIファイルを再生して曲の終わりにコールバックを設定する方法
AVAudioSequencerでMIDIファイルを再生して曲の終わりにコールバックを設定する方法です。AVAudioSequencer 自体にそのようなコールバックがないので CoreMIDI のコールバックを使います。CoreMIDIはSwiftのブロックではなくてC言語の関数ポインタでコールバックを取らないといけないケースがあって大変です。
public class Sequencer { let callBack: @convention(c) (UnsafeMutablePointer<Void>, MusicSequence, MusicTrack, MusicTimeStamp, UnsafePointer<MusicEventUserData>, MusicTimeStamp, MusicTimeStamp) -> Void = { (obj, seq, mt, timestamp, userData, timestamp2, timestamp3) in // Cタイプ関数なのでselfを使えません let mySelf: Sequencer = unsafeBitCast(obj, Sequencer.self) : : 曲の終了後に行う処理 : } var sequencer: AVAudioSequencer public func playWithMidiURL(midiFileUrl: NSURL, audioFiles: [NSURL]) { // サンプラーとオーディオエンジンの初期化 let audioEngine = AVAudioEngine() let samplerNode = AVAudioUnitSampler() // サンプラーとオーディオエンジンをつなげる audioEngine.attachNode(samplerNode) audioEngine.connect(samplerNode, to: audioEngine.mainMixerNode, format: samplerNode.outputFormatForBus(0)) // オーディオエンジンをスタートする do { try samplerNode.loadAudioFilesAtURLs(audioFiles) try audioEngine.start() } catch { } // シーケンサーの初期化 self.sequencer = AVAudioSequencer(audioEngine: audioEngine) let musicSequence = audioEngine.musicSequence // MIDIファイル読み込み do { try sequencer.loadFromURL(midiFileUrl, options: .SMF_ChannelsToTracks) } catch { print("Error load MIDI file") } // シーケンサスタート do { sequencer.prepareToPlay() try sequencer.start() } catch { print("Error play MIDI file") } // 曲の長さを取得 var musicLengthInBeats: NSTimeInterval = 0.0 for track in sequencer.tracks { let lengthInBeats = track.lengthInBeats if musicLengthInBeats < lengthInBeats { musicLengthInBeats = lengthInBeats } } // 曲の最後にコールバックを仕込む MusicSequenceSetUserCallback(musicSequence, callBack, unsafeBitCast(self, UnsafeMutablePointer<Void>.self)) var musicTrack: MusicTrack = nil MusicSequenceGetIndTrack(musicSequence, 0, &musicTrack) let userData: UnsafeMutablePointer<MusicEventUserData> = UnsafeMutablePointer.alloc(1) MusicTrackNewUserEvent(musicTrack, ceil(musicLengthInBeats), userData) } }
曲の長さを調べてタイマーでやれば良いと思われるかもしれませんが、それだと曲のポーズに対応できないのでこのような方法が必要になります。
参考記事
ソースコードはこちら
動作確認済みのソースコードはこちらに置いてあります。 - glassonion1/R9MIDISequencer