2 // Radegast Metaverse Client
3 // Copyright (c) 2009-2013, 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.
29 // $Id: Sound.cs 502 2010-03-14 23:13:46Z latifer $
31 // Uncomment this to get lots more logging
34 using System.Runtime.InteropServices;
35 using System.Collections.Generic;
38 using OpenMetaverse.Assets;
39 using System.Threading;
41 namespace Radegast.Media
44 public class BufferSound : MediaObject
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;
53 /// The individual volume setting for THIS object
55 private float volumeSetting = 0.5f;
58 /// Creates a new sound object
60 /// <param name="system">Sound system</param>
61 public BufferSound(UUID objectId, UUID soundId, bool loop, bool global, Vector3 worldpos, float vol)
64 InitBuffer(objectId, soundId, loop, global, worldpos, vol);
67 public BufferSound(UUID objectId, UUID soundId, bool loop, bool global, Vector3d worldpos, float vol)
70 InitBuffer(objectId, soundId, loop, global, new Vector3(worldpos), vol);
73 private void InitBuffer(UUID objectId, UUID soundId, bool loop, bool global, Vector3 worldpos, float vol)
75 if (manager == null || !manager.SoundSystemAvailable) return;
77 // Do not let this get garbage-collected.
79 allBuffers[objectId] = this;
81 ContainerId = objectId;
83 position = FromOMVSpace(worldpos);
89 "Playing sound at <{0:0.0},{1:0.0},{2:0.0}> ID {3}",
94 Helpers.LogLevel.Debug);
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
101 // Set coordinate space interpretation.
103 mode |= FMOD.MODE._3D_WORLDRELATIVE;
105 mode |= FMOD.MODE._3D_HEADRELATIVE;
108 mode |= FMOD.MODE.LOOP_NORMAL;
110 // Fetch the sound data.
111 manager.Instance.Client.Assets.RequestAsset(
115 new AssetManager.AssetReceivedCallback(Assets_OnSoundReceived));
118 public static void Kill(UUID id)
120 if (allBuffers.ContainsKey(id))
122 BufferSound bs = allBuffers[id];
128 /// Stop all playing sounds in the environment
130 public static void KillAll()
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);
136 foreach (BufferSound s in list)
141 List<MediaObject> objs = new List<MediaObject>(allChannels.Values);
142 foreach (MediaObject obj in objs)
144 if (obj is BufferSound)
145 ((BufferSound)obj).StopSound();
150 /// Adjust volumes of all playing sounds to observe the new global sound volume
152 public static void AdjustVolumes()
154 // Make a list from the dictionary so we do not get a deadlock
155 List<BufferSound> list = new List<BufferSound>(allBuffers.Values);
157 foreach (BufferSound s in list)
164 /// Adjust the volume of THIS sound when all are being adjusted.
166 private void AdjustVolume()
168 Volume = volumeSetting * AllObjectVolume;
171 // A simpler constructor used by PreFetchSound.
172 public BufferSound(UUID soundId)
176 ContainerId = UUID.Zero;
179 manager.Instance.Client.Assets.RequestAsset(
183 new AssetManager.AssetReceivedCallback(Assets_OnSoundReceived));
187 /// Releases resources of this sound object
189 public override void Dispose()
195 * Handle arrival of a sound resource.
197 void Assets_OnSoundReceived(AssetDownload transfer, Asset asset)
199 if (transfer.Success)
201 // If this was a Prefetch, just stop here.
207 // Logger.Log("Opening sound " + Id.ToString(), Helpers.LogLevel.Debug);
209 // Decode the Ogg Vorbis buffer.
210 AssetSound s = asset as AssetSound;
212 byte[] data = s.AssetData;
214 // Describe the data to FMOD
215 extraInfo.length = (uint)data.Length;
216 extraInfo.cbsize = Marshal.SizeOf(extraInfo);
218 invoke(new SoundDelegate(delegate
222 // Create an FMOD sound of this Ogg data.
223 FMODExec(system.createSound(
229 // Register for callbacks.
230 RegisterSound(sound);
233 // If looping is requested, loop the entire thing.
237 FMODExec(sound.getLength(ref soundlen, TIMEUNIT.PCM));
238 FMODExec(sound.setLoopPoints(0, TIMEUNIT.PCM, soundlen - 1, TIMEUNIT.PCM));
239 FMODExec(sound.setLoopCount(-1));
242 // Allocate a channel and set initial volume. Initially paused.
243 FMODExec(system.playSound(CHANNELINDEX.FREE, sound, true, ref channel));
246 String.Format("Channel {0} for {1} assigned to {2}",
247 channel.getRaw().ToString("X"),
248 sound.getRaw().ToString("X"),
250 Helpers.LogLevel.Debug);
252 RegisterChannel(channel);
254 FMODExec(channel.setVolume(volumeSetting * AllObjectVolume));
256 // Take note of when the sound is finished playing.
257 FMODExec(channel.setCallback(endCallback));
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.
264 // Set the sound point of origin. This is in SIM coordinates.
265 FMODExec(channel.set3DAttributes(ref position, ref ZeroVector));
267 // Turn off pause mode. The sound will start playing now.
268 FMODExec(channel.setPaused(false));
272 Logger.Log("Error playing sound: ", Helpers.LogLevel.Error, ex);
278 Logger.Log("Failed to download sound: " + transfer.Status.ToString(),
279 Helpers.LogLevel.Error);
284 /// Handles stop sound even from FMOD
286 /// <returns>RESULT.OK</returns>
287 protected override RESULT EndCallbackHandler()
293 protected void StopSound()
298 protected void StopSound(bool blocking)
300 ManualResetEvent stopped = null;
302 stopped = new ManualResetEvent(false);
306 invoke(new SoundDelegate(delegate
308 string chanStr = "none";
309 string soundStr = "none";
311 // Release the buffer to avoid a big memory leak.
315 allChannels.Remove(channel.getRaw());
316 chanStr = channel.getRaw().ToString("X");
323 soundStr = sound.getRaw().ToString("X");
328 Logger.Log(String.Format("Removing channel {0} sound {1} ID {2}",
332 Helpers.LogLevel.Debug);
335 allBuffers.Remove(ContainerId);