gpt4 book ai didi

ios - 节拍器 ios 快速节拍视觉滞后

转载 作者:行者123 更新时间:2023-11-28 14:28:31 24 4
gpt4 key购买 nike

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

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)"
}
}
}

最佳答案

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

我为您准备了一个实现此方法的项目,因此您可以随意使用:https://github.com/ekscrypto/Swift-Tutorial-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() 在播放前未完全加载音频文件,这会导致第一次播放 tick 音频文件时出现一些计时问题。这个问题就留给读者去思考吧。最初的问题是同步,这应该在上面的代码中证明。

关于ios - 节拍器 ios 快速节拍视觉滞后,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51402112/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com