[Swift] MIDIデータの再生

先日のApp審査の却下を受けて、UIBackgroundModes Keyをオフにして再生する方法を調査。
結論としては、AudioKitのフレームワークは使用せず、Appleが提供しているフレームワークで実現することにしました。

シーケンサー

MIDIデータを再生するために、下記のオブジェクト利用。

    var musicSequence : MusicSequence?
    var musicPlayer   : MusicPlayer?
    var musicTrack    : MusicTrack?


これだけだと、サウンドフォントを使った再生がうまくできなかったので、AUGraphも使いました。

    var processGraph  : AUGraph?
    var chordUnit     : AudioUnit?
    var ioUnit        : AudioUnit?
    var chordNode     = AUNode()
    var ioNode        = AUNode()


まずは、初期化。
    init() {
        NewAUGraph(&processGraph)
        NewMusicPlayer(&musicPlayer)
        NewMusicSequence(&musicSequence)
        MusicSequenceNewTrack(musicSequence!, &musicTrack)
        
        MusicSequenceSetAUGraph(musicSequence!, processGraph)
        MusicPlayerSetSequence(musicPlayer!, musicSequence)
        
        setup()
        loadSF2Preset(unit: chordUnit!)
    }

    private func setup() {
        var cd:AudioComponentDescription = AudioComponentDescription(
            componentType: OSType(kAudioUnitType_MusicDevice),
            componentSubType: OSType(kAudioUnitSubType_Sampler),
            componentManufacturer: OSType(kAudioUnitManufacturer_Apple),
            componentFlags: 0,
            componentFlagsMask: 0)
        
        var ioUnitDescription:AudioComponentDescription = AudioComponentDescription(
            componentType: OSType(kAudioUnitType_Output),
            componentSubType: OSType(kAudioUnitSubType_RemoteIO),
            componentManufacturer: OSType(kAudioUnitManufacturer_Apple),
            componentFlags: 0,
            componentFlagsMask: 0)
        
        let ioUnitOutputElement:AudioUnitElement  = 0
        let chordOutputElement:AudioUnitElement = 0
        var outIsInitialized:DarwinBoolean = false
        var isRunning:DarwinBoolean = false
        
        if let graph = processGraph {
            AUGraphAddNode(graph, &cd, &chordNode)
            AUGraphAddNode(graph, &ioUnitDescription, &ioNode)
            
            AUGraphOpen(graph)
            AUGraphNodeInfo(graph, chordNode, nil, &chordUnit)
            AUGraphNodeInfo(graph, ioNode   , nil, &ioUnit)
            
            AUGraphConnectNodeInput(graph,
                chordNode, chordOutputElement, // srcnode, inSourceOutputNumber
                ioNode   , ioUnitOutputElement) // destnode, inDestInputNumber
            
            AUGraphIsInitialized(graph, &outIsInitialized)
            if outIsInitialized == false {
                AUGraphInitialize(graph)
            }
            AUGraphIsRunning(graph, &isRunning)
            if isRunning == false {
                AUGraphStart(graph)
            }
        }
    }

    /// Get Sound Font URL
    private func soundbankURL(idx:Int) -> URL {
        let sfname = soundFontName[idx]
        guard let bankURL = Bundle.main.url(forResource: sfname, withExtension: "sf2") else {
            fatalError("Sound font file not found.")
        }
        return bankURL
    }
    
    /// Load Sound Font
    func loadSF2Preset(unit: AudioUnit, preset:UInt8 = 0, index: Int = 0)  {
        let bankURL = soundbankURL(idx:index)
        var instdata = AUSamplerInstrumentData(fileURL: Unmanaged.passUnretained(bankURL as CFURL),
                                               instrumentType: UInt8(kInstrumentType_DLSPreset),
                                               bankMSB: UInt8(kAUSampler_DefaultMelodicBankMSB),
                                               bankLSB: UInt8(kAUSampler_DefaultBankLSB),
                                               presetID: preset)
        
        
        AudioUnitSetProperty(
            unit,
            UInt32(kAUSamplerProperty_LoadInstrument),
            UInt32(kAudioUnitScope_Global),
            0,
            &instdata,
            UInt32(MemoryLayout<AUSamplerInstrumentData>.size))
    }


これで、一通りセットアップが完了。

で、MIDIイベント(ノートオン)をMusicTrackに追加します。

                mess = MIDINoteMessage(channel: channel,
                                    note: UInt8(note),
                                    velocity: UInt8(vel),
                                    releaseVelocity: 0,
                                    duration: Float32(duration))
                MusicTrackNewMIDINoteEvent(track, position, &mess)


再生は、MusicPlayerで行います。
(1) 再生停止、(2) 再生位置を最初(0秒)にセット、(3) 再生
の順に処理します。

            MusicPlayerStop(player)
            MusicPlayerSetTime(player, 0)
            MusicPlayerStart(player)

これで、期待する動きをさせることができました。


AVAudioUnitSampler ならサウンドフォントで音が鳴ったので、AudioEngineで紐付けをして、MusicSequenceで再生してみたけど、ループ再生をやりたければ、MusicPlayerを使う必要があり、MusicPlayerだとサイン波でしか音がならなかったり。。。

試行錯誤した結果なので、実はもっとシンプルな方法があるのかもしれないです。


コメント

このブログの人気の投稿

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

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

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