Getting Started

To begin programming with JOAL, the application must first gain a context to a sound device. When a JOAL application begins, it finds a sound device residing on the system through the process of creating or acquiring a context to that device. JOAL supports the ability to create multiple contexts for systems that have more than one sound card, but only one context can be active at any given time. Later, the process for creating and setting up a nondefault audio device context will be discussed more thoroughly.

In general, most users maintain only a single audio card in their computers. To simplify the initialization process for systems needing to work only with the user’s default audio card, the creators of OpenAL created a utility called the Alut. The Alut class allows developers to simplify the initialization and file loading. It creates a single OpenAL context and uses the default settings for the primary device.

The Alut utility is summarily implemented in JOAL. Most applications can initialize access to the default sound card with just a single line of code such as the following:

Alut.alutInit();

The next major utility provided through the Alut class is the Alut.alutLoadWavFile() method. This method allows the user to specify a short set of parameters and automatically loads the audio data and allows it to be set into a Buffer.

The method’s signature follows:

alutLoadWAVFile(String fileName, int format[], ByteBuffer data[],  int size[], int freq[], int loop[])

A quick explanation of the parameters follows:

String fileName: A string that represents the file to be loaded and may include relative paths.

int format: The OpenAL format specifier that tells what the resolution is and whether the sound is in mono or stereo. Available formats are 8- and 16-bit resolution, stereo or mono.

ByteBuffer data: A storage place for the actual sound data.

int size: The size of the buffer in bytes.

int freq: Holds the actual frequency of the audio data.

int loop: Indicates whether the sample will loop.

When ready to load a sample, declare the needed variables as follows:

int[] format = new int[1]; int[] size   = new int[1]; int[] freq   = new int[1]; int[] loop   = new int[1]; ByteBuffer[] audioData = new ByteBuffer[1];

Then make the actual call to the WAV loader as follows:

ALut.alutLoadWAVFile(“bleep.wav”, format, data, size, freq, loop);

The application should keep this file in memory only for a long enough period to copy the information into a Buffer. It should then be let go as follows:

ALut.alutLoadWAVFile(format[0], data[0], size[0], freq[0]);

This process is simple compared to the early versions of DirectSound that required the user to eat the data a chunk at a time to fill out WAVEFORMATEX structs that were used to fill the DSound buffers on the DirectX platform. If you have ever had to write a loader that ate memory a bite at a time (no pun intended, though arguably they all do this), you will recognize the almost overwhelming simplicity provided in this method.

When the application is completed, OpenAL must be uninitialized. This process is just as easy as the process of setting it up. Make the following call, and OpenAL is shut down:

Alut.alutExit();

The only things left to do here are to set up the basic Buffer, Source, and Listener classes, bind the audio to Buffers, and then push the play button.

In the JOAL implementation, the AL interface contains all the methods for controlling Listeners, Buffers, and Sources. When setting the properties for the three core classes, use this interface’s methods to get everything going. To give an example, let’s set up the Listener for an application.

The first thing to do is specify how the application will hold the information relating to the Listener. They key things that must be defined are the Listener class’s position, velocity, and orientation.

A Listener class’s position is represented as a single three-dimensional point in space. To keep things simple, let’s start the Listener class’s position at the origin. Use float arrays to keep the coordinates straight.

float[] listenerPosition = {0.0f,0.0f,0.0f};

Setting a Listener class’s velocity is also fairly straightforward. The main reason this value is needed references how OpenAL implements the Doppler effect. A Doppler shift occurs when apparent movement occurs between one or more sources that receive or transmit sounds. Imagine driving quickly past a honking horn. The sound of the horn emits in a forward direction. How you hear the sound changes, based on your changing position. Consider the example shown in Figure 4.1.

image from book
Figure 4.1: The sound of the horn is Doppler shifting.

The sound of the object at point A varies in contrast to that at point B. OpenAL calculates the Doppler shift using the following formula, referenced from the OpenAL Programmer’s Guide:

sif' = f * (DV – DF * vl) / (DV + DF * vs) where DV = AL_DOPPLER_VELOCITY DF = AL_DOPPLER_FACTOR vl = listener velocity (scalar value along source-to-listener vector) vs = source velocity (scalar value along source-to-listener vector) f = frequency of sample f' = Doppler shifted frequency

JOAL provides a couple of functions to help process the Doppler shift that will be discussed later in the chapter.

Setting the Listener class’s velocity is just as straightforward as setting the position. Let’s set it up.

float[] listenerVelocity = {0.0f,0.0f,0.0f};

A Listener class’s orientation describes the current facing in three dimensions. Setting up the orientation requires six parameters to describe the precise facing of the Listener. The main reason all this description is needed relates to how sounds are spatially organized. Accurate data is needed to make correct audio renderings relative to the Listener. The Listener class’s orientation is set as follows:

float[] listenerOrientation = {0.0f,0.0f,-1.0f,0.0f,0.0f,0.0f};

The first three parameters specify the current x-, y-, and z-vectors, which represent what the Listener is currently looking at. The second three parameters specify the x-, y-, and z-vectors that lead straight out of the top of the Listener. These parameters are sometimes referred to as the “at” and “up” vectors, or the “forward” and “world-up” vectors, respectively. When a game is running, the Listener class’s orientation and position are frequently updated through the game loop. Figure 4.2 shows a graphical representation of the “at” and “up” vectors.

image from book
Figure 4.2: The Listener class’s “up” and “at” vectors.

After the initial setup process, accessing and changing the Listener class’s properties must come through one of the alListener[3f,fv,i] methods. The values in the brackets represent the specified types of properties that are being set for the Listener. Each one of them is implemented as a separate method that takes data of the listed type. Some of the control methods accept three floating-point values and are appended with a “3f” at the end of the method’s name. Others accept floating-point vectors as valid parameters. These methods are appended with an “fv” to indicate that they can accept data in this format. The final and simplest data type is the straightforward integer. Methods that can accept an integer value are appended with an “i”. This convention is consistent when working with OpenAL and JOAL.

As the player moves through the world, the sounds are going to be modified based on the current position that he occupies. These methods will be used many times to update the player’s position based upon direct input from a device or as a result of some physical effect, such as a spring pad or an explosion that hurls the player back, that might cause the player’s position to change. Table 4.1 shows the complete list of properties that are associated with the Listener class as well as the data types that can be applied to them for adjustment.

A more detailed discussion of how these properties can be used in a game as well as the process for adjusting them occurs later in the chapter.

Table 4.1 :  LISTENER PROPERTIES

Property

Data Type

Description

AL_GAIN

f

Gain control; should be set to a positive value

AL_POSITION

3f, fv

x- y-, and z-position

AL_VELOCITY

3f, fv

velocity vector

AL_ORIENTATION

fv

orientation expressed as “at” and “up” vectors

Now let’s set up the required Buffer classes. JOAL requires an application to specify the number of Buffer classes it plans to use, then to generate those Buffer classes before moving on. Let’s get started with these points before moving on.

int[] buffer = new int[1];

This line identifies where the Buffer will be held and referenced later.

al.alGenBuffers(1,buffer);

This line creates a single Buffer to hold audio data referencing it from the first Buffer declared previously.

Now we must copy information from an audio file and move it into the Buffer. Remember the alutLoadWaveFile() method? Now it’s time to link the loading of the file and copy the data obtained into the Buffer. This step is done with the method alBufferData() and references some file format variables that were identified and described earlier. Information is copied into the Buffer in the following way:

ALut.alutLoadWAVFile(“bleep.wav”, format, data, size, freq,loop); Al.alBufferData(buffer[0],format[0],data[0],size[0],freq[0]);

Once the data is copied over, the file is closed out.

ALut.alutLoadWAVFile(format[0], data[0], size[0], freq[0]);

At the end of the application, all the allocated Buffer classes must be deleted using the alDeleteBuffers() method as follows:

Al.alDeleteBuffers(1,buffer); 

So far so good. Now the only thing remaining is to set up a Source. Remember that a Source object is bound with a Buffer and contains much of the processing and positioning capabilities of the system. Note that the Buffer classes do not contain or hold any location or velocity data. This data is all controlled by the Source that is bound to it. The first steps are to create a holding place for the Source and get its position and velocity set up.

int[] source = new int[1]; float[] sourcePosition = {0.0f,0.0f,0.0f}; float[] sourceVelocity = {0.0f,0.0f,0.0f};

The next step is to bind the Source with a Buffer. The first step in this process is to generate a Source in a manner similar to generating a Buffer.

al.a.GenSources(1,source);

Now the individual properties of the Source must be set up through the use of a set of functions based on the type of property that needs to be set. All of the properties for Source classes are controlled through the alSource[3f,fv,i] methods. These methods accept the same types of parameters but are capable of modifying a different set of values as they relate to the Source. Some of the properties are only used to identify.

Table 4.2 shows the different integer-based Source properties that can be set through these methods.

Table 4.2 :  SOURCE INTEGER PROPERTIES

Property

Data Type

Description

AL_SOURCE_RELATIVE

i

Source position is determined by listener’s position

AL_LOOPING

i

Looping for a Source (boolean)

AL_BUFFER

i

Indicates Buffer to provide samples

AL_SOURCE_STATE

i

Used to control and identify current state of a Source

AL_BUFFERS_QUEUED

i

Number of Buffer classes in line on the Source (read only)

AL_BUFFERS_PROCESSED

i

Number of buffer classes process by Source (read only)

Although the use of most of these properties will be discussed later, it’s a good time to discuss what each of them does to a Source.

AL_SOURCE_RELATIVE: Source relative coordinates refer to the distance or orientation of a Source relative to the Listener. If a particular Source is set to AL_SOURCE_RELATIVE and given coordinates of (0,0,0), it will be set to the position of the Listener at all times. It is common to use a Source in this way to play background audio or other constant sounds for the player to hear. A 2D game that wanted to “lock” the positioning of a Source so that it didn’t move could avoid updates to the positioning but could also use the AL_SOURCE_RELATIVE property to ensure that the sound would always be oriented in the current location of the Listener. This setting would be useful for almost all sounds in a 2D game.

AL_LOOPING: This is a state set to AL_TRUE or AL_FALSE. To indicate that a given Source is going to loop, a call must be made to set the property. From that point on, the sample will loop whenever it is played and can be stopped only using the standard stopping mechanisms. The downside of this state is that once this property is set, it can’t be modified. If an application needs to imitate looping for a particular number of times, it can be done through the use of queued Buffer classes that are discussed later in the chapter.

AL_BUFFER: This property is used to indicate the particular Buffer that will be bound to the Source so that the audio data can be properly rendered.

AL_SOURCE_STATE: The Source state is normally used to evaluate the current activity of a given source. Typical states for Sources are AL_PLAYING, AL_STOPPED, and AL_INITIAL. These state transitions are controlled by methods, but it is common to query the state of a Source before taking action. For example, if a Source is currently in the state of AL_PLAYING, calling the play method results in it being set to AL_INITIAL, putting the position of the play cursor back at the beginning of the Buffer and potentially limiting the completion of the currently playing sound. Although it is common for gun or laser sounds in a 2D shooter-styled game to be replayed frequently, a dialog segment might be interrupted at a crucial point if this check isn’t made first.

AL_BUFFERS_QUEUED: This property allows the user to find out the total number of Buffer classes in line to be played through the given Source. The process of lining up multiple Buffer classes to a single Source is discussed more thoroughly later in this chapter. One important note regarding this property is that the value is read only and cannot be set. The value is retrieved using the alGetSourcei() method.

AL_BUFFERS_PROCESSED: This property allows the user to identify the number of Buffer classes that have been processed through the given Source. This property is read only and its value can be retrieved using the alGetSourcei() method.

Sets of Source properties are also controlled through the use of single-float primitives, a floating-point vector, or a group of three plain floating-point values. Table 4.3 identifies each property as well as the proper data type used in conjunction with modifying the relevant property.

Table 4.3 :  SOURCE FLOATING-POINT-BASED PROPERTIES

Property

Data Type

Description

AL_PITCH

f

Increases a source’s pitch

AL_GAIN

f

Increases/decreases a source’s gain

AL_MAX_DISTANCE

f

Sets the maximum distance for a source’s gain to be adjusted

AL_ROLLOFF_FACTOR

f

Adjusts the speed of sound reduction once it reaches a max distance

AL_REFERENCE_DISTANCE

f

Represents the distance for a sound to drop by one half its normal volume

AL_MAX_GAIN

f

Represents the maximum gain for a given source

AL_MIN_GAIN

f

Represents the minimum gain for a given source

AL_CONE_OUTER_GAIN

f

Represents the gain outside an oriented cone

AL_CONE_INNER_ANGLE

f

Represents the gain inside an oriented cone

AL_CONE_OUTER_ANGLE

f,i

Represents the outer angle of a sound cone in degrees

AL_POSITION

fv,3f

Sets the x-, y-, z-position

AL_VELOCITY

fv,3f

Sets the velocity vector

AL_DIRECTION

fv,3f

Sets the direction vector

This list of properties is long, so let’s review the most frequently used properties.

AL_POSITION: This value is one of the more frequently used properties of Source classes. The position value of a Source might be updated frequently to represent the current location of the object in the world. The value is described in the x-, y-, z-coordinates of the Source. This value may remain static in a game if it were positioned around an immobile particle effect. It would be mobile to represent the maneuvering of a level boss maintaining its position with the updating coordinates of the boss enemy.

AL_VELOCITY: This property represents the speed and direction of an object that exists in the game world. The velocity of an object is important to the correct calculation of the Doppler effect, as used by OpenAL. To make an object update by its current velocity, the AL_POSITION property must be updated accordingly. JOAL does not automatically process updates related to velocity.

AL_PITCH, AL_GAIN, AL_MAX_GAIN, AL_MIN_GAIN: This group of properties is fairly straightforward. These values should be positive when applied to a Source. Generally speaking, a value of 1.0 is considered to be the default setting for the given audio.

Distance Model Properties: JOAL provides the ability to render sounds in a realistic fashion through attenuation of a sound over a given distance through the use of Distance Models. In the real world, the attenuation of a sound is governed by the Inverse Square Law, which states that a sound will drop in intensity proportionally to the inverse square of the distance from the source. JOAL provides functionality to calculate and render a sound for a Listener based upon these natural acoustic phenomenons through the implementation of the AL_INVERSE_DISTANCE and AL_INVERSE_DISTANCE_CLAMPED Distance Models.

These Distance Models are fundamentally the same, with one chief difference. The AL_INVERSE_DISTANCE_CLAMPED model uses the AL_REFERENCE_DISTANCE property to clamp down sounds that are below that distance’s threshold. This means that sounds using this distance method can have a minimum distance from the Source by which they can be audibly rendered. Any distance set at less than the reference distance will be clamped accordingly. AL_INVERSE_DISTANCE processes the attenuation of a sound without the limitation of the reference distance. It should also be noted that AL_INVERSE_DISTANCE is the default Distance Model for JOAL, though it can be modified through the alDistanceModel() method.

With that little discussion, let’s review the actual properties in question. The properties AL_MAX_DISTANCE, AL_REFERENCE_DISTANCE, and AL_ROLLOFF_FACTOR are used to calculate how a sound’s gain should be modified as it travels over a given distance away from the Source per the applied Distance Model.

In the real world, sound reflects off many surfaces as it travels through a given medium (usually air) on its way to someone’s ears. A sound heard from a distance may be loud and discernable at certain distances from the originating point but will decay as the sound travels and ultimately dissipates. In JOAL, using the AL_MAX_DISTANCE property allows a given Source to have a specific distance at which the sound will no longer be subject to attenuation. The actual value set is used in the calculations to determine how the sound would appear at the Listener class’s position. A sound that would normally travel beyond the maximum distance is clamped at the actual maximum distance value.

The use of the AL_ROLLOFF_FACTOR allows for the calculation of a scale by which the sound decays up to the AL_MAX_DISTANCE. This is the amount by which the sound’s gain will be reduced in an amortized fashion over the valid distance from the Source. The rolloff factor can be set to 0, which marks individual sources that are not to be attenuated through the Distance Model.

Depending on the Distance Model in use, the AL_REFERENCE_DISTANCE can be defined as the point where a sound will have lost half of its intensity, or, in the case of its use in the AL_INVERSE_DISTANCE_CLAMPED Distance Model, the reference distance specifies the effective minimum distance a Listener must be from a sound to hear it.

One additional note regarding calculations related to distance in OpenAL: JOAL allows developers to process the natural attenuation of a sound based on distance but does not identify standard units of measurement. All calculations related to distance are scale invariant. This means that the application in question must create this standard on its own and maintain consistency. It’s completely fine to use meters, feet, or whatever other measurement necessary; just make sure that the measurements are consistent to save some annoying headaches later.

After that lengthy discussion relating to Source properties, let’s go through and set some of these values based on our loading example up to this point.

al.alSourcei (source[0], AL.AL_BUFFER,   buffer[0]); al.alSourcef (source[0], AL.AL_PITCH,    1.0f     ); al.alSourcef (source[0], AL.AL_GAIN,     1.0f     ); al.alSourcefv(source[0], AL.AL_POSITION, sourcePosition); al.alSourcefv(source[0], AL.AL_VELOCITY, sourceVelocity); al.alSourcei (source[0], AL.AL_LOOPING,  loop[0]  ); 

This source is now bound to use the Buffer created with the bleep.wav file. It uses normal pitch and gain. The position and velocity are mapped to the settings in the appropriate float vectors created earlier. The sound is set not to loop by default. This is a pretty straightforward controlling mechanism.

Quick Recap

Let’s review what has been covered so far. OpenAL is initialized and shut down using the ALut class. PCM-encoded files can be loaded into the Buffer using the Alut WAV-loading method. Three main classes help render the audio in the form of Buffers, Sources, and Listeners. Buffers contain the actual sound data and are bound to Sources. There can be only one Listener at any given time. Source and Listener classes need to be described with position and velocity but also have other properties that can be modified as well. Listener and Source classes can set their attributes (such as current position and velocity) using descriptor methods and by specifying a particular property to set. JOAL is also capable of handling natural sound attenuation over distances by automatically using one of the provided Distance Models. Finally, JOAL doesn’t specify any particular units of measurement for processing calculations related to distance. It is the developer’s responsibility to organize positions and distances in a consistent measurement.

After all that, what’s left?

Player Controls

The most important part of an audio library is the playback controls that it provides. JOAL lets the user play, pause, stop, and rewind a single source or a group of sources. The control methods are contained in the AL interface. Each method takes the Source or group of Sources as arguments. One important thing to note is that a sound will play as long as you decide it should. Should a sound get interrupted or the Alut shut down, the playing of the samples will stop immediately, sometimes without playing at all. Getting a sound to play is as easy as using the following code:

al.alSourcePlay(source[0]); 

Stopping is also easy.

al.alSourceStop(source[0]);

It is also easy to perform the playback methods on a group of Sources at a single time. Each playback method has an alternate form that takes a number of samples and the integer array that holds the Sources. An example that pauses an entire group of Sources follows:

alSourcePausev(3,sources);

It is also useful to identify a Source class’s current state before making calls to stop or to play errantly. To find out the current state of a given Source, we can make a query as follows:

if(al.alGetSourcei(source[0],AL.AL_SOURCE_STATE)== AL.AL_PLAYING) System.out.println("The sound is playing!"); 

The alGetSourcei() method returns an integer to be stored and queried later as needed. The previous code sample is a more direct technique for smaller examples.

The SimplePlayer class in the provided Source code shows how to combine all the topics discussed so far to get a single sound playing. Compile it and tinker around with modifying the attributes of the Source and Listener. One note when compiling JOAL applications—be sure to place the joal.jar file and the library in the appropriate files so that it can be seen in your classpath; otherwise, the application will have no idea what an Alut is.

Loading More Than One Sound

Now that one file can be loaded and played, the next logical step is to increase the number of sounds that can be played in a given application. The process of loading more than one sound is also easy. Making that the next step on the JOAL to do list, let’s get started.

At the high level, the first step is to create additional Buffers and Sources in the same manner as described earlier. The number of Sources must be at least as large as the number of Buffers. It might be good to make a final int of the size needed.

final int MAX_SIZE 2; int[] buffers = new int[MAX_SIZE]; int[] sources = new int[MAX_SIZE];

The next step is to sequentially generate Buffers and load the files in incrementing positions in the Buffers and Sources arrays. It is easy to make a mistake in this segment, so make sure that the Buffer and Source are bound correctly to each other or the application will exhibit unexpected results.

al.alGenBuffers(MAX_SIZE, buffers);     if (al.alGetError() != AL.AL_NO_ERROR) {         return AL.AL_FALSE;     } ALut.alutLoadWAVFile("bleep.wav",format,data,size,freq,loop); al.alBufferData(buffers[0],format[0],data[0],size[0],freq[0]); ALut.alutUnloadWAV(format[0], data[0], size[0], freq[0]); ALut.alutLoadWAVFile("blorp.wav",format,data,size,freq,loop); al.alBufferData(buffers[1],format[0],data[0],size[0],freq[0]); ALut.alutUnloadWAV(format[0], data[0], size[0], freq[0]);

When the sounds have been correctly loaded into Buffers, the next step in the process is to generate the correct number of Sources and then fill out the Buffers, each with its own set of properties.

al.alGenSources(MAX_SIZE,sources); al.alSourcei(sources[0], AL.AL_BUFFER, buffer[0] ); al.alSourcef(sources[0], AL.AL_PITCH,  1.0f); al.alSourcef(sources[0], AL.AL_GAIN,   1.0f); al.alSourcefv(sources[0], AL.AL_POSITION, sourcePosition[0]); al.alSourcefv(sources[0], AL.AL_VELOCITY, sourceVelocity[0]); al.alSourcei(sources[0], AL.AL_LOOPING, AL.AL_FALSE);  al.alSourcei(sources[1], AL.AL_BUFFER, buffer[1] ); al.alSourcef(sources[1], AL.AL_PITCH,  1.5f); al.alSourcef(sources[1], AL.AL_GAIN,   1.0f); al.alSourcefv(sources[1], AL.AL_POSITION, sourcePosition[1]); al.alSourcefv(sources[1], AL.AL_VELOCITY, sourceVelocity[1]); al.alSourcei(sources[1], AL.AL_LOOPING, AL.AL_TRUE);

Each source can be modified based on whatever properties are needed. It is possible to link multiple sources to the same Buffers to avoid needing to load multiple copies of a given audio file. Just make sure that the correct values are set for each source. In most 3D games, the audio will be statically placed in the world to represent audio effect. For example, imagine walking through a jungle and hearing environmental sounds of monkeys and macaws that occasionally recur throughout a given level. The Sources used all have different positions in the world but are using the same set of Buffer information.

Once the Sources and Buffers are set up, it’s time to play back the audio. Triggering the audio effect to play is identical to working with a single Source, but make sure that the application adjusts the parameters based on the actual Source desired. Also watch out for off-by-one errors.

al.alSourcePlay(source[1]);

When a finalized sound engine is implemented in a game, it might function similarly to an actual audio mixer based in software. Channels can be muted, grouped, and processed. Keep that image in mind when building audio libraries for a game.

When the application is complete, and it’s time to clean up the mess, a couple of minor adjustments must be made to removes all Buffers and Sources:

al.alDeleteBuffers(MAX_SIZE, buffer); al.alDeleteSources(MAX_SIZE, source); ALut.alutExit();

Make sure that all of the Buffers and Sources are deleted when the application ends, then follow up with a call to alutExit().

 On the CD   Most of this code should look familiar. It’s mostly a repeated version of the SimplePlayer example used to process and load a single sound for playback. The sample program MultiSounds in the provided Source code shows this process in effect. This code is used for demonstration purposes and for that reason seems a bit less robust than might be desired. We will investigate some cleaner ways to work with multiple Sources and Buffers later. One more thing about the samples provided—they are the vocal styling of the author and are some of the few samples that could be made available with full permission. They are a bit quirky, but they work like a charm.



Practical Java Game Programming
Practical Java Game Programming (Charles River Media Game Development)
ISBN: 1584503262
EAN: 2147483647
Year: 2003
Pages: 171

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net