AVAudioEngine MIDI 文件播放(当前进度+MIDI 结束回调)Swift

Posted

技术标签:

【中文标题】AVAudioEngine MIDI 文件播放(当前进度+MIDI 结束回调)Swift【英文标题】:AVAudioEngine MIDI file play (Current progress + MIDI end callback) Swift 【发布时间】:2020-08-15 10:09:21 【问题描述】:

我正在使用 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 个其他要求,但在这些方面遇到了问题

    我必须在第一个 MID 结束播放后播放另一个 MIDI 文件,为此我需要从引擎或序列中获取完整/完成 MIDI 文件回调我如何加载多个 MIDI 文件顺序?我尝试了很多方法,但都没有帮助。

    我需要显示 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

而且它总是返回 nil。

【问题讨论】:

【参考方案1】:

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

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

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

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。

【讨论】:

以上是关于AVAudioEngine MIDI 文件播放(当前进度+MIDI 结束回调)Swift的主要内容,如果未能解决你的问题,请参考以下文章

在 WatchOS 上使用 AVAudioEngine 录制时阻止播放

AVAudioFile 不能在 AVAudioEngine 中播放

AVAudioEngine 播放多声道音频

AvAudioEngine 可以播放和录音吗?

Java 中用于播放的硬件 MIDI 输出?

异步播放midi文件?