【问题标题】:Xcode 8 Swift 3 Pitch-altering soundsXcode 8 Swift 3 改变音高的声音
【发布时间】:2017-02-15 01:56:33
【问题描述】:

我正在尝试制作一个简单的游戏,它的击打声音在您击打某物时具有不同的音高。我以为这很简单,但结果却是一大堆东西(其中大部分是我完全从别人那里抄来的):

func hitSound(value: Float) {  
 
    let audioPlayerNode = AVAudioPlayerNode()  
 
     audioPlayerNode.stop()  
     engine.stop() // This is an AVAudioEngine defined previously  
     engine.reset()  
 
     engine.attach(audioPlayerNode)  
 
     let changeAudioUnitTime = AVAudioUnitTimePitch()  
     changeAudioUnitTime.pitch = value  
 
     engine.attach(changeAudioUnitTime)  
     engine.connect(audioPlayerNode, to: changeAudioUnitTime, format: nil)  
     engine.connect(changeAudioUnitTime, to: engine.outputNode, format: nil)  
     audioPlayerNode.scheduleFile(file, at: nil, completionHandler: nil) // File is an AVAudioFile defined previously  
     try? engine.start()  
 
     audioPlayerNode.play()  
 }  

由于此代码似乎停止播放当前正在播放的任何声音以播放新声音,有没有办法我可以改变这种行为,使其不会停止播放任何东西?我尝试删除 engine.stop 和 engine.reset 位,但这只会使应用程序崩溃。此外,频繁调用此代码时速度非常慢。我可以做些什么来加快速度吗?非常频繁地需要这种敲击声。

【问题讨论】:

  • 如果我听起来像一个对代码一无所知的家伙,只是要求别人做一些事情,我很抱歉,我什至没有接触任何音频功能然而,所以我只是四处寻找,试图弄清楚是什么。到目前为止,Swift 似乎与 Lua 非常相似,而且我还做了一些其他的工作,我只是被这个音频问题所困扰。

标签: swift xcode audio avfoundation pitch


【解决方案1】:

每次播放声音都会重置引擎!而且您正在创建额外的播放器节点 - 如果您只希望一次播放一个音高变化的声音实例,它实际上要简单得多:

// instance variables
let engine = AVAudioEngine()
let audioPlayerNode = AVAudioPlayerNode()
let changeAudioUnitTime = AVAudioUnitTimePitch()

拨打setupAudioEngine()一次:

func setupAudioEngine() {
    engine.attach(self.audioPlayerNode)

    engine.attach(changeAudioUnitTime)
    engine.connect(audioPlayerNode, to: changeAudioUnitTime, format: nil)
    engine.connect(changeAudioUnitTime, to: engine.outputNode, format: nil)
    try? engine.start()
    audioPlayerNode.play()
}

并拨打hitSound()任意多次:

func hitSound(value: Float) {
    changeAudioUnitTime.pitch = value

    audioPlayerNode.scheduleFile(file, at: nil, completionHandler: nil) // File is an AVAudioFile defined previously
}

附言音高可以向上或向下移动两个八度,范围为 4 个八度,位于 [-2400, 2400] 的数值范围内,单位为“分”。

p.p.s AVAudioUnitTimePitch 是非常酷的技术。我们小时候肯定没有这样的东西。

更新

如果您想要多通道,您可以轻松设置多个播放器和音高节点,但是您必须在启动引擎之前选择通道数。下面是你如何做两个(很容易扩展到 n 个实例,并且你可能想要选择自己的方法来选择在所有播放时中断哪个频道):

// instance variables
let engine = AVAudioEngine()
var nextPlayerIndex = 0
let audioPlayers = [AVAudioPlayerNode(), AVAudioPlayerNode()]
let pitchUnits = [AVAudioUnitTimePitch(), AVAudioUnitTimePitch()]

func setupAudioEngine() {
    var i = 0
    for playerNode in audioPlayers {
        let pitchUnit = pitchUnits[i]

        engine.attach(playerNode)
        engine.attach(pitchUnit)
        engine.connect(playerNode, to: pitchUnit, format: nil)
        engine.connect(pitchUnit, to:engine.mainMixerNode, format: nil)

        i += 1
    }

    try? engine.start()

    for playerNode in audioPlayers {
        playerNode.play()
    }
}

func hitSound(value: Float) {
    let playerNode = audioPlayers[nextPlayerIndex]
    let pitchUnit = pitchUnits[nextPlayerIndex]

    pitchUnit.pitch = value

    // interrupt playing sound if you have to
    if playerNode.isPlaying {
        playerNode.stop()
        playerNode.play()
    }

    playerNode.scheduleFile(file, at: nil, completionHandler: nil) // File is an AVAudioFile defined previously

    nextPlayerIndex = (nextPlayerIndex + 1) % audioPlayers.count
}

【讨论】:

  • 感谢您的回复,这更有意义!我会尝试一下,我真的很困惑为什么它如此滞后,因为许多游戏都能够做更复杂的事情。
  • 好的,我试用了您的版本,它确实运行得更快,但如果可能的话,我正在寻找一种方法以不同的音高一次播放多个声音实例。很抱歉再次打扰您,但目前这些声音播放时间已过时,因为它们都在彼此之后播放
  • 非常感谢您的解释。最后一个问题 - 目前我正在尝试停止当前正在播放的频道以便再次播放声音:let playerNode = audioPlayers[nextPlayerIndex] if playerNode.isPlaying { playerNode.stop() playerNode.play()} 它可以工作,但有时会有一些奇怪的截止声音,而且看起来有点慢我。难道我又做错了什么?
  • 是的,我做错了什么。无论音频文件是否正在播放,isPlaying 总是返回 true,因此它每次都会重置。令人惊讶的是,它的重置速度很慢。我会尝试使用 scheduleFile 部分的完成处理程序来做某事或其他
  • 希望这将是我永远不会打扰您的最后一件事 - 例如,您会如何中断频道?我很惊讶停止和启动一个节点是如此缓慢,所以有没有我忽略的替代方案?我检查了该节点的所有 Apple 文档,我看到的唯一另一件事是重置它,但这似乎没有做任何事情,而你不能通过停止节点来做。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多