(Sorry for my bad English)
Hi, I'm learning LWJGL 3 OpenAL library.
Playing around with the 3D audio attenuation, I noticed that you can't get a gain of zero for a source using a good realistic distance model like AL_INVERSE_DISTANCE_CLAMPED:
I can achieve this using the AL_LINEAR_DISTANCE_CLAMPED because after the AL_MAX_DISTANCE the source gain is zero:
But this is a very bad and unrealistic model....
So, is there a way to have a AL_INVERSE_DISTANCE_CLAMPED model but with a gain of zero after passing the AL_MAX_DISTANCE? Something like this:
Here's a very simplified example of my current code:
import java.io.BufferedInputStream;
import java.nio.ByteBuffer;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import org.lwjgl.BufferUtils;
import static org.lwjgl.openal.AL.*;
import static org.lwjgl.openal.AL10.*;
import static org.lwjgl.openal.ALC11.*;
import static org.lwjgl.openal.ALC.*;
public class Main {
public static void main(String[] args) throws Exception {
// some init stuff from the docs
long device;
long context;
String deviceName = alcGetString(0, ALC_DEFAULT_DEVICE_SPECIFIER);
device = alcOpenDevice(deviceName);
context = alcCreateContext(device, new int[]{0});
alcMakeContextCurrent(context);
createCapabilities(createCapabilities(device));
alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); // using inverse distance clamped model
int source = alGenSources(); // create a source
alSource3f(source, AL_POSITION, 0, 0, 0); // set the source position to 0
// source values to work with the distance model
alSourcef(source, AL_ROLLOFF_FACTOR, 1f); // the rolloff factor makes some changes to the gain curve: the higher the rolloff, the steeper the curve will be
alSourcef(source, AL_REFERENCE_DISTANCE, 5f); // the reference distance determines the distance between source and listener where the gain is exactly 1
//alSourcef(source, AL_MAX_DISTANCE, 15f); // after this distance between source and listener, the sound won't be attenuated anymore in the clamped models
// I leave this value commented because it would be exactly what I don't want
alSourcef(source, AL_GAIN, 1f); // set the gain of the sound to 1
alSourcei(source, AL_LOOPING, AL_TRUE); // loop the sound
// now let's load the audio file
int audioBuffer = alGenBuffers(); // we'll store the buffer id of the sound in this variable
// load the audio file to an AudioInputStream
AudioInputStream stream = AudioSystem.getAudioInputStream(new BufferedInputStream(Main.class.getResourceAsStream("/sound.wav")));
AudioFormat audioFormat = stream.getFormat();
int format, sampleRate, bytesPerFrame, totalBytes;
// OpenAL needs a ByteBuffer to fill its buffer with readable data, so now we need to convert the AudioInputStream to a ByteBuffer and some audio infos
ByteBuffer data;
format = AL_FORMAT_MONO16; // for simplicity, I set the format to mono16 but there are some other ways to get the right format
sampleRate = (int)audioFormat.getSampleRate();
bytesPerFrame = audioFormat.getFrameSize();
totalBytes = (int)stream.getFrameLength() * bytesPerFrame;
data = BufferUtils.createByteBuffer(totalBytes);
// read the stream and put the data in the ByteBuffer
byte[] temp = new byte[totalBytes];
int read = stream.read(temp, 0, totalBytes);
data.clear();
data.put(temp, 0, read);
data.flip(); // flip the buffer, otherwise OpenAL won't be able to read it
stream.close(); // close the stream, we don't need it anymore
alBufferData(audioBuffer, format, data, sampleRate); // now we can finally send the audio data to OpenAL
alSourcei(source, AL_BUFFER, audioBuffer); // tell the source to play this sound
float listenerX = -20f;
alListener3f(AL_POSITION, listenerX, 0, 2); // set the initial listener position to -20 on the x and 2 on the z to have a nice 3D audio transition between the channels
alSourcePlay(source); // play the sound
while(listenerX < 50) {
listenerX += .05f; // move the listener on the X axis
alListener3f(AL_POSITION, listenerX, 0, 2);
Thread.sleep(10);
}
}
}
If you run this code, you'll notice that even when the listener is 50 units far away from the source the sound still clearly audible! I'd like to have a maximum distance from the source where the listener won't be able anymore to hear the sound but without suddently setting the gain to 0.
Is there any way to do it?
UPDATE 1:
I discovered that the gain of the source set by distance model is overwrote by the source gain (the one defined in this way: alSourcef(source, AL_GAIN, gain)). So if I set the source gain to 0f, the distance model will interpolate with a maximum gain value of zero, so there won't be any sound.
So I tried to actually merge the AL_INVERSE_DISTANCE_CLAMPED model with the AL_LINEAR_DISTANCE_CLAMPED model. I'm not good at drawing these graphs but it should be something like that:
The blue curve works like a "limiter" for the green curve.
So, the blue curve is made by linearly interpolating the source gain from 1f to 0f based on the distance between source and listener. The green curve is made by the distance model.
Here's how I implemented this based on the example code I provided before:
First let's create some variables
float rolloff = 1f;
float maxDistance = 20f;
float referenceDistance = 5;
float gain = 1f; // the source gain. It will work as a "limiter" for the distance model
Now we have to set our distance model and then give rolloff, maxDistance and referenceDistance to OpenAL
alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); // using inverse distance clamped model
alSourcef(source, AL_ROLLOFF_FACTOR, rolloff); // the rolloff factor makes some changes to the gain curve: the higher the rolloff, the steeper the curve will be
alSourcef(source, AL_MAX_DISTANCE, maxDistance); // after this distance between source and listener, the sound won't be attenuated anymore in the clamped models
alSourcef(source, AL_REFERENCE_DISTANCE, referenceDistance); // the reference distance determines the distance between source and listener where the gain is exactly 1
Now in the while loop we first find the distance between the source and the listener
float distance = (float)Math.sqrt(dx*dx + dy*dy + dz*dz);
To calculate the distance we can easily find the source and listener coords in this way. Use alGetListener3f(param, buffX, buffY, buffZ) to get listener coords or alGetSource3f(source, param, buffX, buffY, buffZ) passing as first parameter the source ID
public Vector3f getPosition3f() {
FloatBuffer bufferX = BufferUtils.createFloatBuffer(1);
FloatBuffer bufferY = BufferUtils.createFloatBuffer(1);
FloatBuffer bufferZ = BufferUtils.createFloatBuffer(1);
alGetListener3f(AL_POSITION, bufferX, bufferY, bufferZ);
return new Vector3f(bufferX.get(0), bufferY.get(0), bufferZ.get(0));
}
At this point we must calculate the gain given the distance. I'm not a pro in math, so this could be a bit weird but it works
if(distance > 0) gain = 1 - (distance - referenceDistance) / (maxDistance - referenceDistance);
else gain = 1 + (distance + referenceDistance) / (maxDistance - referenceDistance);
The gain could now get below zero or over one, so we need to check this
if(gain < 0) gain = 0;
else if(gain > 1) gain = 1;
Finally we can set the source gain!
alSourcef(source, AL_GAIN, gain);
So, Why I'm not using this as an answer?
Applying this into a Game Loop context, I'm afraid it could slow down the game and weigh heavily on the CPU having to calculate the gain in the same thread for each source at each update. Imagine if for example in the game there was a stable full of animals making noises at the same time. I think it would be difficult for the CPU to do all this in addition to the other update tasks.
mini-update: I just tried this solution on my PC and as I expected, over 300 active sources the game starts to freeze and lag (using an i9-9900k)


