Skip to content

Instantly share code, notes, and snippets.

@takeshich
Last active December 21, 2017 07:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save takeshich/6e071562bf5536619a378b9f0c0b6c1f to your computer and use it in GitHub Desktop.
Save takeshich/6e071562bf5536619a378b9f0c0b6c1f to your computer and use it in GitHub Desktop.
セカンドライフ技術系 Advent Calendar 2017向け
//
// 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.");
}
}
}
}
//
// 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)
{
}
}
}
//
// 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