1 using System;
2 using System.Collections.Generic;
3 using OpenBveApi.Math;
4 using OpenBveApi.Sounds;
5 using OpenTK.Audio.OpenAL;
6 using SoundManager;
7 
8 namespace TrainEditor2.Audio
9 {
10 	internal partial class SoundApi
11 	{
UpdateLinearModel(double timeElapsed)12 		protected override void UpdateLinearModel(double timeElapsed)
13 		{
14 			throw new NotImplementedException();
15 		}
16 
17 		/// <summary>Updates the sound component. Should be called every frame.</summary>
18 		/// <param name="timeElapsed">The time in seconds that elapsed since the last call to this function.</param>
UpdateInverseModel(double timeElapsed)19 		protected override void UpdateInverseModel(double timeElapsed)
20 		{
21 			/*
22 			 * Set up the listener.
23 			 * */
24 			Vector3 listenerPosition = Vector3.Zero;
25 			Orientation3 listenerOrientation = new Orientation3(Vector3.Right, Vector3.Up, Vector3.Forward);
26 			Vector3 listenerVelocity = Vector3.Zero;
27 			AL.Listener(ALListener3f.Position, 0.0f, 0.0f, 0.0f);
28 			AL.Listener(ALListener3f.Velocity, (float)listenerVelocity.X, (float)listenerVelocity.Y, (float)listenerVelocity.Z);
29 			float[] 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 };
30 			AL.Listener(ALListenerfv.Orientation, ref Orientation);
31 
32 			/*
33 			 * Collect all sounds that are to be played
34 			 * and ensure that all others are stopped.
35 			 * */
36 			List<SoundSourceAttenuation> toBePlayed = new List<SoundSourceAttenuation>();
37 			for (int i = 0; i < SourceCount; i++)
38 			{
39 				if (Sources[i].State == SoundSourceState.StopPending)
40 				{
41 					/*
42 					 * The sound is still playing but is to be stopped.
43 					 * Stop the sound, then remove it from the list of
44 					 * sound sources.
45 					 * */
46 					AL.DeleteSources(1, ref Sources[i].OpenAlSourceName);
47 					Sources[i].State = SoundSourceState.Stopped;
48 					Sources[i].OpenAlSourceName = 0;
49 					Sources[i] = Sources[SourceCount - 1];
50 					SourceCount--;
51 					i--;
52 				}
53 				else if (Sources[i].State == SoundSourceState.Stopped)
54 				{
55 					/*
56 					 * The sound was already stopped. Remove it from
57 					 * the list of sound sources.
58 					 * */
59 					Sources[i] = Sources[SourceCount - 1];
60 					SourceCount--;
61 					i--;
62 				}
63 				else if (GlobalMute)
64 				{
65 					/*
66 					 * The sound is playing or about to be played, but
67 					 * the global mute option is enabled. Stop the sound
68 					 * sound if necessary, then remove it from the list
69 					 * of sound sources if the sound is not looping.
70 					 * */
71 					if (Sources[i].State == SoundSourceState.Playing)
72 					{
73 						AL.DeleteSources(1, ref Sources[i].OpenAlSourceName);
74 						Sources[i].State = SoundSourceState.PlayPending;
75 						Sources[i].OpenAlSourceName = 0;
76 					}
77 					if (!Sources[i].Looped)
78 					{
79 						Sources[i].State = SoundSourceState.Stopped;
80 						Sources[i].OpenAlSourceName = 0;
81 						Sources[i] = Sources[SourceCount - 1];
82 						SourceCount--;
83 						i--;
84 					}
85 				}
86 				else
87 				{
88 					/*
89 					 * The sound is to be played or is already playing.
90 					 * */
91 					if (Sources[i].State == SoundSourceState.Playing)
92 					{
93 						int state;
94 						AL.GetSource(Sources[i].OpenAlSourceName, ALGetSourcei.SourceState, out state);
95 						if (state != (int)ALSourceState.Initial & state != (int)ALSourceState.Playing)
96 						{
97 							/*
98 							 * The sound is not playing any longer.
99 							 * Remove it from the list of sound sources.
100 							 * */
101 							AL.DeleteSources(1, ref Sources[i].OpenAlSourceName);
102 							Sources[i].State = SoundSourceState.Stopped;
103 							Sources[i].OpenAlSourceName = 0;
104 							Sources[i] = Sources[SourceCount - 1];
105 							SourceCount--;
106 							i--;
107 							continue;
108 						}
109 					}
110 
111 					/*
112 					 * Calculate the gain, then add the sound
113 					 * to the list of sounds to be played.
114 					 * */
115 					Vector3 position = Sources[i].Position;
116 					Vector3 positionDifference = position - listenerPosition;
117 					double distance = positionDifference.Norm();
118 					double radius = Sources[i].Radius;
119 					double gain;
120 					if (distance < 2.0 * radius)
121 					{
122 						gain = 1.0 - distance * distance * (4.0 * radius - distance) / (16.0 * radius * radius * radius);
123 					}
124 					else
125 					{
126 						gain = radius / distance;
127 					}
128 					gain *= Sources[i].Volume;
129 					if (gain <= 0.0)
130 					{
131 						/*
132 						 * The gain is too low. Stop the sound if playing,
133 						 * but keep looping sounds pending.
134 						 * */
135 						if (Sources[i].State == SoundSourceState.Playing)
136 						{
137 							AL.DeleteSources(1, ref Sources[i].OpenAlSourceName);
138 							Sources[i].State = SoundSourceState.PlayPending;
139 							Sources[i].OpenAlSourceName = 0;
140 						}
141 						if (!Sources[i].Looped)
142 						{
143 							Sources[i].State = SoundSourceState.Stopped;
144 							Sources[i].OpenAlSourceName = 0;
145 							Sources[i] = Sources[SourceCount - 1];
146 							SourceCount--;
147 							i--;
148 						}
149 					}
150 					else
151 					{
152 						/*
153 						 * Add the source.
154 						 * */
155 						toBePlayed.Add(new SoundSourceAttenuation(Sources[i], gain, distance));
156 					}
157 				}
158 			}
159 
160 			/*
161 			 * Now that we have the list of sounds that are to be played,
162 			 * sort them by their gain so that we can determine and
163 			 * adjust the clamp factor.
164 			 * */
165 			double clampFactor = Math.Exp(LogClampFactor);
166 			for (int i = 0; i < toBePlayed.Count; i++)
167 			{
168 				toBePlayed[i].Gain -= clampFactor * toBePlayed[i].Distance * toBePlayed[i].Distance;
169 			}
170 			toBePlayed.Sort();
171 			for (int i = 0; i < toBePlayed.Count; i++)
172 			{
173 				toBePlayed[i].Gain += clampFactor * toBePlayed[i].Distance * toBePlayed[i].Distance;
174 			}
175 			double desiredLogClampFactor;
176 			int index = 16;
177 			if (toBePlayed.Count <= index)
178 			{
179 				desiredLogClampFactor = MinLogClampFactor;
180 			}
181 			else
182 			{
183 				double cutoffDistance = toBePlayed[index].Distance;
184 				if (cutoffDistance <= 0.0)
185 				{
186 					desiredLogClampFactor = MaxLogClampFactor;
187 				}
188 				else
189 				{
190 					double cutoffGain = toBePlayed[index].Gain;
191 					desiredLogClampFactor = Math.Log(cutoffGain / (cutoffDistance * cutoffDistance));
192 					if (desiredLogClampFactor < MinLogClampFactor)
193 					{
194 						desiredLogClampFactor = MinLogClampFactor;
195 					}
196 					else if (desiredLogClampFactor > MaxLogClampFactor)
197 					{
198 						desiredLogClampFactor = MaxLogClampFactor;
199 					}
200 				}
201 			}
202 			const double rate = 3.0;
203 			if (LogClampFactor < desiredLogClampFactor)
204 			{
205 				LogClampFactor += timeElapsed * rate;
206 				if (LogClampFactor > desiredLogClampFactor)
207 				{
208 					LogClampFactor = desiredLogClampFactor;
209 				}
210 			}
211 			else if (LogClampFactor > desiredLogClampFactor)
212 			{
213 				LogClampFactor -= timeElapsed * rate;
214 				if (LogClampFactor < desiredLogClampFactor)
215 				{
216 					LogClampFactor = desiredLogClampFactor;
217 				}
218 			}
219 
220 			/*
221 			 * Play the sounds.
222 			 * */
223 			clampFactor = Math.Exp(LogClampFactor);
224 			for (int i = index; i < toBePlayed.Count; i++)
225 			{
226 				toBePlayed[i].Gain = 0.0;
227 			}
228 			for (int i = 0; i < toBePlayed.Count; i++)
229 			{
230 				SoundSource source = toBePlayed[i].Source;
231 				double gain = toBePlayed[i].Gain - clampFactor * toBePlayed[i].Distance * toBePlayed[i].Distance;
232 				if (gain <= 0.0)
233 				{
234 					/*
235 					 * Stop the sound.
236 					 * */
237 					if (source.State == SoundSourceState.Playing)
238 					{
239 						AL.DeleteSources(1, ref source.OpenAlSourceName);
240 						source.State = SoundSourceState.PlayPending;
241 						source.OpenAlSourceName = 0;
242 					}
243 					if (!source.Looped)
244 					{
245 						source.State = SoundSourceState.Stopped;
246 						source.OpenAlSourceName = 0;
247 					}
248 				}
249 				else
250 				{
251 					/*
252 					 * Ensure the buffer is loaded, then play the sound.
253 					 * */
254 					if (source.State != SoundSourceState.Playing)
255 					{
256 						LoadBuffer(source.Buffer);
257 						if (source.Buffer.Loaded)
258 						{
259 							AL.GenSources(1, out source.OpenAlSourceName);
260 							AL.Source(source.OpenAlSourceName, ALSourcei.Buffer, source.Buffer.OpenAlBufferName);
261 						}
262 						else
263 						{
264 							/*
265 							 * We cannot play the sound because
266 							 * the buffer could not be loaded.
267 							 * */
268 							source.State = SoundSourceState.Stopped;
269 							continue;
270 						}
271 					}
272 
273 					Vector3 position = source.Position;
274 					Vector3 velocity = Vector3.Zero;
275 					position -= listenerPosition;
276 					AL.Source(source.OpenAlSourceName, ALSource3f.Position, (float)position.X, (float)position.Y, (float)position.Z);
277 					AL.Source(source.OpenAlSourceName, ALSource3f.Velocity, (float)velocity.X, (float)velocity.Y, (float)velocity.Z);
278 					AL.Source(source.OpenAlSourceName, ALSourcef.Pitch, (float)source.Pitch);
279 					AL.Source(source.OpenAlSourceName, ALSourcef.Gain, (float)gain);
280 					if (source.State != SoundSourceState.Playing)
281 					{
282 						AL.Source(source.OpenAlSourceName, ALSourceb.Looping, source.Looped);
283 						AL.SourcePlay(source.OpenAlSourceName);
284 						source.State = SoundSourceState.Playing;
285 					}
286 				}
287 			}
288 		}
289 	}
290 }
291