JME 3 Tutorial (11) - Hello Audio

Previous: Hello Terrain, Next: Hello Effects
This tutorial explains how to add 3D sound to a game, and how make sounds play together with other game events. We learn how to use the Audio Renderer, an Audio Listener, and Audio Nodes. We also make use of an Action Listener and a MouseButtonTrigger from the previous Hello Input tutorial to make a mouse click trigger a gun shot sound.

Sample Code

package jme3test.helloworld;
import com.jme3.app.SimpleApplication;
import com.jme3.audio.AudioNode;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
/** Sample 11 - playing 3D audio. */
public class HelloAudio extends SimpleApplication {
  private AudioNode audio_gun;
  private AudioNode audio_nature;
  private Geometry player;
  public static void main(String[] args) {
    HelloAudio app = new HelloAudio();
    app.start();
  }
  @Override
  public void simpleInitApp() {
    flyCam.setMoveSpeed(40);
    /** just a blue box floating in space */
    Box(Vector3f.ZERO, 1, 1, 1);
    player = new Geometry("Player", box1);
    Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    mat1.setColor("Color", ColorRGBA.Blue);
    player.setMaterial(mat1);
    rootNode.attachChild(player);
    /** custom init methods, see below */
    initKeys();
    initAudio();
  }
  /** We create two audio nodes. */
  private void initAudio() {
    /* gun shot sound is to be triggered by a mouse click. */
    audio_gun = new AudioNode(audioRenderer, assetManager, "Sound/Effects/Gun.wav");
    audio_gun.setLooping(false);
    audio_gun.setVolume(2);
    /* nature sound - keeps playing in a loop. */
    audio_nature = new AudioNode(audioRenderer, assetManager, "Sound/Environment/Nature.ogg");
    audio_nature.setLooping(true);
    audio_nature.setPositional(true);
    audio_nature.setLocalTranslation(Vector3f.ZERO.clone());
    audio_nature.setVolume(3);
    audioRenderer.playSource(audio_nature); // play continuously!
  }
  /** Declaring the "Shoot" action, and
   *  mapping it to a trigger (mouse click). */
  private void initKeys() {
    inputManager.addMapping("Shoot", new MouseButtonTrigger(0));
    inputManager.addListener(actionListener, "Shoot");
  }
  /** Defining the "Shoot" action: Play a gun sound. */
  private ActionListener() {
    @Override
    public void onAction(String name, boolean keyPressed, float tpf) {
      if (name.equals("Shoot") && !keyPressed) {
        audioRenderer.playSource(audio_gun); // play once!
      }
    }
  };
  /** Move the listener with the a camera - for 3D audio. */
  @Override
  public void simpleUpdate(float tpf) {
    listener.setLocation(cam.getLocation());
    listener.setRotation(cam.getRotation());
  }
}

When you run the sample, you should see a blue cube, and hear nature ambient sounds. When you click you hear a loud shot.

Understanding the Code Sample

In the game's initSampleApp() method, we create a simple blue cube geometry called player and attach it to the scene – it's just sample content so you see something when running the audio sample. From initSampleApp(), we initialize the game by calling the two custom methods initKeys() and initAudio(). You could call the lines of code in these two methods directly from initSampleApp(); we simply moved them into extra methods to keep the code more readable. Let's look at initKeys(): As we learned in previous tutorials, we use jme's inputManager to respond to user input. We add a mapping for a left mouse button click, and we name this new action Shoot.

/** Declaring the "Shoot" action and mapping to a trigger. */
  private void initKeys() {
    // click to shoot
    inputManager.addMapping("Shoot", new MouseButtonTrigger(0));
    inputManager.addListener(actionListener, "Shoot");
  }

Setting up the ActionListener should also be familiar from previous tutorials. We declare that, when the trigger (the mouse button) is pressed and released, we want to play a gun sound.

  /** Defining the "Shoot" action: play a sound. */
  private ActionListener() {
    @Override
    public void onAction(String name, boolean keyPressed, float tpf) {
      if (name.equals("Shoot") && !keyPressed) {
        audioRenderer.playSource(audio_gun);
      }
    }
  };

Next we have a closer look at initAudio() to learn how to use the AudioRenderer and AudioNodes.

AudioNodes

Using audio is quite simple. Save your audio files into your assets/Sound directory. jME3 supports both Ogg Vorbis (.ogg) and Wave (.wav) files. For each sound you create an audio node. A sound node can be used like any node in the jME scene graph. We create one node for a gunshot sound, and one for a nature sound.

  private AudioNode audio_gun;
  private AudioNode audio_nature;

Look at our custom initAudio() method: Here we initialize the sound objects and set their parameters.

    audio_gun    = new AudioNode( audioRenderer, assetManager, "Sound/Effects/Gun.wav");
    ...
    audio_nature = new AudioNode( audioRenderer, assetManager, "Sound/Environment/Nature.ogg");

These two lines create new sound nodes from the given audio files in the assetManager. In the next lines we specify that we want the gunshot sound to play only once – we don't want it to loop. We specify its volume as gain factor (at 0, sound is muted, at 2, it is twice as loud, etc.).

    audio_gun.setLooping(false);
    audio_gun.setVolume(2);

The nature sound is different: We want it to loop continuously as background sound. This is why we set looping to true, and we already call the play() method on the node. We also choose to set its volume to 3.

    audio_nature.setLooping(true);
    audio_nature.setVolume(3);
...
    audioRenderer.playSource(audio_nature); // play continuously!
  }

We can choose to make the audio_nature a positional sound that comes from a certain place. We have to give the node an explicit translation, in this example we choose Vector3f.ZERO (which stands for the coordinates 0.0f,0.0f,0.0f, the center of the scene.) Since jME supports 3D audio, you will be able to hear this sound coming from this particular location. Making the sound positional is optional.

    audio_nature.setPositional(true);
    audio_nature.setLocalTranslation(Vector3f.ZERO.clone());

Triggering Audio

The two sounds are used differently:

Now every sound knows whether it loops or not. The actual play command is the same for both files:

    audioRenderer.playSource(audio_nature);
...
    audioRenderer.playSource(audio_gun);

Appart from the looping Boolean, the only difference is where play() is called:

 /** Defining the "Shoot" action: Play a gun sound. */
  private ActionListener() {
    @Override
    public void onAction(String name, boolean keyPressed, float tpf) {
      if (name.equals("Shoot") && !keyPressed) {
        audioRenderer.playSource(audio_gun); // play once!
      }
    }
  };

Your Ear in the Scene - 3D Audio Listener

To keep up the 3D audio effect, jME needs to know the position of the sound source, and the position of the ears of the player. The ear is represented by an 3D Audio Listener object. The listener is a built-in object in a SimpleApplication (it is not related to any other Java Listeners, such as the input manager's ActionListener). In order to make the most of the 3D audio effect, we use the simpleUpdate() method to move and rotate the listener (the player's ears) together with the camera (the player's eyes).

public void simpleUpdate(float tpf) {
  listener.setLocation(cam.getLocation());
  listener.setRotation(cam.getRotation());
}

Global, Directional, Positional?

In this example, we defined the nature sound as coming from a certain position, but not the gunshot sound. This means our gunshot is global and can be heard everywhere with the same volume. JME3 also supports directional sounds, that can only be heard from a certain direction (More about Audio here). How do you make this decision?

In short, you must choose in every situation whether you want a sound to be global, directional, or positional.

Conclusion

You now know how to add the two most common types of sound to your game: Global sounds and positional sounds. You can play sounds in two ways: Either continuously in a loop, or situationally just once. You also learned to use sound files that are in either .ogg or .wav format. Tip: jME's Audio implementation also supports more advanced effects such as reverberation and the Doppler effect. You can also create directional sounds, and stream long sound files instead of buffering first. Find out more about these features from the sample code included in the jme3test directory and from the advanced Audio docs. Want some fire and explosions to go with your sounds? Read on to learn more about effects.

sound, documentation, beginner,, beginner, intro

view online version