Воспроизведение MIDI-файла AVAudioEngine (текущий прогресс обратный вызов конца MIDI) Swift

#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 других требования, и у меня возникли проблемы с ними

  1. Я должен воспроизвести другой MIDI-файл после завершения воспроизведения первого MIDI-файла, для этого мне нужно получить полный / завершенный обратный вызов MIDI-файла из любого движка или последовательности ИЛИ Как я могу последовательно загрузить несколько MIDI-файлов? Я перепробовал много способов, но это не помогло.

  2. Мне нужно показать ход воспроизведения 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 создает пул аппаратных потоков для рендеринга в реальном времени. Эти аппаратные потоки превышают запланированные таймеры по приоритету. Делает таймер очень неточным.