SKEmiterNode with AVAudioPlayer for music visuals

2019-08-30 07:32发布

问题:

PLEASE SOMEONE HELP!

I want to have my SKEmiterNode's scale(meaning size) get larger and smaller to the music i have built into the application using AVAudioPlayer. Right now this is pretty much all I have for the SKEmiterNode and it looks great:

beatParticle?.position = CGPoint(x: self.size.width * 0.5, y: self.size.height * 0.5)

var beatParticleEffectNode = SKEffectNode()
beatParticleEffectNode.addChild(beatParticle!)
self.addChild(beatParticleEffectNode)

All the looks are done in the .sks file.

Here is where I call the "updateBeatParticle" function in a continual loop so that It can where i will put my code for making the particle's scale(meaning size) larger and smaller to the music.

var dpLink : CADisplayLink?

dpLink = CADisplayLink(target: self, selector: "updateBeatParticle")
dpLink?.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)

func updateBeatParticle(){
//Put code here
}

Any idea how i can do this? I looked at some tutorials such as this: https://www.raywenderlich.com/36475/how-to-make-a-music-visualizer-in-ios

However, i can't quite get my head around it because they're using an emitterLayer and its in Obj-C and am also interested in any other ideas you wonderful people may have!

回答1:

WARNING: The following code has not been tested. Please let me know if it works.

Firstly, it looks like you are using SpriteKit, therefore you could put the code needed to alter the emitter scale in the SKScene method update:, which automatically gets called virtually as often as a CADisplayLink.

Essentially all you need to do is update the emitter scale in the update: method based on the volume of the channels of your AVAudioPlayer. Note that the audio player may have multiple channels running, so you need to average out the average power for each.

Firstly...

player.meteringEnabled = true 

Set this after you initialise your audio player, so that it will monitor the levels of the channels.

Next, add something like this in your update method.

override func update(currentTime: CFTimeInterval) {

    var scale: CGFloat = 0.5

    if audioPlayer.playing { // Only do this if the audio is actually playing
        audioPlayer.updateMeters() // Tell the audio player to update and fetch the latest readings

        let channels = audioPlayer.numberOfChannels

        var power: Float = 0
        // Loop over each channel and add its average power
        for i in 0..<channels {
            power += audioPlayer.averagePowerForChannel(i)
        }
        power /= Float(channels) // This will give the average power across all the channels in decibels

        // Convert power in decibels to a more appropriate percentage representation
        scale = CGFloat(getIntensityFromPower(power))

    }

    // Set the particle scale to match
    emitterNode.particleScale = scale
}

The method getIntensityFromPower is used to convert the power in decibels, to a more appropriate percentage representation. This method can be declared like so...

// Will return a value between 0.0 ... 1.0, based on the decibels
func getIntensityFromPower(decibels: Float) -> Float {
    // The minimum possible decibel returned from an AVAudioPlayer channel
    let minDecibels: Float = -160
    // The maximum possible decibel returned from an AVAudioPlayer channel
    let maxDecibels: Float = 0

    // Clamp the decibels value
    if decibels < minDecibels {
        return 0
    }
    if decibels >= maxDecibels {
        return 1
    }

    // This value can be adjusted to affect the curve of the intensity
    let root: Float = 2

    let minAmp = powf(10, 0.05 * minDecibels)
    let inverseAmpRange: Float = 1.0 / (1.0 - minAmp)
    let amp: Float = powf(10, 0.05 * decibels)
    let adjAmp = (amp - minAmp) * inverseAmpRange

    return powf(adjAmp, 1.0 / root)
}

The algorithm for this conversion was taken from this StackOverflow response https://stackoverflow.com/a/16192481/3222419.