首页 > 解决方案 > AVAudioEngine MIDI 文件播放(当前进度+MIDI 结束回调)Swift

问题描述

我正在使用 AVAudioEngine、AVAudioSequencer、AVAudioUnitSampler 播放 MID。AVAudioUnitSampler 加载 Soundfont 和 AVAudioSequencer 加载 MIDI 文件。我的初始配置是

    engine = AVAudioEngine()
    
    sampler = AVAudioUnitSampler()
    speedControl = AVAudioUnitVarispeed()
    pitchControl = AVAudioUnitTimePitch()
    
    engine.attach(sampler)
    engine.attach(pitchControl)
    engine.attach(speedControl)
    
    engine.connect(sampler, to: speedControl, format: nil)
    engine.connect(speedControl, to: pitchControl, format: nil)
    engine.connect(pitchControl, to: engine.mainMixerNode, format: nil)

这是我的序列如何加载 MIDI 文件

    func setupSequencer() {
    
    self.sequencer = AVAudioSequencer(audioEngine: self.engine)
    
    
    let options = AVMusicSequenceLoadOptions()
    
    let documentsDirectoryURL =  FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    
    
    let introurl = URL(string: songDetails!.intro!)
    
    let midiFileURL = documentsDirectoryURL.appendingPathComponent(introurl!.lastPathComponent)
    
    do {
        try sequencer.load(from: midiFileURL, options: options)
        print("loaded \(midiFileURL)")
    } catch {
        print("something screwed up \(error)")
        return
    }
    
    sequencer.prepareToPlay()
    if sequencer.isPlaying == false{
        
    }
}

这是采样器如何加载 SoundFont

    func loadSF2PresetIntoSampler(_ preset: UInt8,bankURL:URL ) {
            
    do {
        try self.sampler.loadSoundBankInstrument(at: bankURL,
                                                 program: preset,
                                                 bankMSB: UInt8(kAUSampler_DefaultMelodicBankMSB),
                                                 bankLSB: UInt8(kAUSampler_DefaultBankLSB))
    } catch {
        print("error loading sound bank instrument")
    }
}

它玩得很好,这没有问题。我还有 2 个其他要求,并且在这些方面遇到问题

  1. 我必须在第一个 MID 结束播放后播放另一个 MIDI 文件,因为我需要从 Engine 或 Sequence 获取完整/完成 MIDI 文件回调,或者如何在 Sequence 中加载多个 MIDI 文件?我尝试了很多方法,但没有帮助。

  2. 我需要显示 MIDI 文件播放的进度,例如当前时间和总时间。为此,我尝试了一种在堆栈答案中找到的方法,即:

    var currentPositionInSeconds: TimeInterval {
    get {
        guard let offsetTime = offsetTime else { return 0 }
        guard let lastRenderTime = engine.outputNode.lastRenderTime else { return 0 }
        let frames = lastRenderTime.sampleTime - offsetTime.sampleTime
        return Double(frames) / offsetTime.sampleRate
    }
    

    }

这里offsetTime

        offsetTime = engine.outputNode.lastRenderTime

它总是返回零。

标签: swiftmidiavaudioengine

解决方案


很高兴看到有人使用我的示例代码。

这是一个缺失的功能。请提交雷达。没有雷达什么都不会发生。他们确实会注意他们安排要做什么。

我通过获取序列的长度来伪造它。

if let ft = sequencer.tracks.first {
    self.lengthInSeconds = ft.lengthInSeconds
} else {
    self.lengthInSeconds = 0
}

然后在我的播放功能中,

do {
   try sequencer.start()
   Timer.scheduledTimer(withTimeInterval: self.lengthInSeconds, repeats: false) {
            [weak self] (t: Timer)  in
            guard let self = self else {return}

            t.invalidate() 
            self.logger.debug("sequencer finished")
            self.sequencer.stop()
   }
...

如果您想要更准确,可以使用 DispatchSourceTimer。


推荐阅读