irrKlang sound emitter and listener scene nodes

Post your questions, suggestions and experiences regarding game design, integration of external libraries here. For irrEdit, irrXML and irrKlang, see the
ambiera forums
Post Reply
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

irrKlang sound emitter and listener scene nodes

Post by vitek »

Basically these scene nodes automatically update the irrKlang sound listener and sound emitter positions. All you should need to do to use them...
  1. create and initialize irrKlang engine and irrlicht device
  2. create a sound listener and set it to be the child of the camera
  3. create a sound emitter.
  4. create some sounds, bind them to the emitter with emitter->addSound()

Code: Select all

// irrlicht
#include <ISceneManager.h>
#include <ISceneNode.h>
#include <IVideoDriver.h>
#include <irrArray.h>

// irrKlang
#include <ISoundEngine.h>
#include <ISound.h>

namespace irr {
namespace scene {

class CSoundEmitterSceneNode : public ISceneNode
{
public:
   //! constructor
   CSoundEmitterSceneNode(ISceneNode* parent,
                          ISceneManager* mgr,
                          s32 id = -1);

   //! destructor
   virtual ~CSoundEmitterSceneNode();

   //! This method is called just before the rendering process of the whole scene.
   virtual void OnPreRender();

   //! does nothing.
   virtual void render();

   //! This method is called just after the rendering process of the whole scene.
   virtual void OnPostRender(u32 timeMs);

   //! Just get the bounding box
   const core::aabbox3d<f32>& getBoundingBox() const;

   //! Add a sound to this emitter
   virtual void addSound(audio::ISound* sound);

   //! Remove a sound from this emitter
   virtual void removeSound(audio::ISound* sound);

   //! Remove all sounds from this emitter
   virtual void removeAllSounds();

   //! Get the number of sounds in this emitter
   virtual u32 getSoundCount() const;

   //! Get a specific sound from this emitter
   virtual audio::ISound* getSound(u32 index);

private:
   //! non-virtual used to cleanup sound resources
   void deleteAllSounds();

private:
   core::aabbox3d<f32> Box;

   // we manage more than one sound emitter
   core::array<audio::ISound*> Sounds;
};


class CSoundListenerSceneNode : public ISceneNode
{
public:
   CSoundListenerSceneNode(ISceneNode* parent,
                           ISceneManager* mgr,
                           audio::ISoundEngine* eng,
                           u32 now,
                           s32 id = -1);

   virtual ~CSoundListenerSceneNode();

public:
   //! This method is called just before the rendering process of the whole scene.
   virtual void OnPreRender();

   //! does nothing.
   virtual void render();

   //! This method is called just after the rendering process of the whole scene.
   virtual void OnPostRender(u32 timeMs);

   //! Just get the bounding box
   const core::aabbox3d<f32>& getBoundingBox() const;

private:
   // our bounding box for debug drawing
   core::aabbox3d<f32> Box;

   // the sound engine
   audio::ISoundEngine* SoundEngine;

   // doppler support
   core::vector3df PreviousPosition;
   u32 PreviousTime;
};

CSoundEmitterSceneNode::CSoundEmitterSceneNode(
      ISceneNode* parent,
      ISceneManager* mgr,
      s32 id)
   : ISceneNode(parent, mgr, id)
{
#ifdef _DEBUG
   setDebugName("CSoundEmitterSceneNode");
#endif

   setAutomaticCulling(false);
}

CSoundEmitterSceneNode::~CSoundEmitterSceneNode()
{
   deleteAllSounds();
}

void CSoundEmitterSceneNode::OnPreRender()
{
   if (IsVisible)
      SceneManager->registerNodeForRendering(this, ESNRP_SOLID);

   ISceneNode::OnPreRender();
}

void CSoundEmitterSceneNode::render()
{
   // for debug purposes only, draw a blue box for audio listeners
   if (DebugDataVisible)
   {
      video::IVideoDriver* driver = SceneManager->getVideoDriver();
      driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);

      video::SMaterial m;
      m.Lighting = false;
      driver->setMaterial(m);

      driver->draw3DBox(Box, video::SColor(255, 0, 0, 255));
   }
}

void CSoundEmitterSceneNode::OnPostRender(u32 timeMs)
{
   ISceneNode::OnPostRender(timeMs);

   // would be nice if irrKlang supported velocity/doppler for sounds

   // update the sounds source positions
   const core::vector3df position = getAbsolutePosition();

   u32 s;
   for (s = 0; s < Sounds.size(); ++s)
      Sounds[s]->setPosition(position);
}

const core::aabbox3d<f32>& CSoundEmitterSceneNode::getBoundingBox() const
{
   return Box;
}

void CSoundEmitterSceneNode::addSound(audio::ISound* sound)
{
   // you can't insert the same sound multiple times
   s32 e = Sounds.binary_search(sound);
   if (e != -1)
      return;

   Sounds.push_back(sound);
   sound->grab();

   // make sure the sound is at the correct position
   const core::vector3df position = getAbsolutePosition();
   sound->setPosition(position);
}

void CSoundEmitterSceneNode::removeSound(audio::ISound* sound)
{
   s32 s = Sounds.binary_search(sound);
   if (s != -1)
   {
      Sounds.erase(s);
      sound->drop();
   }
}

void CSoundEmitterSceneNode::removeAllSounds()
{
   deleteAllSounds();
}

u32 CSoundEmitterSceneNode::getSoundCount() const
{
   return Sounds.size();
}

audio::ISound* CSoundEmitterSceneNode::getSound(u32 index)
{
   return Sounds[index];
}

void CSoundEmitterSceneNode::deleteAllSounds()
{
   u32 s;
   for (s = 0; s < Sounds.size(); ++s)
      Sounds[s]->drop();

   Sounds.clear();
}

CSoundListenerSceneNode::CSoundListenerSceneNode(ISceneNode* parent,
                                                 ISceneManager* mgr,
                                                 audio::ISoundEngine* eng,
                                                 u32 now,
                                                 s32 id)
   : ISceneNode(parent, mgr, id)
   , SoundEngine(eng)
   , PreviousTime(now)
{
#ifdef _DEBUG
   setDebugName("CSoundListenerSceneNode");
#endif

   if (SoundEngine)
      SoundEngine->grab();

   setAutomaticCulling(false);
}

CSoundListenerSceneNode::~CSoundListenerSceneNode()
{
   if (SoundEngine)
      SoundEngine->drop();
}

void CSoundListenerSceneNode::OnPreRender()
{
   // listener is always rendered!
   if (SoundEngine)
      SceneManager->registerNodeForRendering(this, ESNRP_SOLID);

   ISceneNode::OnPreRender();
}

void CSoundListenerSceneNode::render()
{
   // for debug purposes only, draw a red box for audio listener
   if (DebugDataVisible)
   {
      video::IVideoDriver* driver = SceneManager->getVideoDriver();
      driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);

      video::SMaterial m;
      m.Lighting = false;
      driver->setMaterial(m);

      driver->draw3DBox(Box, video::SColor(255, 255, 0, 0));
   }
}

void CSoundListenerSceneNode::OnPostRender(u32 timeMs)
{
   // update our transformation matrix
   ISceneNode::OnPostRender(timeMs);

   // get the up vector
   core::vector3df upwards(0.f, 1.f, 0.f);
   getAbsoluteTransformation().rotateVect(upwards);

   // and forward vector
   core::vector3df forwards(0.f, 0.f, 1.f);
   getAbsoluteTransformation().rotateVect(forwards);

   // do doppler
   const f32 elapsed = (timeMs - PreviousTime) / 1000.f;
   PreviousTime = timeMs;

   // do position/velocity
   const core::vector3df position = getAbsolutePosition();
   const core::vector3df velocity = (position - PreviousPosition) / elapsed;
   PreviousPosition = position;

   // feed position, velocity and orientation information to sound engine
   SoundEngine->setListenerPosition(position, forwards, velocity, upwards);
}

const core::aabbox3d<f32>& CSoundListenerSceneNode::getBoundingBox() const
{
   return Box;
}

} // namespace scene
} // namespace irr
Here is an example of how to use it...

Code: Select all

#include <irrlicht.h>
#pragma comment(lib, "irrlicht.lib")

#include <irrklang.h>
#pragma comment(lib, "irrklang.lib")

using namespace irr;

int main()
{
  IrrlichtDevice *device =
    createDevice(video::EDT_OPENGL, core::dimension2d<s32>(640, 480), 16);
  if (!device)
    return 0;

  audio::ISoundEngine* engine = audio::createIrrKlangDevice();
  if (!engine)
    return 0;

  device->setWindowCaption(L"Irrlicht/IrrKlang demo");

  video::IVideoDriver* driver = device->getVideoDriver();
  scene::ISceneManager* smgr = device->getSceneManager();

  // note!!!
  //
  // modified version of example.irr has a node with name 'sphere'
  //
  smgr->loadScene("../../media/example.irr");

  scene::ISceneNode* camera = smgr->addCameraSceneNodeFPS(); 

  scene::CSoundListenerSceneNode* listener = 
    new scene::CSoundListenerSceneNode(camera, smgr, engine, device->getTimer()->getRealTime());
  listener->setDebugDataVisible(true);

  scene::ISceneNode* node = smgr->getSceneNodeFromName("sphere");
  if (node)
  {
    scene::CSoundEmitterSceneNode* emitter =
      new scene::CSoundEmitterSceneNode(node, smgr); 
    emitter->setDebugDataVisible(true);

    audio::ISound* sound =
      engine->play3D("../../media/irrlichttheme.ogg", node->getAbsolutePosition(), true, false, true);
    sound->setMinDistance(10.f);
    sound->setMaxDistance(100.f);

    emitter->addSound(sound);
  }

  while(device->run())
  {
    if (driver->beginScene(true, true, video::SColor(255,100,101,140)))
    {
      smgr->drawAll();

      driver->endScene();
    }
  }

  device->drop();

  return 0;
}
Travis
Last edited by vitek on Sat Dec 23, 2006 11:00 pm, edited 2 times in total.
Anteater
Posts: 266
Joined: Thu Jun 01, 2006 4:02 pm
Location: Earth
Contact:

Post by Anteater »

Cool. What's the license?
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

For you $50. Everyone else can do as they wish. Kidding... irrKlang has its own license, and the above code is free for the taking.
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

Oh yeah, that's definitely a brilliant addition. Now the only thing that's missing is a Linux/OSX port of irrKlang. I guess it's now time to add the 'contributions' directory for extensions which are not usable without external files. But just as the true type scene node it's a pretty useful thing and we can more easily keep those things in sync if they are tagged as officially supported.
sio2
Competition winner
Posts: 1003
Joined: Thu Sep 21, 2006 5:33 pm
Location: UK

Post by sio2 »

This is also a nice example of Irrlicht internals and how to use Irrlicht interfaces to extend functionality (without having to edit the source and recompile the dll).

I got it working pretty easily, with a few minor niggles, and it seems to work really well with irrKlang 0.4.

I lumped all the code into one "main.cpp" but if I were using it in a project I'd separate the class into its own cpp file and header file. A minor point and easy enough to do.

I'm using SVN rev. 406 and the debug settings have changed from a bool type to an enumerated type. Once again, simple enough to modify.

I had no sound to begin with and was scratching my head until I realised I'd named my sphere scene node "Sphere" instead of "sphere". :roll:

A strange issue: the sphere node in example.irr is unnamed so I opened it in irrEdit 0.6, gave the sphere a name (so the example code could find it by name) and saved the scene as example_sphere.irr. When I ran my app it was as though all scene nodes had debug data enabled (wireframe/billboards). Opening example.irr in Notepad, manually adding the name and saving to example_sphere.irr showed the scene just fine in my app, though. :?
Midnight
Posts: 1772
Joined: Fri Jul 02, 2004 2:37 pm
Location: Wonderland

Post by Midnight »

sounds like an irredit bug.

haven't tried this yet but knowing vitek this is an awsome addition.

thanks again dude.
eviral
Posts: 91
Joined: Mon Oct 25, 2004 10:25 am

Update it for Irrlicht 1.3 pleeeeaaaaaase

Post by eviral »

Hello Vitek,

Nice wrapper !

Could you please update it for Irrlicht 1.3 ?

OnPreRender doesn't exist anymore for example...

Thanks a lot !

Eviral
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

Lots of people still use Irrlicht 1.2 or earlier. You can easily fix this yourself using search and replace. OnPreRender becomes OnRegisterSceneNode and OnPostRender becomes OnAnimate.
eviral
Posts: 91
Joined: Mon Oct 25, 2004 10:25 am

VERSION FOR IRRLICHT 1.3 IS HERE

Post by eviral »

Post updated...

HERE IS THE FINAL VERSION 100% WORKING WITH IRRLICHT 1.3 :

// irrlicht
#include <ISceneManager.h>
#include <ISceneNode.h>
#include <IVideoDriver.h>
#include <irrArray.h>

// irrKlang
#include <ISoundEngine.h>
#include <ISound.h>

namespace irr {
namespace scene {

class CSoundEmitterSceneNode : public ISceneNode
{
public:
//! constructor
CSoundEmitterSceneNode(ISceneNode* parent,
ISceneManager* mgr,
s32 id = -1);

//! destructor
virtual ~CSoundEmitterSceneNode();

//! This method is called just before the rendering process of the whole scene.
virtual void OnRegisterSceneNode();

//! does nothing.
virtual void render();

//! This method is called just after the rendering process of the whole scene.
virtual void OnAnimate(u32 timeMs);

//! Just get the bounding box
const core::aabbox3d<f32>& getBoundingBox() const;

//! Add a sound to this emitter
virtual void addSound(audio::ISound* sound);

//! Remove a sound from this emitter
virtual void removeSound(audio::ISound* sound);

//! Remove all sounds from this emitter
virtual void removeAllSounds();

//! Get the number of sounds in this emitter
virtual u32 getSoundCount() const;

//! Get a specific sound from this emitter
virtual audio::ISound* getSound(u32 index);

private:
//! non-virtual used to cleanup sound resources
void deleteAllSounds();

private:
core::aabbox3d<f32> Box;

// we manage more than one sound emitter
core::array<audio::ISound*> Sounds;
};


class CSoundListenerSceneNode : public ISceneNode
{
public:
CSoundListenerSceneNode(ISceneNode* parent,
ISceneManager* mgr,
audio::ISoundEngine* eng,
u32 now,
s32 id = -1);

virtual ~CSoundListenerSceneNode();

public:
//! This method is called just before the rendering process of the whole scene.
virtual void OnRegisterSceneNode();

//! does nothing.
virtual void render();

//! This method is called just after the rendering process of the whole scene.
virtual void OnAnimate(u32 timeMs);

//! Just get the bounding box
const core::aabbox3d<f32>& getBoundingBox() const;

private:
// our bounding box for debug drawing
core::aabbox3d<f32> Box;

// the sound engine
audio::ISoundEngine* SoundEngine;

// doppler support
core::vector3df PreviousPosition;
u32 PreviousTime;
};

CSoundEmitterSceneNode::CSoundEmitterSceneNode(
ISceneNode* parent,
ISceneManager* mgr,
s32 id)
: ISceneNode(parent, mgr, id)
{
#ifdef _DEBUG
setDebugName("CSoundEmitterSceneNode");
#endif

setAutomaticCulling(irr::scene::E_CULLING_TYPE::EAC_OFF);
}

CSoundEmitterSceneNode::~CSoundEmitterSceneNode()
{
deleteAllSounds();
}

void CSoundEmitterSceneNode::OnRegisterSceneNode()
{
if (IsVisible)
SceneManager->registerNodeForRendering(this, ESNRP_SOLID);

//ISceneNode::OnPreRender();
ISceneNode::OnRegisterSceneNode();

}

void CSoundEmitterSceneNode::render()
{
// for debug purposes only, draw a blue box for audio listeners
if (DebugDataVisible)
{
video::IVideoDriver* driver = SceneManager->getVideoDriver();
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);

video::SMaterial m;
m.Lighting = false;
driver->setMaterial(m);

driver->draw3DBox(Box, video::SColor(255, 0, 0, 255));
}
}

void CSoundEmitterSceneNode::OnAnimate(u32 timeMs)
{
//ISceneNode::OnPostRender(timeMs);
ISceneNode::OnAnimate(timeMs);

// would be nice if irrKlang supported velocity/doppler for sounds

// update the sounds source positions
const core::vector3df position = getAbsolutePosition();

u32 s;
for (s = 0; s < Sounds.size(); ++s)
Sounds[s]->setPosition(position);
}

const core::aabbox3d<f32>& CSoundEmitterSceneNode::getBoundingBox() const
{
return Box;
}

void CSoundEmitterSceneNode::addSound(audio::ISound* sound)
{
// you can't insert the same sound multiple times
s32 e = Sounds.binary_search(sound);
if (e != -1)
return;

Sounds.push_back(sound);
sound->grab();

// make sure the sound is at the correct position
const core::vector3df position = getAbsolutePosition();
sound->setPosition(position);
}

void CSoundEmitterSceneNode::removeSound(audio::ISound* sound)
{
s32 s = Sounds.binary_search(sound);
if (s != -1)
{
Sounds.erase(s);
sound->drop();
}
}

void CSoundEmitterSceneNode::removeAllSounds()
{
deleteAllSounds();
}

u32 CSoundEmitterSceneNode::getSoundCount() const
{
return Sounds.size();
}

audio::ISound* CSoundEmitterSceneNode::getSound(u32 index)
{
return Sounds[index];
}

void CSoundEmitterSceneNode::deleteAllSounds()
{
u32 s;
for (s = 0; s < Sounds.size(); ++s)
Sounds[s]->drop();

Sounds.clear();
}

CSoundListenerSceneNode::CSoundListenerSceneNode(ISceneNode* parent,
ISceneManager* mgr,
audio::ISoundEngine* eng,
u32 now,
s32 id)
: ISceneNode(parent, mgr, id)
, SoundEngine(eng)
, PreviousTime(now)
{
#ifdef _DEBUG
setDebugName("CSoundListenerSceneNode");
#endif

if (SoundEngine)
SoundEngine->grab();

setAutomaticCulling(irr::scene::E_CULLING_TYPE::EAC_OFF);
}

CSoundListenerSceneNode::~CSoundListenerSceneNode()
{
if (SoundEngine)
SoundEngine->drop();
}

void CSoundListenerSceneNode::OnRegisterSceneNode()
{
// listener is always rendered!
if (SoundEngine)
SceneManager->registerNodeForRendering(this, ESNRP_SOLID);

//ISceneNode::OnPreRender();
ISceneNode::OnRegisterSceneNode();
}

void CSoundListenerSceneNode::render()
{
// for debug purposes only, draw a red box for audio listener
if (DebugDataVisible)
{
video::IVideoDriver* driver = SceneManager->getVideoDriver();
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);

video::SMaterial m;
m.Lighting = false;
driver->setMaterial(m);

driver->draw3DBox(Box, video::SColor(255, 255, 0, 0));
}
}

void CSoundListenerSceneNode::OnAnimate(u32 timeMs)
{
// update our transformation matrix
//ISceneNode::OnPostRender(timeMs);
ISceneNode::OnAnimate(timeMs);

// get the up vector
core::vector3df upwards(0.f, 1.f, 0.f);
getAbsoluteTransformation().rotateVect(upwards);

// and forward vector
core::vector3df forwards(0.f, 0.f, 1.f);
getAbsoluteTransformation().rotateVect(forwards);

// do doppler
const f32 elapsed = (timeMs - PreviousTime) / 1000.f;
PreviousTime = timeMs;

// do position/velocity
const core::vector3df position = getAbsolutePosition();
const core::vector3df velocity = (position - PreviousPosition) / elapsed;
PreviousPosition = position;

// feed position, velocity and orientation information to sound engine
SoundEngine->setListenerPosition(position, forwards, velocity, upwards);
}

const core::aabbox3d<f32>& CSoundListenerSceneNode::getBoundingBox() const
{
return Box;
}

} // namespace scene
} // namespace irr
Last edited by eviral on Sat Mar 31, 2007 5:58 pm, edited 1 time in total.
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

You need to replace _all_ instances of the strings as mentioned above. You should replace OnPreRender with OnRegisterSceneNode and you should replace OnPostRender with OnAnimate. The old names [OnPreRender and OnPostRender] will be completely removed if you did it right, and you won't have to add a fake timeMs in there anywhere.

Travis
eviral
Posts: 91
Joined: Mon Oct 25, 2004 10:25 am

Ok but what about timeMs ???

Post by eviral »

Ok, i've replace all instances of onPreRender and onPostRender as you told me but about the fake TimeMs I don't know how to dot because OnRegisterSceneNode() doesn't have any parameter.

timeMs is now a parameter of onAnimate.

Do i need to move the part of code with TimeMs into OnAnimate instead of OnRegisterSceneNode ?


Please help, i really need your class working with IrrLicht 1.3.

Thanks

Eviral



void CSoundListenerSceneNode::OnRegisterSceneNode()
{
// update our transformation matrix
//ISceneNode::OnPostRender(timeMs);
ISceneNode::OnRegisterSceneNode();

// get the up vector
core::vector3df upwards(0.f, 1.f, 0.f);
getAbsoluteTransformation().rotateVect(upwards);

// and forward vector
core::vector3df forwards(0.f, 0.f, 1.f);
getAbsoluteTransformation().rotateVect(forwards);

irr::u32 timeMs = 5; // TEMP HARD CODED TIME

// do doppler
const f32 elapsed = (timeMs - PreviousTime) / 1000.f;
PreviousTime = timeMs;

// do position/velocity
const core::vector3df position = getAbsolutePosition();
const core::vector3df velocity = (position - PreviousPosition) / elapsed;
PreviousPosition = position;

// feed position, velocity and orientation information to sound engine
SoundEngine->setListenerPosition(position, forwards, velocity, upwards);
}
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

I'm losing my patience. I said to replace OnPreRender() with OnRegisterSceneNode(). Neither of them take a timeMs parameter, so there is no problem. Any problem that you are running into you've created yourself. You are replacing OnPostRender() with OnRegisterSceneNode() and that is just plain wrong. If the function takes a timeMs parameter, it should be renamed to OnAnimate(). If it doesn't, then it should be renamed to OnRegisterSceneNode().

I suggest you go back to the original code at the top of this thread. Copy and paste the original code above into your source code editor. Do the search and replace as I've suggested three times. It will work. Once you get it working, please come back here and delete the bad code that you have repeatedly pasted so that others don't have to wade through the confusion.

Travis
eviral
Posts: 91
Joined: Mon Oct 25, 2004 10:25 am

Sorry Sorry Sorry

Post by eviral »

ok, ok, sorry...

It works well now...

Thanks a lot
MasterGod
Posts: 2061
Joined: Fri May 25, 2007 8:06 pm
Location: Israel
Contact:

Post by MasterGod »

vitek, your scene nodes are a great addition to my engine. They work perfectly and they are exactly what I needed.
Thanks a lot.
Thanks eviral too for converting to newer edition of Irrlicht.
Image
Dev State: Abandoned (For now..)
Requirements Analysis Doc: ~87%
UML: ~0.5%
Post Reply