/// <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 UpdateLinearModel(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 = World.CameraAlignmentSpeed.Position;
AL.Listener(ALListener3f.Position, 0.0f, 0.0f, 0.0f);
AL.Listener(ALListener3f.Velocity, (float)listenerVelocity.X, (float)listenerVelocity.Y, (float)listenerVelocity.Z);
var Orientation = new[] { (float)listenerOrientation.Z.X, (float)listenerOrientation.Z.Y, (float)listenerOrientation.Z.Z, -(float)listenerOrientation.Y.X, -(float)listenerOrientation.Y.Y, -(float)listenerOrientation.Y.Z };
AL.Listener(ALListenerfv.Orientation, ref Orientation);
/*
* Set up the atmospheric attributes
* */
double elevation = World.AbsoluteCameraPosition.Y + Game.RouteInitialElevation;
double airTemperature = Game.GetAirTemperature(elevation);
double airPressure = Game.GetAirPressure(elevation, airTemperature);
double speedOfSound = Game.GetSpeedOfSound(airPressure, airTemperature);
try
{
AL.SpeedOfSound((float)speedOfSound);
}
catch { }
/*
* Update the sound sources
* */
int actuallyPlaying = 0;
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.DeleteSources(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.DeleteSources(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.
* Calculate the sound gain.
* */
OpenBveApi.Math.Vector3 position;
OpenBveApi.Math.Vector3 velocity;
switch (Sources[i].Type)
{
case SoundType.TrainCar:
OpenBveApi.Math.Vector3 direction;
Sources[i].Train.Cars[Sources[i].Car].CreateWorldCoordinates(Sources[i].Position, out position, out direction);
velocity = Sources[i].Train.Cars[Sources[i].Car].Specs.CurrentSpeed * direction;
break;
default:
position = Sources[i].Position;
velocity = OpenBveApi.Math.Vector3.Zero;
break;
}
OpenBveApi.Math.Vector3 positionDifference = position - listenerPosition;
double gain;
if (GlobalMute)
{
gain = 0.0;
}
else
{
double distance = positionDifference.Norm();
double innerRadius = Sources[i].Radius;
if (World.CameraMode == CameraViewMode.Interior | World.CameraMode == CameraViewMode.InteriorLookAhead)
{
if (Sources[i].Train != TrainManager.PlayerTrain || Sources[i].Car != TrainManager.PlayerTrain.DriverCar)
{
innerRadius *= 0.5;
}
}
double outerRadius = OuterRadiusFactor * innerRadius;
if (distance < outerRadius)
{
if (distance <= innerRadius)
{
gain = Sources[i].Volume;
}
else
{
gain = (distance - outerRadius) / (innerRadius - outerRadius);
gain *= Sources[i].Volume;
}
gain = 3.0 * gain * gain - 2.0 * gain * gain * gain;
}
else
{
gain = 0.0;
}
}
if (gain <= GainThreshold)
{
/*
* If the gain is too low to be audible, stop the sound.
* If the sound is not looping, stop it if necessary,
* then remove it from the list of sound sources.
* */
if (Sources[i].State == SoundSourceState.Playing)
{
AL.DeleteSources(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
{
/*
* Play the sound and update position, velocity, pitch and gain.
* For non-looping sounds, check if the sound is still playing.
* */
gain = (gain - GainThreshold) / (1.0 - GainThreshold);
if (Sources[i].State != SoundSourceState.Playing)
{
LoadBuffer(Sources[i].Buffer);
if (Sources[i].Buffer.Loaded)
{
AL.GenSources(1, out Sources[i].OpenAlSourceName);
AL.Source(Sources[i].OpenAlSourceName, ALSourcei.Buffer, Sources[i].Buffer.OpenAlBufferName);
}
else
{
/*
* We cannot play the sound because
* the buffer could not be loaded.
* */
Sources[i].State = SoundSourceState.Stopped;
continue;
}
}
AL.Source(Sources[i].OpenAlSourceName, ALSource3f.Position, (float)positionDifference.X, (float)positionDifference.Y, (float)positionDifference.Z);
AL.Source(Sources[i].OpenAlSourceName, ALSource3f.Velocity, (float)velocity.X, (float)velocity.Y, (float)velocity.Z);
AL.Source(Sources[i].OpenAlSourceName, ALSourcef.Pitch, (float)Sources[i].Pitch);
AL.Source(Sources[i].OpenAlSourceName, ALSourcef.Gain, (float)gain);
if (Sources[i].State != SoundSourceState.Playing)
{
AL.Source(Sources[i].OpenAlSourceName, ALSourceb.Looping, Sources[i].Looped);
AL.SourcePlay(Sources[i].OpenAlSourceName);
Sources[i].State = SoundSourceState.Playing;
}
if (!Sources[i].Looped)
{
int state;
AL.GetSource(Sources[i].OpenAlSourceName, ALGetSourcei.SourceState, out state);
if (state != (int)ALSourceState.Initial & state != (int)ALSourceState.Playing)
{
/*
* The sound is not playing any longer.
* Remove it from the list of sound sources.
* */
AL.DeleteSources(1, ref Sources[i].OpenAlSourceName);
Sources[i].State = SoundSourceState.Stopped;
Sources[i].OpenAlSourceName = 0;
Sources[i] = Sources[SourceCount - 1];
SourceCount--;
i--;
}
else
{
actuallyPlaying++;
}
}
else
{
actuallyPlaying++;
}
}
}
}
/*
* Adjust the outer radius factor / the clamp factor.
* */
if (actuallyPlaying >= Interface.CurrentOptions.SoundNumber - 2)
{
/*
* Too many sounds are playing.
* Reduce the outer radius factor.
* */
OuterRadiusFactorSpeed -= timeElapsed;
if (OuterRadiusFactorSpeed < -OuterRadiusFactorMaximumSpeed)
{
OuterRadiusFactorSpeed = -OuterRadiusFactorMaximumSpeed;
}
}
else if (actuallyPlaying <= Interface.CurrentOptions.SoundNumber - 6)
{
/*
* Only few sounds are playing.
* Increase the outer radius factor.
* */
OuterRadiusFactorSpeed += timeElapsed;
if (OuterRadiusFactorSpeed > OuterRadiusFactorMaximumSpeed)
{
OuterRadiusFactorSpeed = OuterRadiusFactorMaximumSpeed;
}
}
else
{
/*
* Neither too many nor too few sounds are playing.
* Stabilize the outer radius factor.
* */
if (OuterRadiusFactorSpeed < 0.0)
{
OuterRadiusFactorSpeed += timeElapsed;
if (OuterRadiusFactorSpeed > 0.0)
{
OuterRadiusFactorSpeed = 0.0;
}
}
else
{
OuterRadiusFactorSpeed -= timeElapsed;
if (OuterRadiusFactorSpeed < 0.0)
{
OuterRadiusFactorSpeed = 0.0;
}
}
}
OuterRadiusFactor += OuterRadiusFactorSpeed * timeElapsed;
if (OuterRadiusFactor < OuterRadiusFactorMinimum)
{
OuterRadiusFactor = OuterRadiusFactorMinimum;
OuterRadiusFactorSpeed = 0.0;
}
else if (OuterRadiusFactor > OuterRadiusFactorMaximum)
{
OuterRadiusFactor = OuterRadiusFactorMaximum;
OuterRadiusFactorSpeed = 0.0;
}
}