swift - 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 个其他要求,并且在这些方面遇到问题
我必须在第一个 MID 结束播放后播放另一个 MIDI 文件,因为我需要从 Engine 或 Sequence 获取完整/完成 MIDI 文件回调,或者我如何在 Sequence 中加载多个 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
它总是返回零。
解决方案
很高兴看到有人使用我的示例代码。
这是一个缺失的功能。请提交雷达。没有雷达什么都不会发生。他们确实会注意他们安排要做什么。
我通过获取序列的长度来伪造它。
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。