A new Sound class (Maybe useless, but was the only solution for me!)

Hi, i've tried to manage the Sample3D and something else but i can't get that running. I've some difficulties in understanding the furball SoundManage class too, so i decide to write a class based on a clear and simple tutorial on OpenAL, posted on the LWJGL site. Here is my class (really easy to be used). It allow to create a "sound node" and attaching to others.

Read the initial description to understand usage and limits!! Feel free to modify and if any of the guru think that is interesting take it and insert in the API!



Hope this is usefull for someone!

Bye!







import java.nio.FloatBuffer;

import java.nio.IntBuffer;



import org.lwjgl.BufferUtils;

import org.lwjgl.LWJGLException;

import org.lwjgl.openal.AL;

import org.lwjgl.openal.AL10;

import org.lwjgl.util.WaveData;



import com.jme.math.Vector3f;

import com.jme.renderer.Camera;

import com.jme.renderer.Renderer;

import com.jme.scene.Node;



/*

  • Created on 29-mar-2006
  • @author Alberto Plebani

    *
  • This class is based on the OpenAL LWJGL tutorial. It is adapted to allow
  • the creation of a com.jme.scene.Node which can be attached to other Node
  • istances. The sound, obviously, will follow the node itself!

    *
  • The following is the right way to operate with an OpenALSoundNode:

    *
  • Node aSimpleNode=new Node("NodeName");


  • *
  • OpenALSoundNode music=new OpenALSoundNode("MusicNode");
  • music.setSampleAddress("turn.wav"); //Address as a String
  • music.setCamera(this.cam); //Our cam becomes our ears!
  • music.setLoop(true); //If you want looping (by default is false)
  • music.play(); //Now listen!
  • aSimpleNode.attachChild(music);

    *

  • music.stop() //Easy to understand!

    *
  • There are also a few methods to retrieve and change the state of the sample.

    *
  • If you want to have your sound used as a background sound, you should
  • create an OpenALSoundNode and attach it to your cameraNode. By this
  • way your sound node will always follow your camera and will always face
  • it the same way: so you'll have your soundtrack. Maybe if i'll have
  • time i'll create a method which easily configure this without the need
  • of a CameraNode.

    *
  • This is still a basic implementation, but makes its job well!
  • Only one note: actually this class is able to load only .wav sound file.

    *
  • FEEL FREE TO MODIFY THIS CLASS FOR ANY PURPOSE AND REMEMBER TO TEST THIS
  • CLASS WHICH HAS NOT BEEN FULLY TESTED!

    *
  • TODO:
  • Implement various sound format reading
  • Implement event triggers (actually you have to start playing manually)
  • Implement soundtrack feature (as previously explained)

    *

    */



    /**
  • @author Alberto Plebani

    */

    public class OpenALSoundNode extends Node {



        private Vector3f lastPosition;

        private String sampleAddress;

        private boolean loop=false;

        private Camera camera;

        private Vector3f lastCameraLocation,lastCameraDirection,lastCameraUp;

       

        public static final int PLAYING=0;

        public static final int PAUSED=1;

        public static final int STOPPED=2;

        public static final int UNDEFINED=3;

       

        private IntBuffer buffer=BufferUtils.createIntBuffer(1);

        private IntBuffer source=BufferUtils.createIntBuffer(1);

        private FloatBuffer sourcePos=BufferUtils.createFloatBuffer(3).put(new float[] { 0.0f, 0.0f, 0.0f });

        private FloatBuffer sourceVel=BufferUtils.createFloatBuffer(3).put(new float[] { 0.0f, 0.0f, 0.0f });

        private FloatBuffer listenerPos=BufferUtils.createFloatBuffer(3).put(new float[] { 0.0f, 0.0f, 0.0f });

        private FloatBuffer listenerVel=BufferUtils.createFloatBuffer(3).put(new float[] { 0.0f, 0.0f, 0.0f });

        private FloatBuffer listenerOri=BufferUtils.createFloatBuffer(6).put(new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f });

       

        /

        * This is the only costructor. Use this every time you want to

        * instantiate a new OpenALSoundNode (long class name only because

        * SoundNode is already in use by two different classes!)

        * @param nodeName: the name of the node that you want to assign.

        */

        public OpenALSoundNode(String nodeName) {

            super(nodeName);

            this.lastPosition=this.getWorldTranslation().add(this.getLocalTranslation());

            initAL();

            initFlip();

            initPos();

        }

       

        /


        * This method is used to set ear position. Because usually we want to

        * set ears in the exact place of the camera and with the same

        * orientation, the easiest way to obtain this is to use the setCamera

        * method.

        * @param camera: the camera we are actually using.

        */

        public void setCamera(Camera camera) {

            this.camera=camera;

            this.lastCameraLocation=this.camera.getLocation();

            this.lastCameraDirection=this.camera.getDirection();

            this.lastCameraUp=this.camera.getUp();

            adjustCameraData();       

        }

     

        /

        * Users should never call this method manually! This method is

        * responsible for setting data regarding ears position and

        * orientation (equivalent to the cam).

        */

        private void adjustCameraData() {

            // TODO Auto-generated method stub       

            listenerPos.put(0,camera.getLocation().x);

            listenerPos.put(1,camera.getLocation().y);

            listenerPos.put(2,camera.getLocation().z);

           

            listenerOri.put(0,camera.getDirection().x);

            listenerOri.put(1,camera.getDirection().y);

            listenerOri.put(2,camera.getDirection().z);

            listenerOri.put(3,camera.getUp().x);

            listenerOri.put(4,camera.getUp().y);

            listenerOri.put(5,camera.getUp().z);

            AL10.alListener(AL10.AL_POSITION, listenerPos);

            AL10.alListener(AL10.AL_ORIENTATION, listenerOri);

        }



        /


        * Users should never call this method manually! This method is

        * responsible for basic inizialization.

        */

        private void initPos() {

            // TODO Auto-generated method stub

            AL10.alListener(AL10.AL_POSITION, listenerPos);

            AL10.alListener(AL10.AL_VELOCITY, listenerVel);

            AL10.alListener(AL10.AL_ORIENTATION, listenerOri);

        }



        /

        * Users should never call this method manually! This method is

        * responsible for basic inizialization.

        */

        private void initFlip() {

            // TODO Auto-generated method stub

            sourcePos.flip();

            sourceVel.flip();

            listenerPos.flip();

            listenerVel.flip();

            listenerOri.flip();

        }



        /


        * This method returns the last location of the node.

        * @return the lastPosition.

        */

        public Vector3f getLastPosition() {

            return lastPosition;

        }

       

        /

        * This method is used to load the sample. Call this passing it a valid

        * address String.

        * @param sampleAddress The sample address to load.

        */

        public int setSampleAddress(String sampleAddress) {

            this.sampleAddress=sampleAddress;



            // Load wav data into a buffer.

            AL10.alGenBuffers(buffer);



            if (AL10.alGetError() != AL10.AL_NO_ERROR)

                return AL10.AL_FALSE;



            WaveData waveFile=WaveData.create(sampleAddress);

           

            AL10.alBufferData(buffer.get(0), waveFile.format, waveFile.data, waveFile.samplerate);

            waveFile.dispose();



            // Bind the buffer with the source.

            AL10.alGenSources(source);



            if (AL10.alGetError() != AL10.AL_NO_ERROR)

                return AL10.AL_FALSE;



            AL10.alSourcei(source.get(0), AL10.AL_BUFFER, buffer.get(0));

            AL10.alSourcef(source.get(0), AL10.AL_PITCH, 1.0f);

            AL10.alSourcef(source.get(0), AL10.AL_GAIN, 1.0f);

            AL10.alSource(source.get(0), AL10.AL_POSITION, sourcePos);

            AL10.alSource(source.get(0), AL10.AL_VELOCITY, sourceVel);       

           

            if (this.loop) {

                AL10.alSourcei(source.get(0), AL10.AL_LOOPING,  AL10.AL_TRUE  );

            } else {

                AL10.alSourcei(source.get(0), AL10.AL_LOOPING,  AL10.AL_FALSE );

            }



            // Do another error check and return.

            if (AL10.alGetError() == AL10.AL_NO_ERROR)

                return AL10.AL_TRUE;



            return AL10.AL_FALSE;

        }

       

       

        /


        * This method is used to make the sample playing in loop

        * @param lo: set to true if you want loop or false if not.

        */

        public void setLoop(boolean lo) {

            this.loop=lo;

            if (this.loop) {

                AL10.alSourcei(source.get(0), AL10.AL_LOOPING,  AL10.AL_TRUE  );

            } else {

                AL10.alSourcei(source.get(0), AL10.AL_LOOPING,  AL10.AL_FALSE );

            }

        }

       

        /

        * Users should never call this method manually! This method is

        * responsible for basic inizialization. 

        */

        private void initAL() {

            // TODO Auto-generated method stub

            if (!AL.isCreated()) {

                try {

                    AL.create(null, 15, 22050, true);

                } catch (LWJGLException le) {

                    le.printStackTrace();

                    return;

                }

                AL10.alGetError();

            }

        }



       

        /


        * This method draw the node if it is a geometric. In this special

        * Node implementation, method drwaw simply adjusts the sound source

        * location and/or the ear location and orientation, but only if one of

        * this has changed since last draw. This method is called through the

        * various update chain.

        *

        * @see com.jme.scene.Spatial#draw(com.jme.renderer.Renderer)

        */

        public void draw(Renderer r) {

            //System.out.println("Check");

            Vector3f actualPosition=this.getWorldTranslation().add(this.getLocalTranslation());

            //System.out.println(actualPosition+":"+this.lastPosition);

            if (!actualPosition.equals(this.lastPosition)) {

                //System.out.println("DifferentPosition");

                this.lastPosition=new Vector3f(actualPosition);

                this.updateLocation();

            }

            Vector3f camActualLocation=this.camera.getLocation();

            Vector3f camActualDirection=this.camera.getDirection();

            Vector3f camActualUp=this.camera.getUp();

           

            if ((!(camActualDirection.equals(this.lastCameraDirection)))||(!(camActualLocation.equals(this.lastCameraLocation)))||(!(camActualUp.equals(this.lastCameraUp)))) {

                //System.out.println("DifferentEars");

                this.lastCameraLocation=new Vector3f(this.camera.getLocation());

                this.lastCameraDirection=new Vector3f(this.camera.getDirection());

                this.lastCameraUp=new Vector3f(this.camera.getUp());

                adjustCameraData();

            }

           

        }



        /

        * Users should never call this method manually! This method is

        * responsible for updating the source location. 

        */

        private void updateLocation() {

            // TODO Auto-generated method stub

            sourcePos.put(0,this.lastPosition.x);

            sourcePos.put(1,this.lastPosition.y);

            sourcePos.put(2,this.lastPosition.z);

            AL10.alSource(source.get(0), AL10.AL_POSITION, sourcePos);

        }

       

        /


        * This method plays the sample.

        */

        public void play() {

            AL10.alSourcePlay(source.get(0));

        }

       

        /

        * This method stops the sample.

        */

        public void stop() {

            AL10.alSourceStop(source.get(0));

        }

       

        /


        * This method pauses the sample. Another call to play() makes the sample

        * playing from where it was paused.

        */

        public void pause() {

            AL10.alSourcePause(source.get(0));

        }

       

        /

        * This method check if the sample is playing.

        * @return true if the sample is actually playing

        */

        public boolean isPlaying() {

            int result=AL10.alGetSourcei(source.get(0),AL10.AL_SOURCE_STATE);

            if (result==AL10.AL_PLAYING) {

                return true;

            }

            return false;

        }

       

        /


        * This method check if the sample is stopped.

        * @return true if the sample is actually stopped

        */

        public boolean isStopped() {

            int result=AL10.alGetSourcei(source.get(0),AL10.AL_SOURCE_STATE);

            if (result==AL10.AL_STOPPED) {

                return true;

            }

            return false;

        }

       

        /

        * This method check if the sample is paused.

        * @return true if the sample is actually paused

        */

        public boolean isPaused() {

            int result=AL10.alGetSourcei(source.get(0),AL10.AL_SOURCE_STATE);       

            if (result==AL10.AL_PAUSED) {

                return true;

            }

            return false;

        }

       

        /


        * This method check the state. There are three states allowed: playing,

        * stopped and paused, which refers to the state of the sample.

        * There is another state which is undefined. This is used when the

        * state of the sample is not in one of the other states.

        * @return The state by the use of the constants defined in this class.

        */

        public int checkState() {

            int result=AL10.alGetSourcei(source.get(0),AL10.AL_SOURCE_STATE);

            if (result==AL10.AL_PAUSED) {

                return OpenALSoundNode.PAUSED;

            }

            if (result==AL10.AL_PLAYING) {

                return OpenALSoundNode.PLAYING;

            }

            if (result==AL10.AL_STOPPED) {

                return OpenALSoundNode.STOPPED;

            }

            return OpenALSoundNode.UNDEFINED;

        }



    }


I like the concept by far better than the current sound system. I played around a bit with it, made a test, extracted a superclass to abstract from openAL and extract a listener class to have only one object updating the cam location. If you care to have a look: zip.



If important features from the old system go in there I can even imagine to replace the old one with it. Would you like to work further on it?

Hi irrisor, i'm happy you found it interesting! And i think your implementation is surely elegant and correct!

Yes, i think i can work on it in my spare time!

I'll try to add features that are already present in the sound api used by jme (but i can't really understand how they work, because i can't find any tutorial and the mechanism seems to me to be quite complex!) working on the code you put in the zip.

But, this is really important, on April 7, i'll take a master degree in Informatic Engineering (i think this is the translation of the 5 years long laurea degree course of Ingegneria Informatica)! So in these days i'm busy working on my thesis presentation! And in the  following weeks maybe i'll be too drunk to code something and the remaining time i'll stay with my girlfriend!!! In other words, i'll work on it only in the spare time!

Bye and hope to be ready soon for the api!

Cool - good fortune then :smiley:

I like the look of this a lot. An amazing job! :smiley:

So here come the feature requests, haha…



If you added these 3 methods, I see no reason why it couldn't begin to replace the existing SoundSystem…


    public void setPitch(float pitch) {
       AL10.alSourcef( source, AL10.AL_PITCH, pitch );
    }
   
    public void setVolume(float volume) {
       AL10.alSourcef( source, AL10.AL_GAIN, volume );
    }
   
    public void setRollOff(float rollOff) {
       AL10.alSourcef( source, AL10.AL_ROLLOFF_FACTOR, rollOff );
    }



3 little concerns:
1) I haven't taken a good look so I may be mistaken, but it might be inefficient if the programmer wanted multiple nodes to play the same .wav file. Say, for instance, you had 20 cars that all make the same 3 sounds. It looks like it will load each .wav file 20 times - which makes for 60 loads. Is there a way to buffer it once when the resource is first requested, and then just play "instances" of it at each node later?

2) As in the existing SoundSystem, the listener must be a camera :( This seems like a needless restriction. For third-person games, it might be nice to have the "ears" where the player model is, not where the camera is. I'd keep ListenerNode.setCamera() as a convenience method, but add something like, ListenerNode.setListenerNode(Node) too. Or maybe one will work for them both... Isn't a Camera just a Node?

3) It might be nice if the "RollOff" and "ListenerVolume" (a sort-of 'master volume knob') could be set globally somewhere (instead of unique to each SoundNode). The global setting could act as a default, overridden by individual SoundNodes as necessary.

Keep up the good work!
Spacecookies
  1. yes, that's definately necessary
  2. The stuff in the zip I posted already accounts for this (ListenerNode)
  3. yes, that'd be nice

This should be on the CVS



any updates on this class?

Does the JME team accept outside contributions? If so, is any consideration being given to incorporating these classes? Doesn't take long to run up against the limits of the current system, as I learned just this morning. :slight_smile:



What issues block these classes' inclusion? I'm pondering going through these classes and making various tweaks/inclusions to make the system more fleshed-out, but it would be good to know that issues X, Y and Q are blocking its inclusion so I can focus on those. I know, for instance, that as it stands the system appears to load WAV samples multiple times, and I plan on fixing that. Also, I don't know if there's an easy way to integrate streaming music playback with this system. Perhaps the SoundSystem class can be used only for loading/playing streams, but this looks a bit ugly to me and is probably on the list of things to fix.



If I don't hear anything then I'll roll the changes into my own engine layer, but it would be nice to share them easily with the community, and seems like something that would benefit more than just me, as is evident by this thread.

Contributions are always appreciated and usually readily accepted and integrated.  I fear that the inclusion of this sound stuff is my fault as I was tasked with looking into updating the sound system for jME and just haven't had the time to do so yet.  In most cases if you make changes and just post the diff for the files they can be applied easily by any of the developers.



I will hopefully have time in the next few weeks to take a look at this stuff if someone else doesn't get to it first.  I want to spend the time to make sure the sound system has everything people would want before I'm done with it though.



darkfrog

Oh, cool. Then what I'll do is leave everything in the com.jmex.sound package and make my modifications work as seamlessly with the original structure as possible.



I've already run up against some issues with the existing class (I don't know that it loads files from URLs, for example) and will probably try to add that functionality. I'll also be looking at the javadoc more closely, and will probably rename a few methods. If you like, feel free to ping me when you're ready to take a look at these classes. I'll probably have changed them somewhat substantially (and hopefully for the better :slight_smile: by then, as my project is fairly audio-intensive and really needs solid, robust and highly-detailed audio capability.

What happened with this contribution?

Come on, darkfrog. Take it, put it in CVS and then we can add things if there is something missing. It doesn't get better keeping it here untouched.

Renanse was kind enough to take this task on instead…so harass him about it, I know I am. :wink:

I'm currently working on sound all day, every day.  We'll see how it turns out.  Can't really give more info than that yet.

That sounds good - good sound system approaching :slight_smile:



Yesterday I added the code in the zip file posted here to my jME copy locally and tested it. First I could not hear any sound effects until I decreased the rolloff so I added setters for the pitch, gain and rolloff parameters.

On Windows I had a VM crash when playing a sound and moving the camera at the same time.

A function to play a sound effect at a specified location without having to supply a scene node would be nice. I worked around that with a temporary node.



Could you give us a rough estimation when there will be something available? Do we speak about days, weeks, months or years? :slight_smile: And will you be allowed to give it back to jME at all?

I just would like to know whether it's worth while to work on a temporary solution for my game.

I see. So I won't put too much effort but stick with the SoundNode posted here. WoM client has still a long way to go and we will see how things develop.

I just came across the sound thing as I added some simple weather effects (just rain and lightning).

Hi,



Thanks for the sample code.

Any news from the sound front? I tried working with the current SoundSystem and it was, err… kind of…working, but not really fun. Something like SoundNode would be very helpful in jME right now, even if it's later replaced by the ultra full blown ultimate ear crunching final static public sound system version. Just a thought…



As long as there are no news, I use my SilenceSystem instead:



public class SilenceSystem {
   public void listen() {
      //oh, this silence...
   }
}



Of course, you can put it in com.jmex.sound, if you want. Works with both openAL and fmod :P

It's been a while since I've heard anything from Renanse on his reworking of the sound API…



@Renanse, we're patiently waiting…well, they are…I keep getting more annoying every day until I get what I want!  }:-@

This thing cannot stream from URL can it? I just changed the WaveData.create( samplepath); to

WaveData.create( IO.getSound(samplepath) ); and this works with small sounds, but with larger music

it gets a java.lang.OutOfMemoryError: Java heap space exception

Btw this only reads wav file right? No ogg or mod?

This doesn't play mp3 either does it… An uncompressed music wav file is 50 mb. :stuck_out_tongue: