Using Apple's New Audioengine to Change Pitch of Audioplayer Sound

How would I add effects to AVAudioPlayback

For effects, you have to use Core Audio.

Unfortunately, it sounds like there aren't any canned effects included. Check out the related question: Can I use Core Audio effects on the iPhone ?

iOS: How to play audio without fps drops?

According to your question, if you develop a game for iOS 9+, you can use the new iOS 9 library SKAudioNode (official Apple doc):

var backgroundMusic: SKAudioNode!

For example you can add this to didMoveToView():

if let musicURL = NSBundle.mainBundle().URLForResource("music", withExtension: "m4a") {
backgroundMusic = SKAudioNode(URL: musicURL)
addChild(backgroundMusic)
}

You can also use to play a simple effect:

let beep = SKAudioNode(fileNamed: "beep.wav")
beep.autoplayLooped = false
self.addChild(beep)

Finally, if you want to change the volume:

beep.runAction(SKAction.changeVolumeTo(0.4, duration: 0))

Update:

I see you have update your question speaking about AVAudioPlayer and SKAction. I've tested both of them for my iOS8+ compatible games.

About AVAudioPlayer, I personally use a custom library maked by me based from the old SKTAudio.

I see your code, about AVAudioPlayer init, and my code is different because I use:

@available(iOS 7.0, *)
public init(contentsOfURL url: NSURL, fileTypeHint utiString: String?)

I don't know if fileTypeHint make the difference, so try and fill me about your test.

Advantages about your code:
With a shared instance audio manager based to AVAudioPlayer you can control volume, use your manager wherever you want, ensure compatibility with iOS8

Disadvantages about your code:
Everytime you play a sound and you want to play another sound, the previous is broken, especially if you have launch a background music.

How to solve? According with this SO post to work well without issues seems AVFoundation is limited to 4 AVAudioPlayer properties instantiables, so you can do this:

  • 1) backgroundMusicPlayer: AVAudioPlayer!
  • 2) soundEffectPlayer1: AVAudioPlayer!
  • 3) soundEffectPlayer2: AVAudioPlayer!
  • 4) soundEffectPlayer3: AVAudioPlayer!

You could build a method that switch through the 3 soundEffect to see if is occupied:

if player.playing

and use the next free player. With this workaround you have always your sound played correctly, even your background music.

Looking to change speed of 3D-spatialized audio using SceneKit

I've found an answer to my question so wanted to post this in case others may be having the same / similar issues with this.

It turns out that I needed to explicitly set the audioListener as pointOfView before I could access the audioEnvironmentNode of the AVAudioEngine instance that SceneKit instantiates.

Also, I needed to set the camera on the audioListener before the listener is added to the scene, rather than afterward.

According to Apple's documentation though, if there is only one camera in the scene, which there is in my case, the node that the camera is set on is supposed to automatically default to becoming the pointOfView and thereby also default to being the audioListener.

In practice though, this does not seem to be the case. -And, since the pointOfView did not seem to be associated with the node that my camera was associated with, it seemed that I could not access the current audio environment node.

So the code I have listed in my question does work but with a minor tweak.

let engine = scnView.audioEngine
let environmentNode = scnView.audioEnvironmentNode
let pitch = AVAudioUnitVarispeed()

engine.attach(audioPlayer)
engine.attach(environmentNode)
engine.attach(pitch)

engine.connect(audioPlayer, to: pitch, format: file?.processingFormat)
engine.connect(pitch, to: environmentNode, format: file?.processingFormat)
engine.connect(environmentNode, to: engine.mainMixerNode, format: nil)

Once this is done, then as before, all I need to do is create the SCNAudioPlayer from the returned AVAudioPlayerNode and associate the SCNAudioPlayer with the SCNNode and all is well! :) The audio is presented in 3D based on the SCNNode's position and is easily modifiable using the parameters of the connected AVAudioUnit.

So hope this helps others and please have a wonderful day / weekend!

Cheers! :)

ModGirl

IOS: How to increase the Bass and Treble of a AVAudioPlayer in Swift?

I don't think it is possible to use EQ effects with an AVAudioPlayer.

A quick search gave me answers like this from StackOverflow:

can I use AVAudioPlayer to make an equalizer player?

Or this sadly unanswered question from Apple Developer Forums:

https://forums.developer.apple.com/thread/46998

Instead

What you can do instead is use the AVAudioEngine (https://developer.apple.com/reference/avfoundation/avaudioengine) which gives you the opportunity to add an EQ node (or other effect nodes) to your AVAudioPlayer node.

AVAudioEngine may seem daunting at first, but think of it as a mixer. You have some input nodes that generate sound (AVAudioPlayer nodes for instance), and you can then attach and connect those notes to your AVAudioEngine. The AVAudioEngine has a AVAudioMixerNode so you can control things like volume and so forth.

Between your input nodes and your mixer you can attach effect nodes, like an EQ node for instance and you can add a "tap" and record the final output to a file if so desired.

Reading material

This slideshare introduction helped me a great deal understanding what AVAudioEngine is (the code is Objective C though, but it should be understandable)

The AVAudioEngine in Practice from WWDC 2014 is a great introduction too.

So, I hope you are not frightened by the above, as said, it may seem daunting when you see it at first, but once you get it wired together it works fine and you have the option to add other effects than just EQ (pitch shifting, slowing down a file and so on).

Hope that helps you.

Xcode 8 Swift 3 Pitch-altering sounds

You're resetting the engine every time you play a sound! And you're creating extra player nodes - it's actually much simpler than that if you only want one instance of the pitch shifted sound playing at once:

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

call setupAudioEngine() once:

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

and call hitSound() as many times as you like:

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

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

p.s. pitch can be shifted two octaves up or down, for a range of 4 octaves, and lies in the numerical range of [-2400, 2400], having the unit "cents".

p.p.s AVAudioUnitTimePitch is very cool technology. We definitely didn't have anything like it when I was a kid.

UPDATE

If you want multi channel, you can easily set up multiple player and pitch nodes, however you must choose the number of channels before you start the engine. Here's how you'd do two (it's easy to extend to n instances, and you'll probably want to choose your own method of choosing which channel to interrupt when all are playing):

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


Related Topics



Leave a reply



Submit