OSDN Git Service

Un-disable volume control for inworld sounds. It seems to be working now
[radegast/radegast.git] / Radegast / Core / Media / MediaManager.cs
index a6f2f94..b4e1133 100644 (file)
@@ -50,6 +50,7 @@ namespace Radegast.Media
         public RadegastInstance Instance;
 
         private List<MediaObject> sounds = new List<MediaObject>();
+        ManualResetEvent initDone = new ManualResetEvent(false);
 
         public MediaManager(RadegastInstance instance)
             : base()
@@ -57,9 +58,10 @@ namespace Radegast.Media
             this.Instance = instance;
             manager = this;
 
+
             loadCallback = new FMOD.SOUND_NONBLOCKCALLBACK(DispatchNonBlockCallback);
             endCallback = new FMOD.CHANNEL_CALLBACK(DispatchEndCallback);
-            allBuffers = new LinkedList<BufferSound>();
+            allBuffers = new Dictionary<UUID, BufferSound>();
 
             // Start the background thread that does all the FMOD calls.
             soundThread = new Thread(new ThreadStart(CommandLoop));
@@ -73,16 +75,19 @@ namespace Radegast.Media
             listenerThread.Name = "ListenerThread";
             listenerThread.Start();
 
-            // Subscribe to events about inworld sounds
-            instance.Client.Sound.SoundTrigger += new EventHandler<SoundTriggerEventArgs>(Sound_SoundTrigger);
-            instance.Client.Sound.AttachedSound += new EventHandler<AttachedSoundEventArgs>(Sound_AttachedSound);
-            instance.Client.Sound.PreloadSound += new EventHandler<PreloadSoundEventArgs>(Sound_PreloadSound);
+            // Wait for init to complete
+            initDone.WaitOne();
+            initDone = null;
         }
 
+        /// <summary>
+        /// Thread that processes FMOD calls.
+        /// </summary>
         private void CommandLoop()
         {
             SoundDelegate action = null;
 
+            // Initialze a bunch of static values
             UpVector.x = 0.0f;
             UpVector.y = 1.0f;
             UpVector.z = 0.0f;
@@ -90,15 +95,17 @@ namespace Radegast.Media
             ZeroVector.y = 0.0f;
             ZeroVector.z = 0.0f;
 
-            allSounds = new Dictionary<IntPtr,MediaObject>();
+            allSounds = new Dictionary<IntPtr, MediaObject>();
             allChannels = new Dictionary<IntPtr, MediaObject>();
 
-            InitFMOD();
-            if (!this.soundSystemAvailable) return;
-
             // Initialize the command queue.
             queue = new Queue<SoundDelegate>();
 
+            // Initialize the FMOD sound package
+            InitFMOD();
+            initDone.Set();
+            if (!this.soundSystemAvailable) return;
+
             while (true)
             {
                 // Wait for something to show up in the queue.
@@ -119,7 +126,7 @@ namespace Radegast.Media
                 }
                 catch (Exception e)
                 {
-                    Logger.Log("Error in sound action: " + e.Message,
+                    Logger.Log("Error in sound action:\n    " + e.Message + "\n" + e.StackTrace,
                         Helpers.LogLevel.Error);
                 }
             }
@@ -238,6 +245,20 @@ namespace Radegast.Media
                 system = null;
             }
 
+            if (listenerThread != null)
+            {
+                if (listenerThread.IsAlive)
+                    listenerThread.Abort();
+                listenerThread = null;
+            }
+
+            if (soundThread != null)
+            {
+                if (soundThread.IsAlive)
+                    soundThread.Abort();
+                soundThread = null;
+            }
+
             base.Dispose();
         }
 
@@ -247,24 +268,30 @@ namespace Radegast.Media
         /// </summary>
         private void ListenerUpdate()
         {
-            Vector3 lastpos = new Vector3(0.0f, 0.0f, 0.0f );
+            // Notice changes in position or direction.
+            Vector3 lastpos = new Vector3(0.0f, 0.0f, 0.0f);
             float lastface = 0.0f;
 
             while (true)
             {
+                // Two updates per second.
                 Thread.Sleep(500);
 
                 if (system == null) continue;
 
                 AgentManager my = Instance.Client.Self;
+                Vector3 newPosition = new Vector3(my.SimPosition);
+                float newFace = my.SimRotation.W;
 
                 // If we are standing still, nothing to update now, but
                 // FMOD needs a 'tick' anyway for callbacks, etc.  In looping
                 // 'game' programs, the loop is the 'tick'.   Since Radegast
                 // uses events and has no loop, we use this position update
-                // thread to drive the FMOD tick.
-                if (my.SimPosition.Equals(lastpos) &&
-                    my.Movement.BodyRotation.W == lastface)
+                // thread to drive the FMOD tick.  Have to move more than
+                // 500mm or turn more than 10 desgrees to bother with.
+                //
+                if (newPosition.ApproxEquals(lastpos, 0.5f) &&
+                    Math.Abs(newFace - lastface) < 0.2)
                 {
                     invoke(new SoundDelegate(delegate
                     {
@@ -273,76 +300,303 @@ namespace Radegast.Media
                     continue;
                 }
 
-                lastpos = my.SimPosition;
-                lastface = my.Movement.BodyRotation.W;
+                // We have moved or turned.  Remember new position.
+                lastpos = newPosition;
+                lastface = newFace;
 
                 // Convert coordinate spaces.
-                FMOD.VECTOR listenerpos = FromOMVSpace(my.SimPosition);
+                FMOD.VECTOR listenerpos = FromOMVSpace(newPosition);
 
                 // Get azimuth from the facing Quaternion.  Note we assume the
                 // avatar is standing upright.  Avatars in unusual positions
                 // hear things from unpredictable directions.
                 // By definition, facing.W = Cos( angle/2 )
-                double angle = 2.0 * Math.Acos(my.Movement.BodyRotation.W);
+                // With angle=0 meaning East.
+                double angle = 2.0 * Math.Acos(newFace);
+
+                // Construct facing unit vector in FMOD coordinates.
+                // Z is East, X is South, Y is up.
                 FMOD.VECTOR forward = new FMOD.VECTOR();
-                forward.x = (float)Math.Asin(angle);
+                forward.x = (float)Math.Sin(angle); // South
                 forward.y = 0.0f;
-                forward.z = (float)Math.Acos(angle);
+                forward.z = (float)Math.Cos(angle); // East
+
+                Logger.Log(
+                    String.Format(
+                        "Standing at <{0:0.0},{1:0.0},{2:0.0}> facing {3:d}",
+                            listenerpos.x,
+                            listenerpos.y,
+                            listenerpos.z,
+                            (int)(angle * 180.0 / 3.141592)),
+                    Helpers.LogLevel.Debug);
 
                 // Tell FMOD the new orientation.
-                invoke( new SoundDelegate( delegate
+                invoke(new SoundDelegate(delegate
                 {
-                    FMODExec( system.set3DListenerAttributes(
+                    FMODExec(system.set3DListenerAttributes(
                         0,
                         ref listenerpos,       // Position
                         ref ZeroVector,                // Velocity
                         ref forward,           // Facing direction
-                        ref UpVector ));       // Top of head
+                        ref UpVector));        // Top of head
 
                     FMODExec(system.update());
                 }));
             }
         }
 
-        /**
-         * Handle triggering a sound to play
-         */
+        /// <summary>
+        /// Handle request to play a sound, which might (or mioght not) have been preloaded.
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
         private void Sound_SoundTrigger(object sender, SoundTriggerEventArgs e)
         {
+            if (e.SoundID == UUID.Zero) return;
+
+            Logger.Log("Trigger sound " + e.SoundID.ToString() +
+                " in object " + e.ObjectID.ToString(),
+                Helpers.LogLevel.Debug);
+
             new BufferSound(
+                e.ObjectID,
                 e.SoundID,
                 false,
                 true,
                 e.Position,
-                e.Gain);
+                e.Gain * ObjectVolume);
         }
 
+        /// <summary>
+        /// Handle sound attached to an object
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
         private void Sound_AttachedSound(object sender, AttachedSoundEventArgs e)
         {
-          
+            // This event tells us the Object ID, but not the Prim info directly.
+            // So we look it up in our internal Object memory.
+            Simulator sim = e.Simulator;
+            Primitive p = sim.ObjectsPrimitives.Find((Primitive p2) => { return p2.ID == e.ObjectID; });
+            if (p == null) return;
+
+            // Only one attached sound per prim, so we kill any previous
+            BufferSound.Kill(p.ID);
+
+            // If this is stop sound, we're done since we've already killed sound for this object
+            if ((e.Flags & SoundFlags.Stop) == SoundFlags.Stop)
+            {
+                return;
+            }
+
+            // We seem to get a lot of these zero sounds.
+            if (e.SoundID == UUID.Zero) return;
+
+            // If this is a child prim, its position is relative to the root.
+            Vector3 fullPosition = p.Position;
+
+            while (p != null && p.ParentID != 0)
+            {
+                Avatar av;
+                if (sim.ObjectsAvatars.TryGetValue(p.ParentID, out av))
+                {
+                    p = av;
+                    fullPosition += p.Position;
+                }
+                else
+                {
+                    if (sim.ObjectsPrimitives.TryGetValue(p.ParentID, out p))
+                    {
+                        fullPosition += p.Position;
+                    }
+                }
+            }
+
+            // Didn't find root prim
+            if (p == null) return;
+
+            new BufferSound(
+                e.ObjectID,
+                e.SoundID,
+                (e.Flags & SoundFlags.Loop) == SoundFlags.Loop,
+                true,
+                fullPosition,
+                e.Gain * ObjectVolume);
         }
 
-        /**
-         * Handle request to preload a sound resource.
-         */
+
+        /// <summary>
+        /// Handle request to preload a sound for playing later.
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
         private void Sound_PreloadSound(object sender, PreloadSoundEventArgs e)
         {
-            new BufferSound( e.SoundID );
+            if (e.SoundID == UUID.Zero) return;
+
+            if (!Instance.Client.Assets.Cache.HasAsset(e.SoundID))
+                new BufferSound(e.SoundID);
         }
 
+        /// <summary>
+        /// Handle object updates, looking for sound events
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        private void Objects_ObjectUpdate(object sender, PrimEventArgs e)
+        {
+            HandleObjectSound(e.Prim, e.Simulator);
+        }
 
-    }
+        /// <summary>
+        /// Handle deletion of a noise-making object
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        void Objects_KillObject(object sender, KillObjectEventArgs e)
+        {
+            Primitive p = null;
+            if (!e.Simulator.ObjectsPrimitives.TryGetValue(e.ObjectLocalID, out  p)) return;
 
-    public class PlayingSound
-    {
-        private UUID Id;
-        private Vector3 position;
-        private float volume;
+            // Objects without sounds are not interesting.
+            if (p.Sound == null) return;
+            if (p.Sound == UUID.Zero) return;
+
+            BufferSound.Kill(p.ID);
+        }
+
+        /// <summary>
+        /// Common object sound processing for various Update events
+        /// </summary>
+        /// <param name="p"></param>
+        /// <param name="s"></param>
+        private void HandleObjectSound(Primitive p, Simulator s)
+        {
+            // Objects without sounds are not interesting.
+            if (p.Sound == UUID.Zero) return;
+
+            if ((p.SoundFlags & SoundFlags.Stop) == SoundFlags.Stop)
+            {
+                BufferSound.Kill(p.ID);
+                return;
+            }
+
+            // If this is a child prim, its position is relative to the root prim.
+            Vector3 fullPosition = p.Position;
+            if (p.ParentID != 0)
+            {
+                Primitive parentP;
+                if (!s.ObjectsPrimitives.TryGetValue(p.ParentID, out parentP)) return;
+                fullPosition += parentP.Position;
+            }
+
+            // See if this is an update to  something we already know about.
+            if (allBuffers.ContainsKey(p.ID))
+            {
+                // Exists already, so modify existing sound.
+                BufferSound snd = allBuffers[p.ID];
+                snd.Volume = p.SoundGain * ObjectVolume;
+                snd.Position = fullPosition;
+            }
+            else
+            {
+                // Does not exist, so create a new one.
+                new BufferSound(
+                    p.ID,
+                    p.Sound,
+                    (p.SoundFlags & SoundFlags.Loop) == SoundFlags.Loop,
+                    true,
+                    fullPosition, //Instance.State.GlobalPosition(e.Simulator, fullPosition),
+                    p.SoundGain * ObjectVolume);
+            }
+        }
+
+        /// <summary>
+        /// Control the volume of all inworld sounds
+        /// </summary>
+        public float ObjectVolume
+        {
+            set
+            {
+                AllObjectVolume = value;
+                BufferSound.AdjustVolumes();
+            }
+            get { return AllObjectVolume; }
+        }
 
-        public PlayingSound( UUID soundId )
+        private bool m_objectEnabled = true;
+        /// <summary>
+        /// Enable and Disable inworld sounds
+        /// </summary>
+        public bool ObjectEnable
         {
-            Id = soundId;
+            set
+            {
+                try
+                {
+                    if (value)
+                    {
+                        // Subscribe to events about inworld sounds
+                        Instance.Client.Sound.SoundTrigger += new EventHandler<SoundTriggerEventArgs>(Sound_SoundTrigger);
+                        Instance.Client.Sound.AttachedSound += new EventHandler<AttachedSoundEventArgs>(Sound_AttachedSound);
+                        Instance.Client.Sound.PreloadSound += new EventHandler<PreloadSoundEventArgs>(Sound_PreloadSound);
+                        Instance.Client.Objects.ObjectUpdate += new EventHandler<PrimEventArgs>(Objects_ObjectUpdate);
+                        Instance.Client.Objects.KillObject += new EventHandler<KillObjectEventArgs>(Objects_KillObject);
+                        Instance.Client.Network.SimChanged += new EventHandler<SimChangedEventArgs>(Network_SimChanged);
+                        Instance.Client.Self.ChatFromSimulator += new EventHandler<ChatEventArgs>(Self_ChatFromSimulator);
+                        Logger.Log("Inworld sound enabled", Helpers.LogLevel.Info);
+                    }
+                    else
+                    {
+                        // Subscribe to events about inworld sounds
+                        Instance.Client.Sound.SoundTrigger -= new EventHandler<SoundTriggerEventArgs>(Sound_SoundTrigger);
+                        Instance.Client.Sound.AttachedSound -= new EventHandler<AttachedSoundEventArgs>(Sound_AttachedSound);
+                        Instance.Client.Sound.PreloadSound -= new EventHandler<PreloadSoundEventArgs>(Sound_PreloadSound);
+                        Instance.Client.Objects.ObjectUpdate -= new EventHandler<PrimEventArgs>(Objects_ObjectUpdate);
+                        Instance.Client.Objects.KillObject -= new EventHandler<KillObjectEventArgs>(Objects_KillObject);
+                        Instance.Client.Network.SimChanged -= new EventHandler<SimChangedEventArgs>(Network_SimChanged);
+                        Instance.Client.Self.ChatFromSimulator -= new EventHandler<ChatEventArgs>(Self_ChatFromSimulator);
+                        // Stop all running sounds
+                        BufferSound.KillAll();
+
+                        Logger.Log("Inworld sound disabled", Helpers.LogLevel.Info);
+                    }
+                }
+                catch (Exception e)
+                {
+                    System.Console.WriteLine("Error on enable/disable: " + e.Message);
+                }
+
+                m_objectEnabled = value;
+            }
+            get { return m_objectEnabled; }
         }
+
+        void Self_ChatFromSimulator(object sender, ChatEventArgs e)
+        {
+            if (e.Type == ChatType.StartTyping)
+            {
+                new BufferSound(
+                    UUID.Random(),
+                    new UUID("5e191c7b-8996-9ced-a177-b2ac32bfea06"),
+                    false,
+                    true,
+                    e.Position,
+                    ObjectVolume / 2f);
+            }
+        }
+
+        /// <summary>
+        /// Watch for Teleports to cancel all the old sounds
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        void Network_SimChanged(object sender, SimChangedEventArgs e)
+        {
+            BufferSound.KillAll();
+        }
+
+
     }
 
     public class MediaException : Exception