LWJGL 2 - Lightweight Java Game Library
Other Articles in the Series
· Lesson 1: Single Static Source
· Lesson 2: Looping and Fade-away
· Lesson 3: Multiple Sources
· Lesson 4: A Closer Look at ALC
· Lesson 5: Sources Sharing Buffers
· Lesson 6: Advanced Loading and Error Handles
· Lesson 7: The Doppler Effect

Sources Sharing Buffers: Lesson 5

Author: Jesse Maurais | From: devmaster.net
Modified for LWJGL by: Brian Matzon

At this point in the OpenAL series I will show one method of having your buffers be shared among many sources. This is a very logical and natural step, and it is so easy that some of you may have already done this yourself. If you have you may just skip this tutorial in total and move on. But for those keeners who want to read all of the info I've got to give, you may find this interesting.
Well, here we go. I've decided to only go over bits of the code that are significant, since most of the code has been repeated so far in the series. Check out the full source code in the download.

import java.io.IOException;
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;

public class Lesson5 {

  /** Index of thunder sound */
  public static final int THUNDER = 0;

  /** Index of waterdrop sound */
  public static final int WATERDROP = 1;

  /** Index of stream sound */
  public static final int STREAM = 2;

  /** Index of rain sound */
  public static final int RAIN = 2;

  /** Index of chimes sound */
  public static final int CHIMES = 2;

  /** Index of ocean sound */
  public static final int OCEAN = 2;

  /** Maximum data buffers we will need. */
  public static final int NUM_BUFFERS = 6;

    /** Buffers hold sound data. */
  IntBuffer buffer = BufferUtils.createIntBuffer(NUM_BUFFERS);

    /** Sources are points emitting sound. */
  IntBuffer source = BufferUtils.createIntBuffer(128);

We will be using several wav files so we need quite a few buffers here. We will be using at most 128 sources (typically OpenAL will stop at 32-64 sources, but you shouldn't rely on more than 16!). We can just keep adding sources to the scene until OpenAL runs out of them. This is also the first tutorial where we will deal with sources as being a resource that will run out. And yes, they will run out; they are finite.

  /**
   * boolean LoadALData()
   *
   *  This function will load our sample data from the disk using the Alut
   *  utility and send the data into OpenAL as a buffer. A source is then
   *  also created to play that buffer.
   */
  int loadALData() {

    // Load wav data into a buffer.
    AL10.alGenBuffers(buffer);

    if(AL10.alGetError() != AL10.AL_NO_ERROR)
      return AL10.AL_FALSE;

    WaveData waveFile = WaveData.create("thunder.wav");
    AL10.alBufferData(buffer.get(THUNDER), waveFile.format, waveFile.data, waveFile.samplerate);
    waveFile.dispose();

    waveFile = WaveData.create("waterdrop.wav");
    AL10.alBufferData(buffer.get(WATERDROP), waveFile.format, waveFile.data, waveFile.samplerate);
    waveFile.dispose();

    waveFile = WaveData.create("stream.wav");
    AL10.alBufferData(buffer.get(STREAM), waveFile.format, waveFile.data, waveFile.samplerate);
    waveFile.dispose();

    waveFile = WaveData.create("rain.wav");
    AL10.alBufferData(buffer.get(RAIN), waveFile.format, waveFile.data, waveFile.samplerate);
    waveFile.dispose();

    waveFile = WaveData.create("ocean.wav");
    AL10.alBufferData(buffer.get(OCEAN), waveFile.format, waveFile.data, waveFile.samplerate);
    waveFile.dispose();

    waveFile = WaveData.create("chimes.wav");
    AL10.alBufferData(buffer.get(CHIMES), waveFile.format, waveFile.data, waveFile.samplerate);
    waveFile.dispose();

    // Do another error check and return.
    if (AL10.alGetError() == AL10.AL_NO_ERROR)
      return AL10.AL_TRUE;

    return AL10.AL_FALSE;

We've totally removed the source generation from this function. That's because from now on we will be initializing the sources separately.

  /**
   * void AddSource(ALint type)
   *
   *  Will add a new water drop source to the audio scene.
   */
  private void addSource(int type) {
    int position = source.position();
    source.limit(position + 1);
    AL10.alGenSources(source);

    if (AL10.alGetError() != AL10.AL_NO_ERROR) {
      System.out.println("Error generating audio source.");
      System.exit(-1);
    }

    AL10.alSourcei(source.get(position), AL10.AL_BUFFER,   buffer.get(type) );
    AL10.alSourcef(source.get(position), AL10.AL_PITCH,    1.0f             );
    AL10.alSourcef(source.get(position), AL10.AL_GAIN,     1.0f             );
    AL10.alSource (source.get(position), AL10.AL_POSITION, sourcePos        );
    AL10.alSource (source.get(position), AL10.AL_VELOCITY, sourceVel        );
    AL10.alSourcei(source.get(position), AL10.AL_LOOPING,  AL10.AL_TRUE     );

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

    // next index
    source.position(position+1);
  }

Here's the function that will generate the sources for us. This function will generate a single source for any one of the loaded buffers we generated in the previous source. Given the buffer index 'type'. We do an error check to make sure we have a source to play (like I said, they are finite). If a source cannot be allocated then the program will exit.

  /**
   * void killALData()
   *
   *  We have allocated memory for our buffers and sources which needs
   *  to be returned to the system. This function frees that memory.
   */
  void killALData() {
    // set to 0, num_sources
    int position = source.position();
    source.position(0).limit(position);
    AL10.alDeleteSources(source);
    AL10.alDeleteBuffers(buffer);
  }

This function has been modified a bit to accommodate the number of actually created sources.

    // Loop.
    char c = ' ';
    while(c != 'q') {
      try {
      	c = (char) System.in.read();
      } catch (IOException ioe) {
      	c = 'q';
      }

      switch(c) {
        case 'w': addSource(WATERDROP);  break;
        case 't': addSource(THUNDER);    break;
        case 's': addSource(STREAM);     break;
        case 'r': addSource(RAIN);       break;
        case 'o': addSource(OCEAN);      break;
        case 'c': addSource(CHIMES);     break;
      };
    }
    killALData();
  }
}

Here is the programs inner loop taken straight out of our main. Basically it waits for some keyboard input and on certain key hits it will create a new source of a certain type and add it to the audio scene. Essentially what we have created here is something like one of those nature tapes that people listen to for relaxation. Ours is a little better since it allows the user to customize which sounds that they want in the background. Pretty neat eh? I've been listening to mine while I code. It's a Zen experience (I'm listening to it right now).

The program can be expanded for using more wav files, and have the added feature of placing the sources around the scene in arbitrary positions. You could even allow for sources to play with a given frequency rather than have them loop. However this would require GUI routines that go beyond the scope of the tutorial. A full featured "Weathering Engine" would be a nifty program to make though. ;)

Download source code and resources for this lesson here.

 
content © by lwjgl.org - design © by Daniel Leinich