/// <summary>Updates the sound component. Should be called every frame.</summary>
/// <param name="timeElapsed">The time in seconds that elapsed since the last call to this function.</param>
private static void UpdateInverseModel(double timeElapsed)
{
/*
* Set up the listener.
* */
OpenBveApi.Math.Vector3 listenerPosition = World.AbsoluteCameraPosition;
OpenBveApi.Math.Orientation3 listenerOrientation = new OpenBveApi.Math.Orientation3(World.AbsoluteCameraSide, World.AbsoluteCameraUp, World.AbsoluteCameraDirection);
OpenBveApi.Math.Vector3 listenerVelocity;
if (World.CameraMode == World.CameraViewMode.Interior | World.CameraMode == World.CameraViewMode.InteriorLookAhead | World.CameraMode == World.CameraViewMode.Exterior)
{
TrainManager.Car car = TrainManager.PlayerTrain.Cars[TrainManager.PlayerTrain.DriverCar];
OpenBveApi.Math.Vector3 diff = car.FrontAxle.Follower.WorldPosition - car.RearAxle.Follower.WorldPosition;
if (diff.IsNullVector())
{
listenerVelocity = car.Specs.CurrentSpeed * OpenBveApi.Math.Vector3.Forward;
}
else
{
listenerVelocity = car.Specs.CurrentSpeed * OpenBveApi.Math.Vector3.Normalize(diff);
}
}
else
{
listenerVelocity = OpenBveApi.Math.Vector3.Null;
}
Al.alListener3f(Al.AL_POSITION, 0.0f, 0.0f, 0.0f);
Al.alListener3f(Al.AL_VELOCITY, (float)listenerVelocity.X, (float)listenerVelocity.Y, (float)listenerVelocity.Z);
Al.alListenerfv(Al.AL_ORIENTATION, new float[] { (float)listenerOrientation.Z.X, (float)listenerOrientation.Z.Y, (float)listenerOrientation.Z.Z, -(float)listenerOrientation.Y.X, -(float)listenerOrientation.Y.Y, -(float)listenerOrientation.Y.Z });
/*
* Set up the atmospheric attributes.
* */
double elevation = World.AbsoluteCameraPosition.Y + Game.RouteInitialElevation;
double airTemperature = Game.GetAirTemperature(elevation);
double airPressure = Game.GetAirPressure(elevation, airTemperature);
double airDensity = Game.GetAirDensity(airPressure, airTemperature);
double speedOfSound = Game.GetSpeedOfSound(airPressure, airTemperature);
try {
Al.alSpeedOfSound((float)speedOfSound);
} catch { }
/*
* Collect all sounds that are to be played
* and ensure that all others are stopped.
* */
List <SoundSourceAttenuation> toBePlayed = new List <SoundSourceAttenuation>();
for (int i = 0; i < SourceCount; i++)
{
if (Sources[i].State == SoundSourceState.StopPending)
{
/*
* The sound is still playing but is to be stopped.
* Stop the sound, then remove it from the list of
* sound sources.
* */
Al.alDeleteSources(1, ref Sources[i].OpenAlSourceName);
Sources[i].State = SoundSourceState.Stopped;
Sources[i].OpenAlSourceName = 0;
Sources[i] = Sources[SourceCount - 1];
SourceCount--;
i--;
}
else if (Sources[i].State == SoundSourceState.Stopped)
{
/*
* The sound was already stopped. Remove it from
* the list of sound sources.
* */
Sources[i] = Sources[SourceCount - 1];
SourceCount--;
i--;
}
else if (GlobalMute)
{
/*
* The sound is playing or about to be played, but
* the global mute option is enabled. Stop the sound
* sound if necessary, then remove it from the list
* of sound sources if the sound is not looping.
* */
if (Sources[i].State == SoundSourceState.Playing)
{
Al.alDeleteSources(1, ref Sources[i].OpenAlSourceName);
Sources[i].State = SoundSourceState.PlayPending;
Sources[i].OpenAlSourceName = 0;
}
if (!Sources[i].Looped)
{
Sources[i].State = SoundSourceState.Stopped;
Sources[i].OpenAlSourceName = 0;
Sources[i] = Sources[SourceCount - 1];
SourceCount--;
i--;
}
}
else
{
/*
* The sound is to be played or is already playing.
* */
if (Sources[i].State == SoundSourceState.Playing)
{
int state;
Al.alGetSourcei(Sources[i].OpenAlSourceName, Al.AL_SOURCE_STATE, out state);
if (state != Al.AL_INITIAL & state != Al.AL_PLAYING)
{
/*
* The sound is not playing any longer.
* Remove it from the list of sound sources.
* */
Al.alDeleteSources(1, ref Sources[i].OpenAlSourceName);
Sources[i].State = SoundSourceState.Stopped;
Sources[i].OpenAlSourceName = 0;
Sources[i] = Sources[SourceCount - 1];
SourceCount--;
i--;
continue;
}
}
/*
* Calculate the gain, then add the sound
* to the list of sounds to be played.
* */
OpenBveApi.Math.Vector3 position;
if (Sources[i].Train != null)
{
OpenBveApi.Math.Vector3 direction;
TrainManager.CreateWorldCoordinates(Sources[i].Train, Sources[i].Car, Sources[i].Position.X, Sources[i].Position.Y, Sources[i].Position.Z, out position.X, out position.Y, out position.Z, out direction.X, out direction.Y, out direction.Z);
}
else
{
position = Sources[i].Position;
}
OpenBveApi.Math.Vector3 positionDifference = position - listenerPosition;
double distance = positionDifference.Norm();
double radius = Sources[i].Radius;
if (World.CameraMode == World.CameraViewMode.Interior | World.CameraMode == World.CameraViewMode.InteriorLookAhead)
{
if (Sources[i].Train != TrainManager.PlayerTrain || Sources[i].Car != TrainManager.PlayerTrain.DriverCar)
{
radius *= 0.5;
}
}
double gain;
if (distance < 2.0 * radius)
{
gain = 1.0 - distance * distance * (4.0 * radius - distance) / (16.0 * radius * radius * radius);
}
else
{
gain = radius / distance;
}
gain *= Sources[i].Volume;
if (gain <= 0.0)
{
/*
* The gain is too low. Stop the sound if playing,
* but keep looping sounds pending.
* */
if (Sources[i].State == SoundSourceState.Playing)
{
Al.alDeleteSources(1, ref Sources[i].OpenAlSourceName);
Sources[i].State = SoundSourceState.PlayPending;
Sources[i].OpenAlSourceName = 0;
}
if (!Sources[i].Looped)
{
Sources[i].State = SoundSourceState.Stopped;
Sources[i].OpenAlSourceName = 0;
Sources[i] = Sources[SourceCount - 1];
SourceCount--;
i--;
}
}
else
{
/*
* Add the source.
* */
toBePlayed.Add(new SoundSourceAttenuation(Sources[i], gain, distance));
}
}
}
/*
* Now that we have the list of sounds that are to be played,
* sort them by their gain so that we can determine and
* adjust the clamp factor.
* */
double clampFactor = Math.Exp(LogClampFactor);
for (int i = 0; i < toBePlayed.Count; i++)
{
toBePlayed[i].Gain -= clampFactor * toBePlayed[i].Distance * toBePlayed[i].Distance;
}
toBePlayed.Sort();
for (int i = 0; i < toBePlayed.Count; i++)
{
toBePlayed[i].Gain += clampFactor * toBePlayed[i].Distance * toBePlayed[i].Distance;
}
double desiredLogClampFactor;
int index = Interface.CurrentOptions.SoundNumber;
if (toBePlayed.Count <= index)
{
desiredLogClampFactor = MinLogClampFactor;
}
else
{
double cutoffDistance = toBePlayed[index].Distance;
if (cutoffDistance <= 0.0)
{
desiredLogClampFactor = MaxLogClampFactor;
}
else
{
double cutoffGain = toBePlayed[index].Gain;
desiredLogClampFactor = Math.Log(cutoffGain / (cutoffDistance * cutoffDistance));
if (desiredLogClampFactor < MinLogClampFactor)
{
desiredLogClampFactor = MinLogClampFactor;
}
else if (desiredLogClampFactor > MaxLogClampFactor)
{
desiredLogClampFactor = MaxLogClampFactor;
}
}
}
const double rate = 3.0;
if (LogClampFactor < desiredLogClampFactor)
{
LogClampFactor += timeElapsed * rate;
if (LogClampFactor > desiredLogClampFactor)
{
LogClampFactor = desiredLogClampFactor;
}
}
else if (LogClampFactor > desiredLogClampFactor)
{
LogClampFactor -= timeElapsed * rate;
if (LogClampFactor < desiredLogClampFactor)
{
LogClampFactor = desiredLogClampFactor;
}
}
/*
* Play the sounds.
* */
clampFactor = Math.Exp(LogClampFactor);
for (int i = index; i < toBePlayed.Count; i++)
{
toBePlayed[i].Gain = 0.0;
}
for (int i = 0; i < toBePlayed.Count; i++)
{
SoundSource source = toBePlayed[i].Source;
double gain = toBePlayed[i].Gain - clampFactor * toBePlayed[i].Distance * toBePlayed[i].Distance;
if (gain <= 0.0)
{
/*
* Stop the sound.
* */
if (source.State == SoundSourceState.Playing)
{
Al.alDeleteSources(1, ref source.OpenAlSourceName);
source.State = SoundSourceState.PlayPending;
source.OpenAlSourceName = 0;
}
if (!source.Looped)
{
source.State = SoundSourceState.Stopped;
source.OpenAlSourceName = 0;
}
}
else
{
/*
* Ensure the buffer is loaded, then play the sound.
* */
if (source.State != SoundSourceState.Playing)
{
LoadBuffer(source.Buffer);
if (source.Buffer.Loaded)
{
Al.alGenSources(1, out source.OpenAlSourceName);
Al.alSourcei(source.OpenAlSourceName, Al.AL_BUFFER, source.Buffer.OpenAlBufferName);
}
else
{
/*
* We cannot play the sound because
* the buffer could not be loaded.
* */
source.State = SoundSourceState.Stopped;
continue;
}
}
OpenBveApi.Math.Vector3 position;
OpenBveApi.Math.Vector3 velocity;
if (source.Train != null)
{
OpenBveApi.Math.Vector3 direction;
TrainManager.CreateWorldCoordinates(source.Train, source.Car, source.Position.X, source.Position.Y, source.Position.Z, out position.X, out position.Y, out position.Z, out direction.X, out direction.Y, out direction.Z);
velocity = source.Train.Cars[source.Car].Specs.CurrentSpeed * direction;
}
else
{
position = source.Position;
velocity = OpenBveApi.Math.Vector3.Null;
}
position -= listenerPosition;
Al.alSource3f(source.OpenAlSourceName, Al.AL_POSITION, (float)position.X, (float)position.Y, (float)position.Z);
Al.alSource3f(source.OpenAlSourceName, Al.AL_VELOCITY, (float)velocity.X, (float)velocity.Y, (float)velocity.Z);
Al.alSourcef(source.OpenAlSourceName, Al.AL_PITCH, (float)source.Pitch);
Al.alSourcef(source.OpenAlSourceName, Al.AL_GAIN, (float)gain);
if (source.State != SoundSourceState.Playing)
{
Al.alSourcei(source.OpenAlSourceName, Al.AL_LOOPING, source.Looped ? Al.AL_TRUE : Al.AL_FALSE);
Al.alSourcePlay(source.OpenAlSourceName);
source.State = SoundSourceState.Playing;
}
}
}
}