2015-10-15 3 views
1

Фон: Я нашел один из сеансов Apple WWDC под названием «AVAudioEngine in Practice» и пытаюсь сделать что-то похожее на последнюю демонстрацию, показанную в 43:35 (https://youtu.be/FlMaxen2eyw?t=2614). Я использую SpriteKit вместо SceneKit, но принцип тот же: я хочу генерировать сферы, бросать их вокруг и когда они сталкиваются с движком, играет звук, уникальный для каждой сферы.Подробная информация об использовании AVAudioEngine

Проблема:

  • Я хочу уникальное AudioPlayerNode прикрепленного к каждому SpriteKitNode, так что я могу играть разные звуки для каждой сферы. В настоящее время, если я создаю две сферы и задаю другой шаг для каждого из их AudioPlayerNode, кажется, что воспроизводится только последний созданный AudioPlayerNode, даже когда происходит слияние исходной сферы. Во время демонстрации он упоминает «Я связываю игрока, посвященного игрока каждому шару». Как мне это сделать?

  • Звуковые клики/артефакты каждый раз, когда происходит новое столкновение. Я предполагаю, что это связано с AVAudioPlayerNodeBufferOptions и/или с тем фактом, что я пытаюсь создавать, планировать и потреблять буферы очень быстро каждый раз, когда происходит контакт, что не самый эффективный метод. Какая была бы хорошая работа для этого?

Код: Как упоминалось в видео, «... за каждый мяч, который родился в этот мир, новый узел игрок также создается». У меня есть отдельный класс для сфер, с помощью метода, который возвращает SpriteKitNode, а также создает AudioPlayerNode каждый раз, когда она называется:

class Sphere { 

    var sphere: SKSpriteNode = SKSpriteNode(color: UIColor(), size: CGSize()) 
    var sphereScale: CGFloat = CGFloat(0.01) 
    var spherePlayer = AVAudioPlayerNode() 
    let audio = Audio() 
    let sphereCollision: UInt32 = 0x1 << 0 

    func createSphere(position: CGPoint, pitch: Float) -> SKSpriteNode { 

     let texture = SKTexture(imageNamed: "Slice") 
     let collisionTexture = SKTexture(imageNamed: "Collision") 

     // Define the node 

     sphere = SKSpriteNode(texture: texture, size: texture.size()) 

     sphere.position = position 
     sphere.name = "sphere" 
     sphere.physicsBody = SKPhysicsBody(texture: collisionTexture, size: sphere.size) 
     sphere.physicsBody?.dynamic = true 
     sphere.physicsBody?.mass = 0 
     sphere.physicsBody?.restitution = 0.5 
     sphere.physicsBody?.usesPreciseCollisionDetection = true 
     sphere.physicsBody?.categoryBitMask = sphereCollision 
     sphere.physicsBody?.contactTestBitMask = sphereCollision 
     sphere.zPosition = 1 

     // Create AudioPlayerNode 

     spherePlayer = audio.createPlayer(pitch) 

     return sphere  
    } 

Вот мой Audio Class, с которой я создаю AudioPCMBuffers и AudioPlayerNodes

class Audio { 

let engine: AVAudioEngine = AVAudioEngine() 

func createBuffer(name: String, type: String) -> AVAudioPCMBuffer { 

    let audioFilePath = NSBundle.mainBundle().URLForResource(name as String, withExtension: type as String)! 
    let audioFile = try! AVAudioFile(forReading: audioFilePath) 
    let buffer = AVAudioPCMBuffer(PCMFormat: audioFile.processingFormat, frameCapacity: UInt32(audioFile.length)) 
    try! audioFile.readIntoBuffer(buffer) 

    return buffer 
} 

func createPlayer(pitch: Float) -> AVAudioPlayerNode { 

    let player = AVAudioPlayerNode() 
    let buffer = self.createBuffer("PianoC1", type: "wav") 
    let pitcher = AVAudioUnitTimePitch() 
    let delay = AVAudioUnitDelay() 
    pitcher.pitch = pitch 
    delay.delayTime = 0.2 
    delay.feedback = 90 
    delay.wetDryMix = 0 

    engine.attachNode(pitcher) 
    engine.attachNode(player) 
    engine.attachNode(delay) 

    engine.connect(player, to: pitcher, format: buffer.format) 
    engine.connect(pitcher, to: delay, format: buffer.format) 
    engine.connect(delay, to: engine.mainMixerNode, format: buffer.format) 

    engine.prepare() 
    try! engine.start() 

    return player 
} 
} 

в моем GameScene классе я тогда проверить на столкновение, планировать буфер и играть AudioPlayerNode, если контакт произошел

func didBeginContact(contact: SKPhysicsContact) { 

     let firstBody: SKPhysicsBody = contact.bodyA 

     if (firstBody.categoryBitMask & sphere.sphereCollision != 0) { 

     let buffer1 = audio.createBuffer("PianoC1", type: "wav") 
     sphere.spherePlayer.scheduleBuffer(buffer1, atTime: nil, options: AVAudioPlayerNodeBufferOptions.Interrupts, completionHandler: nil) 
     sphere.spherePlayer.play() 

     } 
} 

Я новичок в S wift и только имеют базовые знания программирования, поэтому любое предложение/критика приветствуется.

ответ

2

Я работал на AVAudioEngine в scenekit и пытаюсь сделать что-то еще, но это будет то, что вы ищете:

https://developer.apple.com/library/mac/samplecode/AVAEGamingExample/Listings/AVAEGamingExample_AudioEngine_m.html

Это объясняет процесс: 1-инстанцирование вашего собственный AVAudioEngine суб-класс 2-метода для загрузки PCMBuffers для каждого AVAudioPlayer 3-Изменения параметров вашего узла по окружающей среде для размещения реверберации для большого числа объектов пинбола

Edit: Старинный, испытано и добавил несколько особенностей:

1-Вы создаете подкласс AVAudioEngine, назовите его, например, AudioLayerEngine. Это доступ к эффектам AVAudioUnit, таким как искажение, задержка, шаг и многие другие эффекты, доступные как AudioUnits. 2-Инициализируйте, настроив некоторые конфигурации для звукового движка, такие как алгоритм рендеринга, показывая AVAudioEnvironmentNode для воспроизведения с 3D-позициями объектов SCNNode или объектов SKNode, если вы находитесь в 2D, но хотите 3D-эффекты. 3-Создайте некоторые вспомогательные методы для загрузки предустановок для каждого эффекта AudioUnit, который вы хотите 0 - создать вспомогательный метод для создания аудиоплеера, а затем добавить его к любому узлу, который вы хотите, столько раз, сколько хотите, поскольку этот SCNNode принимает методы .audioPlayers, которые возвращают [AVAudioPlayer] или [SCNAudioPlayer] 5-начинается воспроизведение.

Я вставлял весь класс для справки, чтобы вы могли его структурировать по своему усмотрению, но имейте в виду, что если вы связываете это с SceneKit или SpriteKit, вы используете этот audioEngine для управления всеми вашими звуками вместо Внутренний AVAudioEngine от SceneKit. Это означает, что вы инстанцирует это в Gameview при методе AwakeFromNib

import Foundation 
import SceneKit 
import AVFoundation 

class AudioLayerEngine:AVAudioEngine{ 
    var engine:AVAudioEngine! 
    var environment:AVAudioEnvironmentNode! 
    var outputBuffer:AVAudioPCMBuffer! 
    var voicePlayer:AVAudioPlayerNode! 
    var multiChannelEnabled:Bool! 
    //audio effects 
    let delay = AVAudioUnitDelay() 
    let distortion = AVAudioUnitDistortion() 
    let reverb = AVAudioUnitReverb() 

    override init(){ 
     super.init() 
engine = AVAudioEngine() 
environment = AVAudioEnvironmentNode() 

engine.attachNode(self.environment) 
voicePlayer = AVAudioPlayerNode() 
engine.attachNode(voicePlayer) 
voicePlayer.volume = 1.0 
     outputBuffer = loadVoice() 
     wireEngine() 
     startEngine() 
voicePlayer.scheduleBuffer(self.outputBuffer, completionHandler: nil) 
voicePlayer.play() 
    } 

    func startEngine(){ 
     do{ 
      try engine.start() 
     }catch{ 
      print("error loading engine") 
     } 
    } 

    func loadVoice()->AVAudioPCMBuffer{ 
     let URL = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("art.scnassets/sounds/interface/test", ofType: "aiff")!) 
     do{ 
      let soundFile = try AVAudioFile(forReading: URL, commonFormat: AVAudioCommonFormat.PCMFormatFloat32, interleaved: false) 
      outputBuffer = AVAudioPCMBuffer(PCMFormat: soundFile.processingFormat, frameCapacity: AVAudioFrameCount(soundFile.length)) 
      do{ 
      try soundFile.readIntoBuffer(outputBuffer) 
      }catch{ 
       print("somethign went wrong with loading the buffer into the sound fiel") 
      } 
      print("returning buffer") 
      return outputBuffer 
     }catch{ 
     } 
     return outputBuffer 
    } 

    func wireEngine(){ 
loadDistortionPreset(AVAudioUnitDistortionPreset.MultiCellphoneConcert) 
     engine.attachNode(distortion) 
     engine.attachNode(delay) 
engine.connect(voicePlayer, to: distortion, format: self.outputBuffer.format) 
     engine.connect(distortion, to: delay, format: self.outputBuffer.format) 
       engine.connect(delay, to: environment, format: self.outputBuffer.format) 
     engine.connect(environment, to: engine.outputNode, format: constructOutputFormatForEnvironment()) 

    } 

    func constructOutputFormatForEnvironment()->AVAudioFormat{ 
let outputChannelCount = self.engine.outputNode.outputFormatForBus(1).channelCount 
let hardwareSampleRate = self.engine.outputNode.outputFormatForBus(1).sampleRate 
let environmentOutputConnectionFormat = AVAudioFormat(standardFormatWithSampleRate: hardwareSampleRate, channels: outputChannelCount) 
multiChannelEnabled = false 
     return environmentOutputConnectionFormat 
    } 

    func loadDistortionPreset(preset: AVAudioUnitDistortionPreset){ 
     distortion.loadFactoryPreset(preset) 
} 

    func createPlayer(node: SCNNode){ 
     let player = AVAudioPlayerNode() 
distortion.loadFactoryPreset(AVAudioUnitDistortionPreset.SpeechCosmicInterference) 
engine.attachNode(player) 
engine.attachNode(distortion) 
engine.connect(player, to: distortion, format: outputBuffer.format) 
     engine.connect(distortion, to: environment, format: constructOutputFormatForEnvironment()) 
let algo = AVAudio3DMixingRenderingAlgorithm.HRTF 
     player.renderingAlgorithm = algo 
     player.reverbBlend = 0.3 
     player.renderingAlgorithm = AVAudio3DMixingRenderingAlgorithm.HRTF 
    } 

} 
+0

Хотя эта ссылка может ответить на этот вопрос, то лучше включить основные части ответа здесь и предоставить ссылку для справки. Ответные ссылки могут стать недействительными, если связанная страница изменится. - [Из обзора] (/ review/low-quality-posts/11350414) –

+2

@BeauNouvelle Я отредактировал ответ с полным протестированным кодом и дополнительной функцией – triple7

Смежные вопросы