2 // Radegast Metaverse Client
3 // Copyright (c) 2009, Radegast Development Team
4 // All rights reserved.
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are met:
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.
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.
32 using System.Collections.Generic;
35 using System.Threading;
38 namespace Radegast.Media
40 public class MediaManager : MediaObject
43 /// Indicated wheather spund sytem is ready for use
45 public bool SoundSystemAvailable { get { return soundSystemAvailable; } }
46 private bool soundSystemAvailable = false;
47 private Thread soundThread;
48 private Thread listenerThread;
49 RadegastInstance instance;
51 private List<MediaObject> sounds = new List<MediaObject>();
53 public MediaManager(RadegastInstance instance)
56 this.instance = instance;
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";
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();
72 private void CommandLoop()
74 SoundDelegate action = null;
83 allSounds = new Dictionary<IntPtr,MediaObject>();
84 allChannels = new Dictionary<IntPtr, MediaObject>();
87 if (!this.soundSystemAvailable) return;
89 // Initialize the command queue.
90 queue = new Queue<SoundDelegate>();
94 // Wait for something to show up in the queue.
97 while (queue.Count == 0)
101 action = queue.Dequeue();
104 // We have an action, so call it.
112 Logger.Log("Error in sound action: " + e.Message,
113 Helpers.LogLevel.Error);
119 /// Initialize the FMOD sound system.
121 private void InitFMOD()
125 FMODExec(FMOD.Factory.System_Create(ref system));
127 FMODExec(system.getVersion(ref version));
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") + ".");
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;
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,
148 // Set FMOD speaker mode to what the driver supports.
149 FMODExec(system.setSpeakerMode(speakermode));
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));
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)
160 FMODExec(system.setDSPBufferSize(1024, 10));
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));
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)
171 FMODExec(system.setSoftwareFormat(
173 FMOD.SOUND_FORMAT.PCMFLOAT,
175 FMOD.DSP_RESAMPLER.LINEAR)
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)
183 // Can not handle surround sound - back to Stereo.
184 FMODExec(system.setSpeakerMode(FMOD.SPEAKERMODE.STEREO));
187 FMODExec(system.init(
189 FMOD.INITFLAG.NORMAL,
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
201 soundSystemAvailable = true;
202 Logger.Log("Initialized FMOD Ex", Helpers.LogLevel.Debug);
206 Logger.Log("Failed to initialize the sound system: ", Helpers.LogLevel.Warning, ex);
210 public override void Dispose()
214 for (int i = 0; i < sounds.Count; i++)
216 if (!sounds[i].Disposed)
226 Logger.Log("FMOD interface stopping", Helpers.LogLevel.Info);
235 /// Thread to update listener position and generally keep
238 private void ListenerUpdate()
240 Vector3 lastpos = new Vector3(0.0f, 0.0f, 0.0f );
241 float lastface = 0.0f;
247 if (system == null) continue;
249 AgentManager my = instance.Client.Self;
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)
259 invoke(new SoundDelegate(delegate
261 FMODExec(system.update());
266 lastpos = my.SimPosition;
267 lastface = my.Movement.BodyRotation.W;
269 // Convert coordinate spaces.
270 FMOD.VECTOR listenerpos = FromOMVSpace(my.SimPosition);
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);
280 forward.z = (float)Math.Acos(angle);
282 // Tell FMOD the new orientation.
283 invoke( new SoundDelegate( delegate
285 FMODExec( system.set3DListenerAttributes(
287 ref listenerpos, // Position
288 ref ZeroVector, // Velocity
289 ref forward, // Facing direction
290 ref UpVector )); // Top of head
292 FMODExec(system.update());
299 public class MediaException : Exception
301 public MediaException(string msg)