#swift #midi #avaudioengine
#swift #midi #avaudioengine
Вопрос:
Я играю в середине, используя AVAudioEngine, AVAudioSequencer, AVAudioUnitSampler. 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 других требования, и у меня возникли проблемы с ними
-
Я должен воспроизвести другой MIDI-файл после завершения воспроизведения первого MIDI-файла, для этого мне нужно получить полный / завершенный обратный вызов MIDI-файла из любого движка или последовательности ИЛИ Как я могу последовательно загрузить несколько MIDI-файлов? Я перепробовал много способов, но это не помогло.
-
Мне нужно показать ход воспроизведения MIDI-файла, например, текущее время и общее время. Для этого я попробовал метод, найденный где-то в ответах stack, который:
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 = 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, если хотите большей точности.
Комментарии:
1. Не используйте scheduledTimer для точности. AVAudioEngine создает пул аппаратных потоков для рендеринга в реальном времени. Эти аппаратные потоки превышают запланированные таймеры по приоритету. Делает таймер очень неточным.