Xcode 8 Swift 3改变音高

时间:2016-10-06 10:49:12

标签: swift xcode audio avfoundation pitch

所以,I asked this question before on the Apple Developer Forums但从来没有得到正确答案,所以我想我会在这里问:

我正在尝试制作一个简单的游戏,每当你点击某个东西时,它的音高会有不同的音高。我认为它很简单,但它最终带来了很多东西(其中大部分都是我完全从别人那里复制过来的):

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位,但这只会使应用程序崩溃。此外,经常调用时,此代码非常慢。我有什么办法可以加快速度吗?很频繁地需要这种命中声音。

1 个答案:

答案 0 :(得分:3)

每次播放声音时,您都会重置引擎!并且您正在创建额外的播放器节点 - 如果您只想要一次播放音高转换声音的一个实例,那么它实际上要简单得多:

// 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
}

P.S。音高可以向上或向下移动两个八度,对于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
}