OSDN Git Service

Provide a 'tick' to FMOD so callbacks will work.
[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         RadegastInstance instance;
49
50         private List<MediaObject> sounds = new List<MediaObject>();
51
52         public MediaManager(RadegastInstance instance)
53             : base()
54         {
55             this.instance = instance;
56
57             // Start the background thread that does all the FMOD calls.
58             soundThread = new Thread(new ThreadStart(CommandLoop));
59             soundThread.IsBackground = true;
60             soundThread.Name = "SoundThread";
61             soundThread.Start();
62         }
63
64         private void CommandLoop()
65         {
66             SoundDelegate action;
67
68             UpVector.x = 0.0f;
69             UpVector.y = 1.0f;
70             UpVector.z = 0.0f;
71             ZeroVector.x = 0.0f;
72             ZeroVector.y = 0.0f;
73             ZeroVector.z = 0.0f;
74
75             allSounds = new Dictionary<IntPtr,MediaObject>();
76             allChannels = new Dictionary<IntPtr, MediaObject>();
77
78             InitFMOD();
79             if (!this.soundSystemAvailable) return;
80
81             // Initialize the command queue.
82             queue = new Queue<SoundDelegate>();
83
84             try
85             {
86                 while (true)
87                 {
88                     // Wait for something to show up in the queue.
89                     lock (queue)
90                     {
91                         while (queue.Count == 0)
92                         {
93                             Monitor.Wait(queue);
94                         }
95
96                         action = queue.Dequeue();
97                     }
98
99                     // We have an action, so call it.
100                     action();
101                 }
102             }
103             catch (Exception e)
104             {
105                 System.Console.WriteLine("Sound shutdown " + e.Message);
106             }
107         }
108
109         /// <summary>
110         /// Initialize the FMOD sound system.
111         /// </summary>
112         private void InitFMOD()
113         {
114             try
115             {
116                 FMODExec(FMOD.Factory.System_Create(ref system));
117                 uint version = 0;
118                 FMODExec(system.getVersion(ref version));
119
120                 if (version < FMOD.VERSION.number)
121                     throw new MediaException("You are using an old version of FMOD " +
122                         version.ToString("X") +
123                         ".  This program requires " +
124                         FMOD.VERSION.number.ToString("X") + ".");
125
126                 // Assume no special hardware capabilities except 5.1 surround sound.
127                 FMOD.CAPS caps = FMOD.CAPS.NONE;
128                 FMOD.SPEAKERMODE speakermode = FMOD.SPEAKERMODE._5POINT1;
129
130                 // Get the capabilities of the driver.
131                 int minfrequency = 0, maxfrequency = 0;
132                 StringBuilder name = new StringBuilder(128);
133                 FMODExec(system.getDriverCaps(0, ref caps,
134                     ref minfrequency, ref maxfrequency,
135                     ref speakermode)
136                 );
137
138                 // Set FMOD speaker mode to what the driver supports.
139                 FMODExec(system.setSpeakerMode(speakermode));
140
141                 // Forcing the ALSA sound system on Linux seems to avoid a CPU loop
142                 if (System.Environment.OSVersion.Platform == PlatformID.Unix)
143                     FMODExec(system.setOutput(FMOD.OUTPUTTYPE.ALSA));
144
145                 // The user has the 'Acceleration' slider set to off, which
146                 // is really bad for latency.  At 48khz, the latency between
147                 // issuing an fmod command and hearing it will now be about 213ms.
148                 if ((caps & FMOD.CAPS.HARDWARE_EMULATED) == FMOD.CAPS.HARDWARE_EMULATED)
149                 {
150                     FMODExec(system.setDSPBufferSize(1024, 10));
151                 }
152
153                 // Get driver information so we can check for a wierd one.
154                 FMOD.GUID guid = new FMOD.GUID();
155                 FMODExec(system.getDriverInfo(0, name, 128, ref guid));
156
157                 // Sigmatel sound devices crackle for some reason if the format is pcm 16bit.
158                 // pcm floating point output seems to solve it.
159                 if (name.ToString().IndexOf("SigmaTel") != -1)
160                 {
161                     FMODExec(system.setSoftwareFormat(
162                         48000,
163                         FMOD.SOUND_FORMAT.PCMFLOAT,
164                         0, 0,
165                         FMOD.DSP_RESAMPLER.LINEAR)
166                     );
167                 }
168
169                 // Try to initialize with all those settings, and Max 32 channels.
170                 FMOD.RESULT result = system.init(32, FMOD.INITFLAG.NORMAL, (IntPtr)null);
171                 if (result == FMOD.RESULT.ERR_OUTPUT_CREATEBUFFER)
172                 {
173                     // Can not handle surround sound - back to Stereo.
174                     FMODExec(system.setSpeakerMode(FMOD.SPEAKERMODE.STEREO));
175
176                     // And init again.
177                     FMODExec(system.init(
178                         32,
179                         FMOD.INITFLAG.NORMAL,
180                         (IntPtr)null)
181                     );
182                 }
183
184                 // Set real-world effect scales.
185                 FMODExec(system.set3DSettings(
186                     1.0f,   // Doppler scale
187                     1.0f,   // Distance scale is meters
188                     1.0f)   // Rolloff factor
189                 );
190
191                 soundSystemAvailable = true;
192                 Logger.Log("Initialized FMOD Ex", Helpers.LogLevel.Debug);
193             }
194             catch (Exception ex)
195             {
196                 Logger.Log("Failed to initialize the sound system: ", Helpers.LogLevel.Warning, ex);
197             }
198         }
199
200         public override void Dispose()
201         {
202             lock (sounds)
203             {
204                 for (int i = 0; i < sounds.Count; i++)
205                 {
206                     if (!sounds[i].Disposed)
207                         sounds[i].Dispose();
208                 }
209                 sounds.Clear();
210             }
211
212             sounds = null;
213
214             if (system != null)
215             {
216                 system.release();
217                 system = null;
218             }
219
220             base.Dispose();
221         }
222
223         /// <summary>
224         /// Thread to update listener position and generally keep
225         /// FMOD up to date.
226         /// </summary>
227         private void ListenerUpdate()
228         {
229             Vector3 lastpos = new Vector3(0.0f, 0.0f, 0.0f );
230             float lastface = 0.0f;
231
232             while (true)
233             {
234                 Thread.Sleep(500);
235
236                 if (system == null) continue;
237
238                 AgentManager my = instance.Client.Self;
239
240                 // If we are standing still, nothing to update now, but
241                 // FMOD needs a 'tick' anyway for callbacks, etc.  In looping
242                 // 'game' programs, the loop is the 'tick'.   Since Radegast
243                 // uses events and has no loop, we use this position update
244                 // thread to drive the tick.
245                 if (my.SimPosition.Equals(lastpos) &&
246                     my.Movement.BodyRotation.W == lastface)
247                 {
248                     invoke(new SoundDelegate(delegate
249                     {
250                         system.update();
251                     }));
252                     continue;
253                 }
254
255                 lastpos = my.SimPosition;
256                 lastface = my.Movement.BodyRotation.W;
257
258                 // Convert coordinate spaces.
259                 FMOD.VECTOR listenerpos = FromOMVSpace(my.SimPosition);
260
261                 // Get azimuth from the facing Quaternion.  Note we assume the
262                 // avatar is standing upright.  Avatars in unusual positions
263                 // hear things from unpredictable directions.
264                 // By definition, facing.W = Cos( angle/2 )
265                 double angle = 2.0 * Math.Acos(my.Movement.BodyRotation.W);
266                 FMOD.VECTOR forward = new FMOD.VECTOR();
267                 forward.x = (float)Math.Asin(angle);
268                 forward.y = 0.0f;
269                 forward.z = (float)Math.Acos(angle);
270
271                 // Tell FMOD the new orientation.
272                 invoke( new SoundDelegate( delegate
273                 {
274                     FMODExec( system.set3DListenerAttributes(
275                         0,
276                         ref listenerpos,        // Position
277                         ref ZeroVector,         // Velocity
278                         ref forward,            // Facing direction
279                         ref UpVector ));        // Top of head
280
281                     system.update();
282                 }));
283             }
284         }
285
286     }
287
288     public class MediaException : Exception
289     {
290         public MediaException(string msg)
291             : base(msg)
292         {
293         }
294     }
295 }