Search code examples
objective-cswiftaugmented-realityscenekitarkit

ARKit – Spatial Audio barely changes the volume over distance


I created a SCNNode and added an Audio to it.

It is a Mono audio. Everything is set up correctly.

It is working as Spatial Audio, that's not the problem.

The problem is that as i get closer or far away it barely changes the volume. I know it changes if i get very very far away, but it's nothing like Apple demonstrated here:

https://youtu.be/d9kb1LfNNU4?t=23

Some other games i see the audio volume really changing from one step distance.

With mine, with one step you can't even tell the volume changed. You need at least 4 steps.

Anyone has any clue why?

Code bellow:

SCNNode *audioNode = [[SCNNode alloc] init];
SCNAudioSource *audioSource = [[SCNAudioSource alloc] initWithFileNamed:audioFileName];
audioSource.loops = YES;
[audioSource load];
audioSource.volume = 0.05; // <-- i used different values. won't change much either
audioSource.positional = YES;
//audioSource.shouldStream = NO; // <-- makes no difference
[audioNode addAudioPlayer:[SCNAudioPlayer audioPlayerWithSource:audioSource]];

[audioNode runAction:[SCNAction playAudioSource:audioSource waitForCompletion:NO] completionHandler:nil];
[massNode addChildNode:audioNode];

Maybe scale of the nodes?

The whole scene is the size of around 4 feet.

When i add an object i usually scale it to 0.005 (otherwise it gets way too big). But i also tried with one that was already in the right size from .scn file.

It shouldn't affect anything tho, since the result is a coffee table size scene and i can see the objects alright.


Solution

  • Updated.

    Here's a working code for controlling sound's decay (works in iOS and macOS):

    import AVFoundation
    import ARKit
    
    class ViewController: UIViewController, AVAudioMixing {
    
        @IBOutlet var sceneView: SCNView!
        // @IBOutlet var sceneView: ARSCNView!
        
        func destination(forMixer mixer: AVAudioNode,
                                    bus: AVAudioNodeBus) -> AVAudioMixingDestination? {
            return nil
        }
        var volume: Float = 0.0
        var pan: Float = 0.0
        
        var sourceMode: AVAudio3DMixingSourceMode = .bypass
        var pointSourceInHeadMode: AVAudio3DMixingPointSourceInHeadMode = .bypass
        
        var renderingAlgorithm = AVAudio3DMixingRenderingAlgorithm.sphericalHead
        var rate: Float = 1.2
        var reverbBlend: Float = 40.0
        var obstruction: Float = -100.0
        var occlusion: Float = -100.0
        var position = AVAudio3DPoint(x: 0, y: 0, z: 10)
        let audioNode = SCNNode()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            let myScene = SCNScene()
            let cameraNode = SCNNode()
            cameraNode.camera = SCNCamera()
            cameraNode.position = SCNVector3(0, 0, 0)
            myScene.rootNode.addChildNode(cameraNode)
            
            // let sceneView = view as! SCNView
            sceneView.scene = myScene
            sceneView.backgroundColor = UIColor.orange
            
            let myPath = Bundle.main.path(forResource: "Mono_Audio", ofType: "mp3")           
            let myURL = URL(fileURLWithPath: myPath!)
            let mySource = SCNAudioSource(url: myURL)!
            mySource.loops = true
            mySource.isPositional = true           // Positional Audio
            mySource.shouldStream = false          // FALSE for Positional Audio
            mySource.volume = volume
            mySource.reverbBlend = reverbBlend
            mySource.rate = rate
    
            mySource.load()
            
            let player = SCNAudioPlayer(source: mySource)
            let sphere: SCNGeometry = SCNSphere(radius: 0.1)
            let sphereNode = SCNNode(geometry: sphere)
            sphereNode.addChildNode(audioNode)
            myScene.rootNode.addChildNode(sphereNode)
            audioNode.addAudioPlayer(player)            
    
            sceneView.audioEnvironmentNode.distanceAttenuationParameters.maximumDistance = 2
            sceneView.audioEnvironmentNode.distanceAttenuationParameters.referenceDistance = 0.1   
            sceneView.audioEnvironmentNode.renderingAlgorithm = .auto
    
            // sceneView.audioEnvironmentNode.reverbParameters.enable = true
            // sceneView.audioEnvironmentNode.reverbParameters.loadFactoryReverbPreset(.plate)
            
            let hither = SCNAction.moveBy(x: 0, y: 0, z: 1, duration: 2)
            let thither = SCNAction.moveBy(x: 0, y: 0, z: -1, duration: 2)
            
            let sequence = SCNAction.sequence([hither, thither])
            let loop = SCNAction.repeatForever(sequence)
            sphereNode.runAction(loop) 
        }
    }
    

    And, yes, you're absolutely right – there are some obligatory settings.

    But there are 7 of them:

    • use AVAudioMixing protocol with its stubs (properties and methods).

    • use MONO audio file.

    • use source.isPositional = true.

    • use source.shouldStream = false.

    • assign maximumDistance value to distanceAttenuationParameters property.

    • assign referenceDistance value to distanceAttenuationParameters property.

    • and location of mySource.load() is very important in your code.

    P.S. If the aforementioned tips didn't help you, then use additional instance properties to make your sound even quieter using a graph, obstacles and orientation of implicit listener:

    var rolloffFactor: Float { get set }      // attenuation's graph, default = 1 
    
    var obstruction: Float { get set }        // default = 0.0
    
    var occlusion: Float { get set }          // default = 0.0
    
    var listenerAngularOrientation: AVAudio3DAngularOrientation { get set } //(0,0,0)
    

    It definitely works if you'll write it in Objective-C.

    In this example the distance of audioNode is 1 meter away from a listener.