OSDN Git Service

FMOD calls a callback after object collected. Not found it yet
[radegast/radegast.git] / Radegast / Core / Media / MediaManager.cs
1 // 
2 // Radegast Metaverse Client
3 // Copyright (c) 2009, Radegast Development Team
4 // All rights reserved.
5 // 
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are met:
8 // 
9 //     * Redistributions of source code must retain the above copyright notice,
10 //       this list of conditions and the following disclaimer.
11 //     * Redistributions in binary form must reproduce the above copyright
12 //       notice, this list of conditions and the following disclaimer in the
13 //       documentation and/or other materials provided with the distribution.
14 //     * Neither the name of the application "Radegast", nor the names of its
15 //       contributors may be used to endorse or promote products derived from
16 //       this software without specific prior written permission.
17 // 
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 //
29 // $Id$
30 //
31 using System;
32 using System.Collections.Generic;
33 using System.Text;
34 using FMOD;
35 using System.Threading;
36 using OpenMetaverse;
37
38 namespace Radegast.Media
39 {
40     public class MediaManager : MediaObject
41     {
42         /// <summary>
43         /// Indicated wheather spund sytem is ready for use
44         /// </summary>
45         public bool SoundSystemAvailable { get { return soundSystemAvailable; } }
46         private bool soundSystemAvailable = false;
47         private Thread soundThread;
48         private Thread listenerThread;
49         RadegastInstance instance;
50
51         private List<MediaObject> sounds = new List<MediaObject>();
52
53         public MediaManager(RadegastInstance instance)
54             : base()
55         {
56             this.instance = instance;
57             manager = this;
58
59             // Start the background thread that does all the FMOD calls.
60             soundThread = new Thread(new ThreadStart(CommandLoop));
61             soundThread.IsBackground = true;
62             soundThread.Name = "SoundThread";
63             soundThread.Start();
64
65             // Start the background thread that updates listerner position.
66             listenerThread = new Thread(new ThreadStart(ListenerUpdate));
67             listenerThread.IsBackground = true;
68             listenerThread.Name = "ListenerThread";
69             listenerThread.Start();
70         }
71
72         private void CommandLoop()
73         {
74             SoundDelegate action = null;
75
76             UpVector.x = 0.0f;
77             UpVector.y = 1.0f;
78             UpVector.z = 0.0f;
79             ZeroVector.x = 0.0f;
80             ZeroVector.y = 0.0f;
81             ZeroVector.z = 0.0f;
82
83             allSounds = new Dictionary<IntPtr,MediaObject>();
84             allChannels = new Dictionary<IntPtr, MediaObject>();
85
86             InitFMOD();
87             if (!this.soundSystemAvailable) return;
88
89             // Initialize the command queue.
90             queue = new Queue<SoundDelegate>();
91
92             while (true)
93             {
94                 // Wait for something to show up in the queue.
95                 lock (queue)
96                 {
97                     while (queue.Count == 0)
98                     {
99                         Monitor.Wait(queue);
100                     }
101                     action = queue.Dequeue();
102                 }
103
104                 // We have an action, so call it.
105                 try
106                 {
107                     action();
108                     action = null;
109                 }
110                 catch (Exception e)
111                 {
112                     Logger.Log("Error in sound action: " + e.Message,
113                         Helpers.LogLevel.Error);
114                 }
115             }
116         }
117
118         /// <summary>
119         /// Initialize the FMOD sound system.
120         /// </summary>
121         private void InitFMOD()
122         {
123             try
124             {
125                 FMODExec(FMOD.Factory.System_Create(ref system));
126                 uint version = 0;
127                 FMODExec(system.getVersion(ref version));
128
129                 if (version < FMOD.VERSION.number)
130                     throw new MediaException("You are using an old version of FMOD " +
131                         version.ToString("X") +
132                         ".  This program requires " +
133                         FMOD.VERSION.number.ToString("X") + ".");
134
135                 // Assume no special hardware capabilities except 5.1 surround sound.
136                 FMOD.CAPS caps = FMOD.CAPS.NONE;
137                 FMOD.SPEAKERMODE speakermode = FMOD.SPEAKERMODE._5POINT1;
138
139                 // Get the capabilities of the driver.
140                 int minfrequency = 0, maxfrequency = 0;
141                 StringBuilder name = new StringBuilder(128);
142                 FMODExec(system.getDriverCaps(0, ref caps,
143                     ref minfrequency,
144                     ref maxfrequency,
145                     ref speakermode)
146                 );
147
148                 // Set FMOD speaker mode to what the driver supports.
149                 FMODExec(system.setSpeakerMode(speakermode));
150
151                 // Forcing the ALSA sound system on Linux seems to avoid a CPU loop
152                 if (System.Environment.OSVersion.Platform == PlatformID.Unix)
153                     FMODExec(system.setOutput(FMOD.OUTPUTTYPE.ALSA));
154
155                 // The user has the 'Acceleration' slider set to off, which
156                 // is really bad for latency.  At 48khz, the latency between
157                 // issuing an fmod command and hearing it will now be about 213ms.
158                 if ((caps & FMOD.CAPS.HARDWARE_EMULATED) == FMOD.CAPS.HARDWARE_EMULATED)
159                 {
160                     FMODExec(system.setDSPBufferSize(1024, 10));
161                 }
162
163                 // Get driver information so we can check for a wierd one.
164                 FMOD.GUID guid = new FMOD.GUID();
165                 FMODExec(system.getDriverInfo(0, name, 128, ref guid));
166
167                 // Sigmatel sound devices crackle for some reason if the format is pcm 16bit.
168                 // pcm floating point output seems to solve it.
169                 if (name.ToString().IndexOf("SigmaTel") != -1)
170                 {
171                     FMODExec(system.setSoftwareFormat(
172                         48000,
173                         FMOD.SOUND_FORMAT.PCMFLOAT,
174                         0, 0,
175                         FMOD.DSP_RESAMPLER.LINEAR)
176                     );
177                 }
178
179                 // Try to initialize with all those settings, and Max 32 channels.
180                 FMOD.RESULT result = system.init(32, FMOD.INITFLAG.NORMAL, (IntPtr)null);
181                 if (result == FMOD.RESULT.ERR_OUTPUT_CREATEBUFFER)
182                 {
183                     // Can not handle surround sound - back to Stereo.
184                     FMODExec(system.setSpeakerMode(FMOD.SPEAKERMODE.STEREO));
185
186                     // And init again.
187                     FMODExec(system.init(
188                         32,
189                         FMOD.INITFLAG.NORMAL,
190                         (IntPtr)null)
191                     );
192                 }
193
194                 // Set real-world effect scales.
195                 FMODExec(system.set3DSettings(
196                     1.0f,   // Doppler scale
197                     1.0f,   // Distance scale is meters
198                     1.0f)   // Rolloff factor
199                 );
200
201                 soundSystemAvailable = true;
202                 Logger.Log("Initialized FMOD Ex", Helpers.LogLevel.Debug);
203             }
204             catch (Exception ex)
205             {
206                 Logger.Log("Failed to initialize the sound system: ", Helpers.LogLevel.Warning, ex);
207             }
208         }
209
210         public override void Dispose()
211         {
212             lock (sounds)
213             {
214                 for (int i = 0; i < sounds.Count; i++)
215                 {
216                     if (!sounds[i].Disposed)
217                         sounds[i].Dispose();
218                 }
219                 sounds.Clear();
220             }
221
222             sounds = null;
223
224             if (system != null)
225             {
226                 Logger.Log("FMOD interface stopping", Helpers.LogLevel.Info);
227                 system.release();
228                 system = null;
229             }
230
231             base.Dispose();
232         }
233
234         /// <summary>
235         /// Thread to update listener position and generally keep
236         /// FMOD up to date.
237         /// </summary>
238         private void ListenerUpdate()
239         {
240             Vector3 lastpos = new Vector3(0.0f, 0.0f, 0.0f );
241             float lastface = 0.0f;
242
243             while (true)
244             {
245                 Thread.Sleep(500);
246
247                 if (system == null) continue;
248
249                 AgentManager my = instance.Client.Self;
250
251                 // If we are standing still, nothing to update now, but
252                 // FMOD needs a 'tick' anyway for callbacks, etc.  In looping
253                 // 'game' programs, the loop is the 'tick'.   Since Radegast
254                 // uses events and has no loop, we use this position update
255                 // thread to drive the tick.
256                 if (my.SimPosition.Equals(lastpos) &&
257                     my.Movement.BodyRotation.W == lastface)
258                 {
259                     invoke(new SoundDelegate(delegate
260                     {
261                         FMODExec(system.update());
262                     }));
263                     continue;
264                 }
265
266                 lastpos = my.SimPosition;
267                 lastface = my.Movement.BodyRotation.W;
268
269                 // Convert coordinate spaces.
270                 FMOD.VECTOR listenerpos = FromOMVSpace(my.SimPosition);
271
272                 // Get azimuth from the facing Quaternion.  Note we assume the
273                 // avatar is standing upright.  Avatars in unusual positions
274                 // hear things from unpredictable directions.
275                 // By definition, facing.W = Cos( angle/2 )
276                 double angle = 2.0 * Math.Acos(my.Movement.BodyRotation.W);
277                 FMOD.VECTOR forward = new FMOD.VECTOR();
278                 forward.x = (float)Math.Asin(angle);
279                 forward.y = 0.0f;
280                 forward.z = (float)Math.Acos(angle);
281
282                 // Tell FMOD the new orientation.
283                 invoke( new SoundDelegate( delegate
284                 {
285                     FMODExec( system.set3DListenerAttributes(
286                         0,
287                         ref listenerpos,        // Position
288                         ref ZeroVector,         // Velocity
289                         ref forward,            // Facing direction
290                         ref UpVector ));        // Top of head
291
292                     FMODExec(system.update());
293                 }));
294             }
295         }
296
297     }
298
299     public class MediaException : Exception
300     {
301         public MediaException(string msg)
302             : base(msg)
303         {
304         }
305     }
306 }