A Day In The Life

とあるプログラマの備忘録

Swift3でMIDIファイルをパースする方法

Swift3 で MIDI ファイルのパースをしようとしたら意外に方法が見つからなくて苦労したのでメモです。 MusicEventIteratorGetEventInfo 関数で取得したデータを MIDINoteMessage オブジェクトに変換する方法がわからなくてハマりました。

import AudioToolbox

public class MIDIParser {
    
  public static func parse(url midiFileUrl: URL) {
        
    var musicSequence: MusicSequence?
    var result = OSStatus(noErr)
    result = NewMusicSequence(&musicSequence)
    guard let sequence = musicSequence else {
      print("error creating sequence : \(result)")
      return
    }
        
    // MIDIファイルの読み込み
    MusicSequenceFileLoad(sequence, midiFileUrl as CFURL, .midiType, MusicSequenceLoadFlags.smf_ChannelsToTracks)
        
    var musicTrack: MusicTrack? = nil
    var sequenceLength: MusicTimeStamp = 0
    var trackCount: UInt32 = 0
    MusicSequenceGetTrackCount(sequence, &trackCount)
    for i in 0 ..< trackCount {
      var trackLength: MusicTimeStamp = 0
      var trackLengthSize: UInt32 = 0
            
      MusicSequenceGetIndTrack(sequence, i, & musicTrack)
      guard let track = musicTrack else {
        continue
      }
            
      MusicTrackGetProperty(track, kSequenceTrackProperty_TrackLength, &trackLength, &trackLengthSize)
            
      if sequenceLength < trackLength {
        sequenceLength = trackLength
      }
            
      var tmpIterator: MusicEventIterator?
      NewMusicEventIterator(track, &tmpIterator)
      guard let iterator = tempIterator else {
        continue
      }
            
      var hasNext: DarwinBoolean = false
      MusicEventIteratorHasCurrentEvent(iterator, &hasNext)
            
      var type: MusicEventType = 0
      var stamp: MusicTimeStamp = -1
      var data: UnsafeRawPointer?
      var size: UInt32 = 0
            
      while hasNext.boolValue {
        MusicEventIteratorGetEventInfo(iterator, &stamp, &type, &data, &size)
        if type == kMusicEventType_MIDINoteMessage {
          let messagePtr = UnsafePointer<MIDINoteMessage>(data?.assumingMemoryBound(to: MIDINoteMessage.self))
                    
          guard let channel = messagePtr?.pointee.channel,
            let note = messagePtr?.pointee.note,
            let velocity = messagePtr?.pointee.velocity,
            let duration = messagePtr?.pointee.duration else {
              continue
          }
            
          let message = MIDINoteMessage(channel: channel,
              note: note,
              velocity: velocity,
              releaseVelocity: 0,
              duration: duration)
          print(message)
        }
                
        MusicEventIteratorNextEvent(iterator)
        MusicEventIteratorHasCurrentEvent(iterator, &hasNext)
      }
      DisposeMusicEventIterator(iterator)
      MusicSequenceDisposeTrack(sequence, track)
    }
    DisposeMusicSequence(sequence)
  }
}

参考

Objc や Swift2の情報はそこそこありました。

Swift3 のポインタ型についてちゃんと理解してればハマらなかったのかなと反省してます。