OSDN Git Service

198aada3b8ccc680a9b8a89e5a6fff48248478c9
[radegast/radegast.git] / Radegast / Core / Media / BufferSound.cs
1 // 
2 // Radegast Metaverse Client
3 // Copyright (c) 2009-2013, 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: Sound.cs 502 2010-03-14 23:13:46Z latifer $
30 //
31 // Uncomment this to get lots more logging
32 //#define TRACE_SOUND
33 using System;
34 using System.Runtime.InteropServices;
35 using System.Collections.Generic;
36 using FMOD;
37 using OpenMetaverse;
38 using OpenMetaverse.Assets;
39 using System.Threading;
40
41 namespace Radegast.Media
42 {
43
44     public class BufferSound : MediaObject
45     {
46         private UUID Id;
47         private UUID ContainerId;
48         private Boolean prefetchOnly = false;
49         private FMOD.MODE mode;
50         public Sound Sound { get { return sound; } }
51         private Boolean loopSound = false;
52         /// <summary>
53         /// The individual volume setting for THIS object
54         /// </summary>
55         private float volumeSetting = 0.5f;
56
57         /// <summary>
58         /// Creates a new sound object
59         /// </summary>
60         /// <param name="system">Sound system</param>
61         public BufferSound(UUID objectId, UUID soundId, bool loop, bool global, Vector3 worldpos, float vol)
62             : base()
63         {
64             InitBuffer(objectId, soundId, loop, global, worldpos, vol);
65         }
66
67         public BufferSound(UUID objectId, UUID soundId, bool loop, bool global, Vector3d worldpos, float vol)
68             : base()
69         {
70             InitBuffer(objectId, soundId, loop, global, new Vector3(worldpos), vol);
71         }
72
73         private void InitBuffer(UUID objectId, UUID soundId, bool loop, bool global, Vector3 worldpos, float vol)
74         {
75             if (manager == null || !manager.SoundSystemAvailable) return;
76
77             // Do not let this get garbage-collected.
78             lock (allBuffers)
79                 allBuffers[objectId] = this;
80
81             ContainerId = objectId;
82             Id = soundId;
83             position = FromOMVSpace(worldpos);
84             volumeSetting = vol;
85             loopSound = loop;
86
87             Logger.Log(
88                 String.Format(
89                     "Playing sound at <{0:0.0},{1:0.0},{2:0.0}> ID {3}",
90                     position.x,
91                     position.y,
92                     position.z,
93                     Id.ToString()),
94                 Helpers.LogLevel.Debug);
95
96             // Set flags to determine how it will be played.
97             mode = FMOD.MODE.SOFTWARE | // Need software processing for all the features
98                 FMOD.MODE._3D |         // Need 3D effects for placement
99                 FMOD.MODE.OPENMEMORY;   // Use sound data in memory
100
101             // Set coordinate space interpretation.
102             if (global)
103                 mode |= FMOD.MODE._3D_WORLDRELATIVE;
104             else
105                 mode |= FMOD.MODE._3D_HEADRELATIVE;
106
107             if (loopSound)
108                 mode |= FMOD.MODE.LOOP_NORMAL;
109
110             // Fetch the sound data.
111             manager.Instance.Client.Assets.RequestAsset(
112                 Id,
113                 AssetType.Sound,
114                 false,
115                 new AssetManager.AssetReceivedCallback(Assets_OnSoundReceived));
116         }
117
118         public static void Kill(UUID id)
119         {
120             if (allBuffers.ContainsKey(id))
121             {
122                 BufferSound bs = allBuffers[id];
123                 bs.StopSound(true);
124             }
125         }
126
127         /// <summary>
128         /// Stop all playing sounds in the environment
129         /// </summary>
130         public static void KillAll()
131         {
132             // Make a list from the dictionary so we do not get a deadlock
133             // on it when removing entries.
134             List<BufferSound> list = new List<BufferSound>(allBuffers.Values);
135
136             foreach (BufferSound s in list)
137             {
138                 s.StopSound();
139             }
140
141             List<MediaObject> objs = new List<MediaObject>(allChannels.Values);
142             foreach (MediaObject obj in objs)
143             {
144                 if (obj is BufferSound)
145                     ((BufferSound)obj).StopSound();
146             }
147         }
148
149         /// <summary>
150         /// Adjust volumes of all playing sounds to observe the new global sound volume
151         /// </summary>
152         public static void AdjustVolumes()
153         {
154             // Make a list from the dictionary so we do not get a deadlock
155             List<BufferSound> list = new List<BufferSound>(allBuffers.Values);
156
157             foreach (BufferSound s in list)
158             {
159                 s.AdjustVolume();
160             }
161         }
162
163         /// <summary>
164         /// Adjust the volume of THIS sound when all are being adjusted.
165         /// </summary>
166         private void AdjustVolume()
167         {
168             Volume = volumeSetting * AllObjectVolume;
169         }
170
171         // A simpler constructor used by PreFetchSound.
172         public BufferSound(UUID soundId)
173             : base()
174         {
175             prefetchOnly = true;
176             ContainerId = UUID.Zero;
177             Id = soundId;
178
179             manager.Instance.Client.Assets.RequestAsset(
180                 Id,
181                 AssetType.Sound,
182                 false,
183                 new AssetManager.AssetReceivedCallback(Assets_OnSoundReceived));
184         }
185
186         /// <summary>
187         /// Releases resources of this sound object
188         /// </summary>
189         public override void Dispose()
190         {
191             base.Dispose();
192         }
193
194         /**
195          * Handle arrival of a sound resource.
196          */
197         void Assets_OnSoundReceived(AssetDownload transfer, Asset asset)
198         {
199             if (transfer.Success)
200             {
201                 // If this was a Prefetch, just stop here.
202                 if (prefetchOnly)
203                 {
204                     return;
205                 }
206
207                 //                Logger.Log("Opening sound " + Id.ToString(), Helpers.LogLevel.Debug);
208
209                 // Decode the Ogg Vorbis buffer.
210                 AssetSound s = asset as AssetSound;
211                 s.Decode();
212                 byte[] data = s.AssetData;
213
214                 // Describe the data to FMOD
215                 extraInfo.length = (uint)data.Length;
216                 extraInfo.cbsize = Marshal.SizeOf(extraInfo);
217
218                 invoke(new SoundDelegate(delegate
219                 {
220                     try
221                     {
222                         // Create an FMOD sound of this Ogg data.
223                         FMODExec(system.createSound(
224                             data,
225                             mode,
226                             ref extraInfo,
227                             ref sound));
228
229                         // Register for callbacks.
230                         RegisterSound(sound);
231
232
233                         // If looping is requested, loop the entire thing.
234                         if (loopSound)
235                         {
236                             uint soundlen = 0;
237                             FMODExec(sound.getLength(ref soundlen, TIMEUNIT.PCM));
238                             FMODExec(sound.setLoopPoints(0, TIMEUNIT.PCM, soundlen - 1, TIMEUNIT.PCM));
239                             FMODExec(sound.setLoopCount(-1));
240                         }
241
242                         // Allocate a channel and set initial volume.  Initially paused.
243                         FMODExec(system.playSound(CHANNELINDEX.FREE, sound, true, ref channel));
244 #if TRACE_SOUND
245                     Logger.Log(
246                         String.Format("Channel {0} for {1} assigned to {2}",
247                              channel.getRaw().ToString("X"),
248                              sound.getRaw().ToString("X"),
249                              Id),
250                         Helpers.LogLevel.Debug);
251 #endif
252                         RegisterChannel(channel);
253
254                         FMODExec(channel.setVolume(volumeSetting * AllObjectVolume));
255
256                         // Take note of when the sound is finished playing.
257                         FMODExec(channel.setCallback(endCallback));
258
259                         // Set attenuation limits.
260                         FMODExec(sound.set3DMinMaxDistance(
261                                     1.2f,       // Any closer than this gets no louder
262                                     100.0f));     // Further than this gets no softer.
263
264                         // Set the sound point of origin.  This is in SIM coordinates.
265                         FMODExec(channel.set3DAttributes(ref position, ref ZeroVector));
266
267                         // Turn off pause mode.  The sound will start playing now.
268                         FMODExec(channel.setPaused(false));
269                     }
270                     catch (Exception ex)
271                     {
272                         Logger.Log("Error playing sound: ", Helpers.LogLevel.Error, ex);
273                     }
274                 }));
275             }
276             else
277             {
278                 Logger.Log("Failed to download sound: " + transfer.Status.ToString(),
279                                         Helpers.LogLevel.Error);
280             }
281         }
282
283         /// <summary>
284         /// Handles stop sound even from FMOD
285         /// </summary>
286         /// <returns>RESULT.OK</returns>
287         protected override RESULT EndCallbackHandler()
288         {
289             StopSound();
290             return RESULT.OK;
291         }
292
293         protected void StopSound()
294         {
295             StopSound(false);
296         }
297
298         protected void StopSound(bool blocking)
299         {
300             ManualResetEvent stopped = null;
301             if (blocking)
302                 stopped = new ManualResetEvent(false);
303
304             finished = true;
305
306             invoke(new SoundDelegate(delegate
307             {
308                 string chanStr = "none";
309                 string soundStr = "none";
310
311                 // Release the buffer to avoid a big memory leak.
312                 if (channel != null)
313                 {
314                     lock (allChannels)
315                         allChannels.Remove(channel.getRaw());
316                     chanStr = channel.getRaw().ToString("X");
317                     channel.stop();
318                     channel = null;
319                 }
320
321                 if (sound != null)
322                 {
323                     soundStr = sound.getRaw().ToString("X");
324                     sound.release();
325                     sound = null;
326                 }
327 #if TRACE_SOUND
328                 Logger.Log(String.Format("Removing channel {0} sound {1} ID {2}",
329                     chanStr,
330                     soundStr,
331                     Id.ToString()),
332                     Helpers.LogLevel.Debug);
333 #endif
334                 lock (allBuffers)
335                     allBuffers.Remove(ContainerId);
336
337                 if (blocking)
338                     stopped.Set();
339             }));
340
341             if (blocking)
342                 stopped.WaitOne();
343
344         }
345
346     }
347 }