【问题标题】:Metronome ios swift beat visuals lag节拍器 ios swift beat 视觉滞后
【发布时间】:2018-12-26 09:06:19
【问题描述】:

我正在尝试通过实现苹果提供的示例代码来创建节拍器应用。一切正常,但我发现节拍视觉效果与播放器时间不正确同步。这是苹果提供的示例代码

let secondsPerBeat = 60.0 / tempoBPM
let samplesPerBeat = Float(secondsPerBeat * Float(bufferSampleRate))
let beatSampleTime: AVAudioFramePosition = AVAudioFramePosition(nextBeatSampleTime)
let playerBeatTime: AVAudioTime = AVAudioTime(sampleTime: AVAudioFramePosition(beatSampleTime), atRate: bufferSampleRate)
// This time is relative to the player's start time.

player.scheduleBuffer(soundBuffer[bufferNumber]!, at: playerBeatTime, options: AVAudioPlayerNodeBufferOptions(rawValue: 0), completionHandler: {
self.syncQueue!.sync() {
self.beatsScheduled -= 1
self.bufferNumber ^= 1
self.scheduleBeats()
}
})

beatsScheduled += 1

if (!playerStarted) {
// We defer the starting of the player so that the first beat will play precisely
// at player time 0. Having scheduled the first beat, we need the player to be running
// in order for nodeTimeForPlayerTime to return a non-nil value.

player.play()
playerStarted = true
}
let callbackBeat = beatNumber
beatNumber += 1
// calculate the beattime for animating the UI based on the playerbeattime.
let nodeBeatTime: AVAudioTime = player.nodeTime(forPlayerTime: playerBeatTime)!
let output: AVAudioIONode = engine.outputNode
let latencyHostTicks: UInt64 = AVAudioTime.hostTime(forSeconds: output.presentationLatency)
//calcualte the final dispatch time which will update the UI in particualr intervals
let dispatchTime = DispatchTime(uptimeNanoseconds: nodeBeatTime.hostTime + latencyHostTicks)**
// Visuals.
DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: dispatchTime) {
if (self.isPlaying) {
// send current call back beat.
self.delegate!.metronomeTicking!(self, bar: (callbackBeat / 4) + 1, beat: (callbackBeat % 4) + 1)

}
}
}
// my view controller class where i'm showing the beat number
class ViewController: UIViewController ,UIGestureRecognizerDelegate,Metronomedelegate{

@IBOutlet var rhythmlabel: UILabel!
//view did load method
override func viewDidLoad() {


}
//delegate method for getting the beat value from metronome engine and showing in the UI label.

func metronomeTicking(_ metronome: Metronome, bar: Int, beat: Int) {
    DispatchQueue.main.async {
        print("Playing Beat \(beat)")
//show beat in label
       self.rhythmlabel.text = "\(beat)"
    }
}
}

【问题讨论】:

  • 您的代码中没有“视觉效果”。所以不清楚这个问题是关于什么的。
  • 您好,我已经编辑了代码,请检查一次 (callbackBeat % 4) + 1 是我需要在视图中显示的节拍。
  • 但是您没有显示任何在任何视图中显示任何内容的代码。并且您标记 Visuals 的代码位于后台线程上,因此 course 存在延迟。
  • @matt 我已经在我的视图控制器中实现了委托方法。 ** MetronomeTicking** 将在我的视图控制器中被调用,我正在显示延迟出现的动画。
  • 我还是不明白DispatchQueue.global(qos: .userInitiated)的意思。这会将您置于后台线程上,并授予运行时权限以在需要时执行您的代码。在我看来,这与您想要做的完全相反。当然,如果您想要以最高准确度获得最低延迟,那么您想进入 main 线程。当然我还是不能保证asyncAfter的准确性;在我看来,这似乎是另一个风险来源。理想情况下,您应该在声音播放时直接“打勾”。

标签: ios iphone swift metronome


【解决方案1】:

我认为你无缘无故地处理这个有点太复杂了。您真正需要的是在启动节拍器时设置 DispatchTime,并在 DispatchTime 启动时触发函数调用,根据所需频率更新调度时间,并在节拍器启用时循环。

我为您准备了一个实现此方法的项目,以便您可以随意使用和使用:https://github.com/ekscrypto/Swift-Tutorial-Metronome

祝你好运!

Metronome.swift

import Foundation
import AVFoundation

class Metronome {
    var bpm: Float = 60.0 { didSet {
        bpm = min(300.0,max(30.0,bpm))
        }}
    var enabled: Bool = false { didSet {
        if enabled {
            start()
        } else {
            stop()
        }
        }}
    var onTick: ((_ nextTick: DispatchTime) -> Void)?
    var nextTick: DispatchTime = DispatchTime.distantFuture

    let player: AVAudioPlayer = {
        do {
            let soundURL = Bundle.main.url(forResource: "metronome", withExtension: "wav")!
            let soundFile = try AVAudioFile(forReading: soundURL)
            let player = try AVAudioPlayer(contentsOf: soundURL)
            return player
        } catch {
            print("Oops, unable to initialize metronome audio buffer: \(error)")
            return AVAudioPlayer()
        }
    }()

    private func start() {
        print("Starting metronome, BPM: \(bpm)")
        player.prepareToPlay()
        nextTick = DispatchTime.now()
        tick()
    }

    private func stop() {
        player.stop()
        print("Stoping metronome")
    }

    private func tick() {
        guard
            enabled,
            nextTick <= DispatchTime.now()
            else { return }

        let interval: TimeInterval = 60.0 / TimeInterval(bpm)
        nextTick = nextTick + interval
        DispatchQueue.main.asyncAfter(deadline: nextTick) { [weak self] in
            self?.tick()
        }

        player.play(atTime: interval)
        onTick?(nextTick)
    }
}

ViewController.swift

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var bpmLabel: UILabel!
    @IBOutlet weak var tickLabel: UILabel!

    let myMetronome = Metronome()

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        myMetronome.onTick = { (nextTick) in
            self.animateTick()
        }
        updateBpm()
    }

    private func animateTick() {
        tickLabel.alpha = 1.0
        UIView.animate(withDuration: 0.35) {
            self.tickLabel.alpha = 0.0
        }
    }

    @IBAction func startMetronome(_: Any?) {
        myMetronome.enabled = true
    }

    @IBAction func stopMetronome(_: Any?) {
        myMetronome.enabled = false
    }

    @IBAction func increaseBpm(_: Any?) {
        myMetronome.bpm += 1.0
        updateBpm()
    }
    @IBAction func decreaseBpm(_: Any?) {
        myMetronome.bpm -= 1.0
        updateBpm()
    }

    private func updateBpm() {
        let metronomeBpm = Int(myMetronome.bpm)
        bpmLabel.text = "\(metronomeBpm)"
    }
}

注意:似乎存在预加载问题,prepareToPlay() 在播放前没有完全加载音频文件,这会导致第一次播放滴答音频文件时出现一些计时问题。这个问题留给读者自己去解决。最初的问题是同步,这应该在上面的代码中演示。

【讨论】:

  • @RAM 你还需要包括你的视觉动画是如何开始的
  • 谢谢,我已经用我的视图控制器功能更新了代码。请检查一下。
  • @Ram 用 git repo 来查看我的更新答案。干杯!
  • 谢谢,我已经检查过了,但是视图控制器代码是空的
  • @RAM 糟糕,抱歉忘记推送我的提交。立即查看! :)
猜你喜欢
  • 1970-01-01
  • 2019-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-18
相关资源
最近更新 更多