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 RadegastInstance instance;
50 private List<MediaObject> sounds = new List<MediaObject>();
52 public MediaManager(RadegastInstance instance)
55 this.instance = instance;
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";
64 private void CommandLoop()
75 allSounds = new Dictionary<IntPtr,MediaObject>();
76 allChannels = new Dictionary<IntPtr, MediaObject>();
79 if (!this.soundSystemAvailable) return;
81 // Initialize the command queue.
82 queue = new Queue<SoundDelegate>();
88 // Wait for something to show up in the queue.
91 while (queue.Count == 0)
96 action = queue.Dequeue();
99 // We have an action, so call it.
105 System.Console.WriteLine("Sound shutdown " + e.Message);
110 /// Initialize the FMOD sound system.
112 private void InitFMOD()
116 FMODExec(FMOD.Factory.System_Create(ref system));
118 FMODExec(system.getVersion(ref version));
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") + ".");
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;
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,
138 // Set FMOD speaker mode to what the driver supports.
139 FMODExec(system.setSpeakerMode(speakermode));
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));
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)
150 FMODExec(system.setDSPBufferSize(1024, 10));
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));
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)
161 FMODExec(system.setSoftwareFormat(
163 FMOD.SOUND_FORMAT.PCMFLOAT,
165 FMOD.DSP_RESAMPLER.LINEAR)
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)
173 // Can not handle surround sound - back to Stereo.
174 FMODExec(system.setSpeakerMode(FMOD.SPEAKERMODE.STEREO));
177 FMODExec(system.init(
179 FMOD.INITFLAG.NORMAL,
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
191 soundSystemAvailable = true;
192 Logger.Log("Initialized FMOD Ex", Helpers.LogLevel.Debug);
196 Logger.Log("Failed to initialize the sound system: ", Helpers.LogLevel.Warning, ex);
200 public override void Dispose()
204 for (int i = 0; i < sounds.Count; i++)
206 if (!sounds[i].Disposed)
224 /// Thread to update listener position and generally keep
227 private void ListenerUpdate()
229 Vector3 lastpos = new Vector3(0.0f, 0.0f, 0.0f );
230 float lastface = 0.0f;
236 if (system == null) continue;
238 AgentManager my = instance.Client.Self;
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)
248 invoke(new SoundDelegate(delegate
255 lastpos = my.SimPosition;
256 lastface = my.Movement.BodyRotation.W;
258 // Convert coordinate spaces.
259 FMOD.VECTOR listenerpos = FromOMVSpace(my.SimPosition);
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);
269 forward.z = (float)Math.Acos(angle);
271 // Tell FMOD the new orientation.
272 invoke( new SoundDelegate( delegate
274 FMODExec( system.set3DListenerAttributes(
276 ref listenerpos, // Position
277 ref ZeroVector, // Velocity
278 ref forward, // Facing direction
279 ref UpVector )); // Top of head
288 public class MediaException : Exception
290 public MediaException(string msg)