3d audio bug with 'coordinate-system yup'

I use the “y-up” coordinate system in my panda3d project, as this is what I am used to. This works great for all of the graphics features, but it seems to have no effect on the positional audio. I am using Panda3D 1.5.4 and the default openAL sound library (tried FMOD, it has the same problem).

I thought it might be something specific to my project, but I modified the 3d sound demo from the “Panda3D Audio for the Absolute Beginner” tutorial and I get the same behaviour. In the unmodified example I hold S to bring the cube behind the camera; the audio is now playing on the rear left speaker. In the modified example below, the audio is playing on both the rear left and front left speakers (I believe the audio system still thinks Z-axis is up and down).

Is there another setting to change the coordinate system of the audio, or is this a genuine bug?

I’ve included the modified tutorial code below (5-MovingSound.py):

#################################################
#    Panda3D Audio for the Absolute Beginner    #
#            Created by: Joe Kelly              #
#          Lesson 5 - Moving 3D audio           #
#################################################
from pandac.PandaModules import loadPrcFileData
loadPrcFileData('', 'coordinate-system yup')

import direct.directbase.DirectStart

#Import our class outline
from direct.showbase.DirectObject import DirectObject

#Import general program functions
import sys

#Import the Task library
from direct.task.Task import Task
from direct.showbase.Audio3DManager import Audio3DManager

#Create the Class
class World(DirectObject):

  #Initialize World and Controls
  def __init__(self):

    #Start All Keys as "up"
    self.keyMap = {"left":0, "right":0, "up":0, "down":0}

    #List Keys for Use
    self.accept("escape", sys.exit)
    self.accept("a", self.setKey, ["left",1])
    self.accept("d", self.setKey, ["right",1])
    self.accept("w", self.setKey, ["up",1])
    self.accept("s", self.setKey, ["down",1])
    self.accept("a-up", self.setKey, ["left",0])
    self.accept("d-up", self.setKey, ["right",0])
    self.accept("w-up", self.setKey, ["up",0])
    self.accept("s-up", self.setKey, ["down",0])

    #Do not allow mouse control of camera
    base.disableMouse()

    #Load Models
    self.boxDude = loader.loadModel("models/box")
    self.boxDude.reparentTo(render)
    self.boxDude.setPos(-8,0,-50)

    self.sphereDude = loader.loadModel("models/sphere")
    self.sphereDude.reparentTo(render)
    self.sphereDude.setPos(8,0,-50)

    #Add movement to Task list
    taskMgr.add(self.move,"moveFunc")

    #Play sound
    self.audio3d = Audio3DManager(base.sfxManagerList[0], camera)
    self.rainSound = self.audio3d.loadSfx('audio/shortrain.wav')
    self.audio3d.attachSoundToObject(self.rainSound, self.boxDude)
    self.rainSound.setLoop(True)
    self.rainSound.play()
    self.audio3d.setDropOffFactor(0.1)

  #Set keystate variables taken from the "accept" lines
  def setKey(self, key, value):
    self.keyMap[key] = value

  #Move objects based on keystates
  def move(self, task):

    if (self.keyMap["left"]!=0):
      self.boxDude.setPos(self.boxDude.getX() - 0.2, 0, self.boxDude.getZ())
    if (self.keyMap["right"]!=0):
      self.boxDude.setPos(self.boxDude.getX() + 0.2, 0, self.boxDude.getZ())
    if (self.keyMap["up"]!=0):
      self.boxDude.setPos(self.boxDude.getX(), 0, self.boxDude.getZ() - 0.2)
    if (self.keyMap["down"]!=0):
      self.boxDude.setPos(self.boxDude.getX(), 0, self.boxDude.getZ() + 0.2)

    return Task.cont

#Initialize the class
w = World()
run()

It’s quite likely a bug. Many subsystems in Panda have never been tested with anything other than the default Z-up coordinate system, and I suspect the 3-D audio system is one such subsystem.

Anyone who’d like to take a look at Audio3DManager to see what it might be doing wrong here is more than welcome to do so, and it would certainly be appreciated. :slight_smile:

David

Thanks for the reply David.
This seems to be the problem area (direct/src/showbase/Audio3DManager.py):

        # Update the position of the listener based on the object
        # to which it is attached
        if self.listener_target:
            pos = self.listener_target.getPos(self.root)
            forward = self.listener_target.getRelativeVector(self.root, VBase3(0,1,0))
            up = self.listener_target.getRelativeVector(self.root, VBase3(0,0,1))
            vel = self.getListenerVelocity()
            self.audio_manager.audio3dSetListenerAttributes(pos[0], pos[1], pos[2], vel[0], vel[1], vel[2], forward[0], forward[1], forward[2], up[0], up[1], up[2]) 
        else:
            self.audio_manager.audio3dSetListenerAttributes(0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1)
        return Task.cont

The “forward” and “up” directions are hard-coded for Z-up coordinate system. I was able to simply change the vectors to make it work properly with Y-up, but I don’t know what the proper fix would be to make it work with any coordinate system.

Ah, you mean the vectors VBase3(0,1,0) and VBase3(0,0,1)? These should be replaced by Vec3.forward() and Vec3.up(), respectively. Is this the only fix required?

Thanks!
David

Yes, everything seems to work fine with those changes. I didn’t see any other mentions of specific directions in the Audio3DManager.

FYI I ran into a similar problem with Panda’s native collision system, I believe it was the “pusher” if that makes sense. I use ODE now though so I couldn’t tell you for sure.

Hmm, I guess I’ll investigate the pusher. But if you can remember any more details, it would be helpful; I’m certain the original implementation of the pusher was cs-independent, and if there are any problems, they must be related to some very specific usage.

David

Luckily I keep backups of everything. :slight_smile:
In this example you can change the “yup” variable at the top to switch between default coordinate system and Y-up. With Y-up the collisions are happening at the right location, but the force put back onto the colliding object (player) is wrong. Often you will see the player is thrown violently sideways when walking straight-on into a wall, which does not happen with the default coordinate system.

http://drop.io/ucscbsf/asset/panda-yup-collision-zip

Ah, you’re right. That’s the “horizontal” flag misbehaving. Hmm, that flag really oughtn’t to be on by default.

David

Actually, for the Audio3DManager, I supposed the last line there should be changed as well:

        # Update the position of the listener based on the object
        # to which it is attached
        world_forward = Vec3.forward()
        world_up = Vec3.up()
        if self.listener_target:
            pos = self.listener_target.getPos(self.root)
            forward = self.listener_target.getRelativeVector(self.root, world_forward)
            up = self.listener_target.getRelativeVector(self.root, world_up)
            vel = self.getListenerVelocity()
            self.audio_manager.audio3dSetListenerAttributes(pos[0], pos[1], pos[2], vel[0], vel[1], vel[2], forward[0], forward[1], forward[2], up[0], up[1], up[2])
        else:
            self.audio_manager.audio3dSetListenerAttributes(0, 0, 0, 0, 0, 0, world_forward[0], world_forward[1], world_forward[2], world_up[0], world_up[1], world_up[2])
        return Task.cont

I am now looking at a different problem where it seems the audio positions do not update when the listener moves, but as soon as the object the sound is attached to moves the sound pops to the right direction relative to the listener. I’ll update when I have more information on that one.

I have a demo of this new bug now (it is not related to the coordinate system). I tried it with Panda 1.5.4 and 1.6.2, both had the same problem. In this example WASD is used to move the cube, and the positional audio is fine. TFGH is used to move the camera, and you will notice that the volume changes depending on distance, but the direction of the sound does not. As soon as you move the cube a tiny bit, the audio snaps to the proper direction.

import sys
from pandac.PandaModules import Vec3
import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject
from direct.showbase.Audio3DManager import Audio3DManager


SHIFT_AMOUNT = 0


class World(DirectObject):
  def __init__(self):
    self.keyMap = {}
    self.accept("escape", sys.exit)
    for key in [('a', 'left'), ('d', 'right'), ('w', 'up'), ('s', 'down'),
                ('f', 'cleft'), ('h', 'cright'), ('t', 'cup'), ('g', 'cdown')]:
        self.keyMap[key[1]] = 0
        self.accept(key[0], self.setKey, [key[1], 1])
        self.accept('%s-up' % key[0], self.setKey, [key[1], 0])

    #Do not allow mouse control of camera
    base.disableMouse()

    #Load Models
    self.boxDude = loader.loadModel("models/box")
    self.boxDude.reparentTo(render)
    self.sphereDude = loader.loadModel("models/sphere")
    self.sphereDude.reparentTo(render)
    self.boxDude.setPos(-8, 50, 0)
    self.sphereDude.setPos(8, 50, 0)

    #Add movement to Task list
    taskMgr.add(self.move,"moveFunc")

    #Play sound
    self.audio3d = Audio3DManager(base.sfxManagerList[0], camera)
    self.rainSound = self.audio3d.loadSfx('audio/shortrain.wav')
    self.audio3d.attachSoundToObject(self.rainSound, self.boxDude)
    self.rainSound.setLoop(True)
    self.rainSound.play()
    self.audio3d.setDropOffFactor(0.1)

  #Set keystate variables taken from the "accept" lines
  def setKey(self, key, value):
    self.keyMap[key] = value

  #Move objects based on keystates
  def move(self, task):
    v = Vec3(self.keyMap["right"] - self.keyMap["left"],
             self.keyMap["up"] - self.keyMap["down"], 0 + SHIFT_AMOUNT)
    cv = Vec3(self.keyMap["cright"] - self.keyMap["cleft"],
             self.keyMap["cup"] - self.keyMap["cdown"], 0)
    self.boxDude.setPos(self.boxDude.getPos() + v)
    camera.setPos(camera.getPos() + cv)

    return task.cont


w = World()
run()

As a test, you can change the SHIFT_AMOUNT variable to something small like 0.001 and this will move the cube a tiny bit each frame. Sure enough the audio direction updates properly now when moving the camera.

My guess:
There is some kind of optimization somewhere that skips the update if the sound has not moved position, but this update should happen if either the sound OR listener move.