Last active
December 21, 2017 07:29
-
-
Save takeshich/6e071562bf5536619a378b9f0c0b6c1f to your computer and use it in GitHub Desktop.
セカンドライフ技術系 Advent Calendar 2017向け
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Radegast Metaverse Client | |
// Copyright (c) 2009-2014, Radegast Development Team | |
// All rights reserved. | |
// | |
// Redistribution and use in source and binary forms, with or without | |
// modification, are permitted provided that the following conditions are met: | |
// | |
// * Redistributions of source code must retain the above copyright notice, | |
// this list of conditions and the following disclaimer. | |
// * Redistributions in binary form must reproduce the above copyright | |
// notice, this list of conditions and the following disclaimer in the | |
// documentation and/or other materials provided with the distribution. | |
// * Neither the name of the application "Radegast", nor the names of its | |
// contributors may be used to endorse or promote products derived from | |
// this software without specific prior written permission. | |
// | |
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
// | |
// $Id: Sound.cs 502 2010-03-14 23:13:46Z latifer $ | |
// | |
// Uncomment this to get lots more logging | |
//#define TRACE_SOUND | |
using System; | |
using System.Runtime.InteropServices; | |
using System.Collections.Generic; | |
using OpenMetaverse; | |
using OpenMetaverse.Assets; | |
using System.Threading; | |
using System.IO; | |
using Tsukikage.Audio; | |
using OpenTK.Audio.OpenAL; | |
namespace Radegast.Media | |
{ | |
public class BufferSound : MediaObject | |
{ | |
private UUID Id; | |
private UUID ContainerId; | |
private Boolean prefetchOnly = false; | |
private Boolean loopSound = false; | |
/// <summary> | |
/// The individual volume setting for THIS object | |
/// </summary> | |
private float volumeSetting = 0.5f; | |
/// <summary> | |
/// Creates a new sound object | |
/// </summary> | |
/// <param name="system">Sound system</param> | |
public BufferSound(UUID objectId, UUID soundId, bool loop, bool global, Vector3 worldpos, float vol) | |
: base() | |
{ | |
InitBuffer(objectId, soundId, loop, global, worldpos, vol); | |
} | |
public BufferSound(UUID objectId, UUID soundId, bool loop, bool global, Vector3d worldpos, float vol) | |
: base() | |
{ | |
InitBuffer(objectId, soundId, loop, global, new Vector3(worldpos), vol); | |
} | |
private void InitBuffer(UUID objectId, UUID soundId, bool loop, bool global, Vector3 worldpos, float vol) | |
{ | |
if (manager == null || !manager.SoundSystemAvailable) return; | |
// Do not let this get garbage-collected. | |
lock (allBuffers) | |
allBuffers[objectId] = this; | |
ContainerId = objectId; | |
Id = soundId; | |
position = FromOMVSpace(worldpos); | |
volumeSetting = vol; | |
loopSound = loop; | |
Logger.Log( | |
String.Format( | |
"Playing sound at <{0:0.0},{1:0.0},{2:0.0}> ID {3}", | |
position.X, | |
position.Y, | |
position.Z, | |
Id.ToString()), | |
Helpers.LogLevel.Debug); | |
// Fetch the sound data. | |
manager.Instance.Client.Assets.RequestAsset( | |
Id, | |
AssetType.Sound, | |
false, | |
new AssetManager.AssetReceivedCallback(Assets_OnSoundReceived)); | |
} | |
public static void Kill(UUID id) | |
{ | |
if (allBuffers.ContainsKey(id)) | |
{ | |
BufferSound bs = allBuffers[id]; | |
bs.StopSound(true); | |
} | |
} | |
/// <summary> | |
/// Stop all playing sounds in the environment | |
/// </summary> | |
public static void KillAll() | |
{ | |
// Make a list from the dictionary so we do not get a deadlock | |
// on it when removing entries. | |
List<BufferSound> list = new List<BufferSound>(allBuffers.Values); | |
foreach (BufferSound s in list) | |
{ | |
s.StopSound(); | |
} | |
List<MediaObject> objs = new List<MediaObject>(allChannels.Values); | |
foreach (MediaObject obj in objs) | |
{ | |
if (obj is BufferSound) | |
((BufferSound)obj).StopSound(); | |
} | |
} | |
/// <summary> | |
/// Adjust volumes of all playing sounds to observe the new global sound volume | |
/// </summary> | |
public static void AdjustVolumes() | |
{ | |
// Make a list from the dictionary so we do not get a deadlock | |
List<BufferSound> list = new List<BufferSound>(allBuffers.Values); | |
foreach (BufferSound s in list) | |
{ | |
s.AdjustVolume(); | |
} | |
} | |
/// <summary> | |
/// Adjust the volume of THIS sound when all are being adjusted. | |
/// </summary> | |
private void AdjustVolume() | |
{ | |
Volume = volumeSetting * AllObjectVolume; | |
} | |
// A simpler constructor used by PreFetchSound. | |
public BufferSound(UUID soundId) | |
: base() | |
{ | |
prefetchOnly = true; | |
ContainerId = UUID.Zero; | |
Id = soundId; | |
manager.Instance.Client.Assets.RequestAsset( | |
Id, | |
AssetType.Sound, | |
false, | |
new AssetManager.AssetReceivedCallback(Assets_OnSoundReceived)); | |
} | |
/// <summary> | |
/// Releases resources of this sound object | |
/// </summary> | |
public override void Dispose() | |
{ | |
base.Dispose(); | |
} | |
/** | |
* Handle arrival of a sound resource. | |
*/ | |
void Assets_OnSoundReceived(AssetDownload transfer, Asset asset) | |
{ | |
if (transfer.Success) | |
{ | |
// If this was a Prefetch, just stop here. | |
if (prefetchOnly) | |
{ | |
return; | |
} | |
//Logger.Log("Opening sound " + Id.ToString(), Helpers.LogLevel.Debug); | |
// Decode the Ogg Vorbis buffer. | |
AssetSound s = asset as AssetSound; | |
s.Decode(); | |
byte[] data = s.AssetData; | |
var source = AL.GenSource(); | |
var buffer = AL.GenBuffer(); | |
using (var ms = new MemoryStream(data)) | |
{ | |
int channels, bits_per_sample, sample_rate; | |
var sound_data = LoadOgg(ms, out channels, out bits_per_sample, out sample_rate); | |
AL.BufferData(buffer, GetSoundFormat(channels, bits_per_sample), sound_data, sound_data.Length, sample_rate); | |
} | |
RegisterSound(source, buffer); | |
AL.Source(source, ALSourcef.Pitch, 1.0f); // | |
AL.Source(source, ALSourcef.Gain, volumeSetting); // 音量 | |
AL.Source(source, ALSource3f.Position, position.X, position.Y, position.Z); | |
AL.Source(source, ALSourceb.Looping, loopSound); // 繰り返し | |
AL.SourcePlay(source); | |
} | |
else | |
{ | |
Logger.Log("Failed to download sound: " + transfer.Status.ToString(), | |
Helpers.LogLevel.Error); | |
} | |
} | |
protected void StopSound() | |
{ | |
StopSound(false); | |
} | |
protected void StopSound(bool blocking) | |
{ | |
ManualResetEvent stopped = null; | |
if (blocking) | |
stopped = new ManualResetEvent(false); | |
finished = true; | |
// TODO:キャンセルされたときの処理 | |
AL.SourcePause(this.Source); | |
//使わなくなったデータをクリーンアップ | |
AL.DeleteSource(this.Source); | |
AL.DeleteBuffer(this.Buffer); | |
invoke(new SoundDelegate(delegate | |
{ | |
lock (allBuffers) | |
allBuffers.Remove(ContainerId); | |
if (blocking) | |
stopped.Set(); | |
})); | |
if (blocking) | |
stopped.WaitOne(); | |
} | |
protected static byte[] LoadOgg(System.IO.Stream stream, out int channels, out int bits, out int rate) | |
{ | |
if (stream == null) | |
throw new ArgumentNullException("stream"); | |
byte[] returnbuf; | |
using (MemoryStream outMs = new MemoryStream()) | |
{ | |
using (var osb = new OggDecodeStream(stream)) | |
{ | |
byte[] readBuffer = new byte[2048]; | |
channels = osb.Channels; | |
bits = osb.BitsPerSample; | |
rate = osb.SamplesPerSecond; | |
while (true) | |
{ | |
var read_size = osb.Read(readBuffer, 0, readBuffer.Length); | |
if (read_size <= 0) break; | |
outMs.Write(readBuffer, 0, readBuffer.Length); | |
} | |
outMs.Flush(); | |
outMs.Seek(0, SeekOrigin.Begin); | |
returnbuf = outMs.GetBuffer(); | |
} | |
} | |
return returnbuf; | |
} | |
protected static ALFormat GetSoundFormat(int channels, int bits) | |
{ | |
switch (channels) | |
{ | |
case 1: return bits == 8 ? ALFormat.Mono8 : ALFormat.Mono16; | |
case 2: return bits == 8 ? ALFormat.Stereo8 : ALFormat.Stereo16; | |
default: throw new NotSupportedException("The specified sound format is not supported."); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Radegast Metaverse Client | |
// Copyright (c) 2009-2014, Radegast Development Team | |
// All rights reserved. | |
// | |
// Redistribution and use in source and binary forms, with or without | |
// modification, are permitted provided that the following conditions are met: | |
// | |
// * Redistributions of source code must retain the above copyright notice, | |
// this list of conditions and the following disclaimer. | |
// * Redistributions in binary form must reproduce the above copyright | |
// notice, this list of conditions and the following disclaimer in the | |
// documentation and/or other materials provided with the distribution. | |
// * Neither the name of the application "Radegast", nor the names of its | |
// contributors may be used to endorse or promote products derived from | |
// this software without specific prior written permission. | |
// | |
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
// | |
// $Id$ | |
// | |
using System; | |
using System.Collections.Generic; | |
using System.Text; | |
using OpenTK.Audio.OpenAL; | |
using System.Threading; | |
using OpenMetaverse; | |
using OpenMetaverse.Assets; | |
#if (COGBOT_LIBOMV || USE_STHREADS) | |
using ThreadPoolUtil; | |
using Thread = ThreadPoolUtil.Thread; | |
using ThreadPool = ThreadPoolUtil.ThreadPool; | |
using Monitor = ThreadPoolUtil.Monitor; | |
#endif | |
namespace Radegast.Media | |
{ | |
public class MediaManager : MediaObject | |
{ | |
/// <summary> | |
/// Indicated wheather spund sytem is ready for use | |
/// </summary> | |
public bool SoundSystemAvailable { get { return soundSystemAvailable; } } | |
private bool soundSystemAvailable = false; | |
private Thread soundThread; | |
private Thread listenerThread; | |
public RadegastInstance Instance; | |
private List<MediaObject> sounds = new List<MediaObject>(); | |
ManualResetEvent initDone = new ManualResetEvent(false); | |
private IntPtr device; | |
private OpenTK.ContextHandle context; | |
public MediaManager(RadegastInstance instance) | |
: base() | |
{ | |
this.Instance = instance; | |
manager = this; | |
if (MainProgram.CommandLine.DisableSound) | |
{ | |
soundSystemAvailable = false; | |
return; | |
} | |
//endCallback = new FMOD.CHANNEL_CALLBACK(DispatchEndCallback); | |
allBuffers = new Dictionary<UUID, BufferSound>(); | |
// Start the background thread that does all the FMOD calls. | |
soundThread = new Thread(new ThreadStart(CommandLoop)); | |
soundThread.IsBackground = true; | |
soundThread.Name = "SoundThread"; | |
soundThread.Start(); | |
// Start the background thread that updates listerner position. | |
listenerThread = new Thread(new ThreadStart(ListenerUpdate)); | |
listenerThread.IsBackground = true; | |
listenerThread.Name = "ListenerThread"; | |
listenerThread.Start(); | |
Instance.ClientChanged += new EventHandler<ClientChangedEventArgs>(Instance_ClientChanged); | |
// Wait for init to complete | |
initDone.WaitOne(); | |
initDone = null; | |
} | |
void Instance_ClientChanged(object sender, ClientChangedEventArgs e) | |
{ | |
UnregisterClientEvents(e.OldClient); | |
if (ObjectEnable) | |
RegisterClientEvents(e.Client); | |
} | |
void RegisterClientEvents(GridClient client) | |
{ | |
client.Sound.SoundTrigger += new EventHandler<SoundTriggerEventArgs>(Sound_SoundTrigger); | |
client.Sound.AttachedSound += new EventHandler<AttachedSoundEventArgs>(Sound_AttachedSound); | |
client.Sound.PreloadSound += new EventHandler<PreloadSoundEventArgs>(Sound_PreloadSound); | |
client.Objects.ObjectUpdate += new EventHandler<PrimEventArgs>(Objects_ObjectUpdate); | |
client.Objects.KillObject += new EventHandler<KillObjectEventArgs>(Objects_KillObject); | |
client.Network.SimChanged += new EventHandler<SimChangedEventArgs>(Network_SimChanged); | |
client.Self.ChatFromSimulator += new EventHandler<ChatEventArgs>(Self_ChatFromSimulator); | |
} | |
void UnregisterClientEvents(GridClient client) | |
{ | |
client.Sound.SoundTrigger -= new EventHandler<SoundTriggerEventArgs>(Sound_SoundTrigger); | |
client.Sound.AttachedSound -= new EventHandler<AttachedSoundEventArgs>(Sound_AttachedSound); | |
client.Sound.PreloadSound -= new EventHandler<PreloadSoundEventArgs>(Sound_PreloadSound); | |
client.Objects.ObjectUpdate -= new EventHandler<PrimEventArgs>(Objects_ObjectUpdate); | |
client.Objects.KillObject -= new EventHandler<KillObjectEventArgs>(Objects_KillObject); | |
client.Network.SimChanged -= new EventHandler<SimChangedEventArgs>(Network_SimChanged); | |
client.Self.ChatFromSimulator -= new EventHandler<ChatEventArgs>(Self_ChatFromSimulator); | |
} | |
/// <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; | |
ZeroVector.X = 0.0f; | |
ZeroVector.Y = 0.0f; | |
ZeroVector.Z = 0.0f; | |
allSounds = new Dictionary<int, MediaObject>(); | |
allChannels = new Dictionary<int, MediaObject>(); | |
// Initialize the command queue. | |
queue = new Queue<SoundDelegate>(); | |
// Initialize the FMOD sound package | |
InitOpenAL(); | |
initDone.Set(); | |
if (!this.soundSystemAvailable) return; | |
while (true) | |
{ | |
// Wait for something to show up in the queue. | |
lock (queue) | |
{ | |
while (queue.Count == 0) | |
{ | |
Monitor.Wait(queue); | |
} | |
action = queue.Dequeue(); | |
} | |
// We have an action, so call it. | |
try | |
{ | |
action(); | |
action = null; | |
} | |
catch (Exception e) | |
{ | |
Logger.Log("Error in sound action:\n " + e.Message + "\n" + e.StackTrace, | |
Helpers.LogLevel.Error); | |
} | |
} | |
} | |
/// <summary> | |
/// Initialize the OpenAL sound system. | |
/// </summary> | |
private void InitOpenAL() | |
{ | |
//初期化 | |
device = Alc.OpenDevice(null); | |
context = Alc.CreateContext(device, (int[])null); | |
Alc.MakeContextCurrent(context); | |
//-- ドップラー効果の設定 | |
//shift = DOPPLER_FACTOR * freq * (DOPPLER_VELOCITY - listener.velocity) / (DOPPLER_VELOCITY + source.velocity) | |
float DOPPLER_FACTOR = 0.5f; | |
float DOPPLER_VELOCITY = 10.0f; | |
AL.DopplerFactor(DOPPLER_FACTOR); | |
AL.DopplerVelocity(DOPPLER_VELOCITY); | |
soundSystemAvailable = true; | |
Logger.Log("InitOpenAL()", | |
Helpers.LogLevel.Debug); | |
} | |
public override void Dispose() | |
{ | |
if (Instance.Client != null) | |
UnregisterClientEvents(Instance.Client); | |
lock (sounds) | |
{ | |
for (int i = 0; i < sounds.Count; i++) | |
{ | |
if (!sounds[i].Disposed) | |
sounds[i].Dispose(); | |
} | |
sounds.Clear(); | |
} | |
sounds = null; | |
//Logger.Log("OpenAL interface stopping", Helpers.LogLevel.Info); | |
Alc.MakeContextCurrent(OpenTK.ContextHandle.Zero); | |
Alc.DestroyContext(context); | |
Alc.CloseDevice(device); | |
if (listenerThread != null) | |
{ | |
if (listenerThread.IsAlive) | |
listenerThread.Abort(); | |
listenerThread = null; | |
} | |
if (soundThread != null) | |
{ | |
if (soundThread.IsAlive) | |
soundThread.Abort(); | |
soundThread = null; | |
} | |
base.Dispose(); | |
} | |
/// <summary> | |
/// Thread to update listener position and generally keep | |
/// FMOD up to date. | |
/// </summary> | |
private void ListenerUpdate() | |
{ | |
// 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); | |
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. 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 | |
//{ | |
// FMODExec(system.update()); | |
//})); | |
continue; | |
} | |
// We have moved or turned. Remember new position. | |
lastpos = newPosition; | |
lastface = newFace; | |
// Convert coordinate spaces. | |
OpenTK.Vector3 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 ) | |
// 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.Sin(angle); // South | |
//forward.y = 0.0f; | |
//forward.z = (float)Math.Cos(angle); // East | |
// look.x.y.z / up.x.y.z | |
//float[] ListenOri = { (float)Math.Sin(angle), 0.0f, (float)Math.Cos(angle), 0.0f, 1.0f, 0.0f }; | |
float[] ListenOri = { -1.0f * (float)Math.Sin(angle), 0.0f, -1.0f * (float)Math.Cos(angle), 0.0f, 1.0f, 0.0f }; | |
//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. | |
OpenTK.Vector3 newPos = FromOMVSpace(newPosition); | |
AL.Listener(ALListener3f.Position, newPos.X,newPos.Y,newPos.Z); | |
AL.Listener(ALListener3f.Velocity, 0.0f, 0.0f, 0.0f); | |
AL.Listener(ALListenerfv.Orientation, ref ListenOri); | |
//Logger.Log(String.Format("ALListener3f.Position X:{0},Y:{1},Z:{2}", newPos.X, newPos.Y, newPos.Z), | |
// Helpers.LogLevel.Debug); | |
} | |
} | |
/// <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 * 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); | |
} | |
/// <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) | |
{ | |
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; | |
// Objects without sounds are not interesting. | |
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; } | |
} | |
/// <summary> | |
/// UI sounds volume | |
/// </summary> | |
public float UIVolume = 0.5f; | |
private bool m_objectEnabled = true; | |
/// <summary> | |
/// Enable and Disable inworld sounds | |
/// </summary> | |
public bool ObjectEnable | |
{ | |
set | |
{ | |
if (value) | |
{ | |
// Subscribe to events about inworld sounds | |
RegisterClientEvents(Instance.Client); | |
Logger.Log("Inworld sound enabled", Helpers.LogLevel.Info); | |
} | |
else | |
{ | |
// Subscribe to events about inworld sounds | |
UnregisterClientEvents(Instance.Client); | |
// Stop all running sounds | |
BufferSound.KillAll(); | |
Logger.Log("Inworld sound disabled", Helpers.LogLevel.Info); | |
} | |
m_objectEnabled = value; | |
} | |
get { return m_objectEnabled; } | |
} | |
void Self_ChatFromSimulator(object sender, ChatEventArgs e) | |
{ | |
if (e.Type == ChatType.StartTyping) | |
{ | |
new BufferSound( | |
UUID.Random(), | |
UISounds.Typing, | |
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(); | |
} | |
/// <summary> | |
/// Plays a sound | |
/// </summary> | |
/// <param name="sound">UUID of the sound to play</param> | |
public void PlayUISound(UUID sound) | |
{ | |
if (!soundSystemAvailable) return; | |
new BufferSound( | |
UUID.Random(), | |
sound, | |
false, | |
true, | |
Instance.Client.Self.SimPosition, | |
UIVolume); | |
} | |
} | |
public class MediaException : Exception | |
{ | |
public MediaException(string msg) | |
: base(msg) | |
{ | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Radegast Metaverse Client | |
// Copyright (c) 2009-2014, Radegast Development Team | |
// All rights reserved. | |
// | |
// Redistribution and use in source and binary forms, with or without | |
// modification, are permitted provided that the following conditions are met: | |
// | |
// * Redistributions of source code must retain the above copyright notice, | |
// this list of conditions and the following disclaimer. | |
// * Redistributions in binary form must reproduce the above copyright | |
// notice, this list of conditions and the following disclaimer in the | |
// documentation and/or other materials provided with the distribution. | |
// * Neither the name of the application "Radegast", nor the names of its | |
// contributors may be used to endorse or promote products derived from | |
// this software without specific prior written permission. | |
// | |
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
// | |
// $Id$ | |
// | |
using System; | |
using System.Collections.Generic; | |
using System.Text; | |
using System.Threading; | |
using System.Runtime.InteropServices; | |
using OpenTK.Audio.OpenAL; | |
using OpenMetaverse; | |
namespace Radegast.Media | |
{ | |
public abstract class MediaObject : Object, IDisposable | |
{ | |
/// <summary> | |
/// Indicates if this object's resources have already been disposed | |
/// </summary> | |
public bool Disposed { get { return disposed; } } | |
private bool disposed = false; | |
protected Boolean finished = false; | |
/// All commands are made through queued delegate calls, so they | |
/// are guaranteed to take place in the same thread. FMOD requires this. | |
public delegate void SoundDelegate(); | |
/// Queue of sound commands | |
/// | |
protected static Queue<SoundDelegate> queue; | |
protected static MediaManager manager; | |
protected static Dictionary<UUID,BufferSound> allBuffers; | |
public int Source { | |
get { return source; } | |
set { source = value; } | |
} | |
private int source; | |
public int Buffer | |
{ | |
get { return buffer; } | |
set { buffer = value; } | |
} | |
private int buffer; | |
//// Vectors used for orienting spatial axes. | |
protected static OpenTK.Vector3 UpVector; | |
protected static OpenTK.Vector3 ZeroVector; | |
protected static float AllObjectVolume = 0.8f; | |
public MediaObject() | |
{ | |
} | |
protected bool Cloned = false; | |
public virtual void Dispose() | |
{ | |
disposed = true; | |
} | |
public bool Active { get { return true; } } | |
/// <summary> | |
/// Put a delegate call on the command queue. These will be executed on | |
/// the FMOD control thread. All FMOD calls must happen there. | |
/// </summary> | |
/// <param name="action"></param> | |
protected void invoke(SoundDelegate action) | |
{ | |
// Do nothing if queue not ready yet. | |
if (queue == null) return; | |
// Put that on the queue and wake up the background thread. | |
lock (queue) | |
{ | |
queue.Enqueue( action ); | |
Monitor.Pulse(queue); | |
} | |
} | |
/// <summary> | |
/// Change a playback volume | |
/// </summary> | |
protected float volume = 0.8f; | |
public float Volume | |
{ | |
get | |
{ | |
return volume; | |
} | |
set | |
{ | |
volume = value; | |
AL.Source(this.Source, ALSourcef.Gain, volume); | |
} | |
} | |
/// <summary> | |
/// Update the 3D position of a sound source. | |
/// </summary> | |
protected OpenTK.Vector3 position = new OpenTK.Vector3(); | |
public OpenMetaverse.Vector3 Position | |
{ | |
set | |
{ | |
position = FromOMVSpace(value); | |
AL.Source(this.Source, ALSource3f.Position, position.X, position.Y, position.Z); | |
} | |
} | |
public void Stop() | |
{ | |
AL.SourcePause(this.Source); | |
} | |
/// <summary> | |
/// Convert OpenMetaVerse to FMOD coordinate space. | |
/// </summary> | |
/// <param name="omvV"></param> | |
/// <returns></returns> | |
protected OpenTK.Vector3 FromOMVSpace(OpenMetaverse.Vector3 omvV) | |
{ | |
// OMV X is forward/East, Y is left/North, Z is up. | |
// FMOD Z is forward/East, X is right/South, Y is up. | |
OpenTK.Vector3 v = new OpenTK.Vector3(); | |
v.X = -omvV.Y; | |
v.Y = omvV.Z; | |
v.Z = omvV.X; | |
return v; | |
} | |
protected static Dictionary<int,MediaObject> allSounds; | |
protected static Dictionary<int, MediaObject> allChannels; | |
protected void RegisterSound(int source,int buffer) | |
{ | |
if (allSounds.ContainsKey(source)) | |
allSounds.Remove(source); | |
allSounds.Add(source, this); | |
this.Source = source; | |
this.Buffer = buffer; | |
AL.BindBufferToSource(source, buffer); | |
} | |
protected void UnRegisterSound() | |
{ | |
if (allSounds.ContainsKey( this.Source )) | |
{ | |
allSounds.Remove(this.Source); | |
} | |
AL.DeleteSource(this.Source); | |
AL.DeleteBuffer(this.Buffer); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment