Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
SoundEffect.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 
4 using System;
5 using System.Collections.Generic;
6 using System.IO;
7 using System.Threading;
8 
9 using SiliconStudio.Core;
10 using SiliconStudio.Core.IO;
11 using SiliconStudio.Paradox.Audio.Wave;
12 
13 namespace SiliconStudio.Paradox.Audio
14 {
15  /// <summary>
16  /// This class provides a loaded sound resource which is localizable in the 3D scene.
17  /// <para>SoundEffects are usually short sounds that are localized and need to be played with very low latency.
18  /// Classical examples are gun shots, foot steps, etc... Sound effects can be looped to seem longer.
19  /// Its is not recommended to use SoundEffect to play long track as the memory required will be considerable.
20  /// For that use please refer to the <see cref="SoundMusic"/> class.</para>
21  /// <para>You can then create multiple instances of that sound resource by calling <see cref="CreateInstance"/>.</para>
22  /// </summary>
23  /// <remarks>
24  /// <para>A SoundEffect contains the audio data and metadata (such as wave data and data format) loaded from a sound file.
25  /// You can create multiple <see cref="SoundEffectInstance"/> objects, and play and localize them independently.
26  /// All these objects share the resources of that SoundEffect.
27  /// For convenience purposes a default <see cref="SoundEffectInstance"/> is automatically created and associated to each SoundEffect,
28  /// so that you can directly play them without having the need to create a first instance.</para>
29  /// <para>
30  /// You can create a SoundEffect by calling the static <see cref="Load"/> load function.
31  /// Currently only wav files are supported for soundEffects.
32  /// </para>
33  /// <para>
34  /// The only limit to the number of loaded SoundEffect objects is memory.
35  /// A loaded SoundEffect will continue to hold its memory resources throughout its lifetime.
36  /// All <see cref="SoundEffectInstance"/> objects created from a SoundEffect share memory resources.
37  /// When a SoundEffect object is destroyed, all <see cref="SoundEffectInstance"/> objects previously created by that SoundEffect stop playing and become invalid.
38  /// </para>
39  /// <para>
40  /// Disposing a SoundEffect object, stops playing all the children <see cref="SoundEffectInstance"/> and then dispose them.
41  /// </para>
42  /// </remarks>
43  /// <seealso cref="SoundEffectInstance"/>
44  /// <seealso cref="SoundMusic"/>
45  /// <seealso cref="IPositionableSound"/>
46  public sealed partial class SoundEffect : SoundBase, IPositionableSound
47  {
48  internal WaveFormat WaveFormat { get; private set; }
49 
50  private IntPtr nativeDataBuffer;
51 
52  internal IntPtr WaveDataPtr { get; private set; }
53 
54  internal int WaveDataSize { get; private set; }
55 
56  /// <summary>
57  /// Create and Load a sound effect from an input wav stream.
58  /// </summary>
59  /// <param name="engine">Name of the audio engine in which to create the sound effect</param>
60  /// <param name="stream">A stream corresponding to a wav file.</param>
61  /// <returns>A new instance soundEffect ready to be played</returns>
62  /// <exception cref="ArgumentNullException"><paramref name="engine"/> or <paramref name="stream"/> is null.</exception>
63  /// <exception cref="NotSupportedException">The wave file or has more than 2 channels or is not encoded in 16bits.</exception>
64  /// <exception cref="InvalidOperationException">The content of the stream does not correspond to a valid wave file.</exception>
65  /// <exception cref="OutOfMemoryException">There is not enough memory anymore to load the specified file in memory. </exception>
66  /// <exception cref="ObjectDisposedException">The audio engine has already been disposed</exception>
67  /// <remarks>Supported WAV files' audio format is the 16bits PCM format.</remarks>
68  public static SoundEffect Load(AudioEngine engine, Stream stream)
69  {
70  if(engine == null)
71  throw new ArgumentNullException("engine");
72 
73  if (stream == null)
74  throw new ArgumentNullException("stream");
75 
76  if(engine.IsDisposed)
77  throw new ObjectDisposedException("Audio Engine");
78 
79  // create a native memory stream to extract the lz4 audio stream.
80  var newSdEff = new SoundEffect(engine) { nativeDataBuffer = Utilities.AllocateMemory((int)stream.Length) };
81 
82  var nativeStream = new NativeMemoryStream(newSdEff.nativeDataBuffer, stream.Length);
83  stream.CopyTo(nativeStream);
84  nativeStream.Position = 0;
85 
86  var waveStreamReader = new SoundStream(nativeStream);
87  var waveFormat = waveStreamReader.Format;
88 
89  if (waveFormat.Channels > 2)
90  throw new NotSupportedException("The wave file contains more than 2 data channels. Only mono and stereo formats are currently supported.");
91 
92  if (waveFormat.Encoding != WaveFormatEncoding.Pcm || waveFormat.BitsPerSample != 16)
93  throw new NotSupportedException("The wave file audio format is not supported. Only 16bits PCM encoded formats are currently supported.");
94 
95  newSdEff.WaveFormat = waveFormat;
96  newSdEff.WaveDataPtr = newSdEff.nativeDataBuffer + (int)nativeStream.Position;
97  newSdEff.WaveDataSize = (int)waveStreamReader.Length;
98  newSdEff.Name = "Sound Effect " + soundEffectCreationCount;
99 
100  newSdEff.AdaptAudioDataImpl();
101 
102  // register the sound to the AudioEngine so that it will be properly freed if AudioEngine is disposed before this.
103  engine.RegisterSound(newSdEff);
104 
105  Interlocked.Increment(ref soundEffectCreationCount);
106 
107  return newSdEff;
108  }
109 
110  /// <summary>
111  /// The number of SoundEffect Created so far. Used only to give a unique name to the SoundEffect.
112  /// </summary>
113  private static int soundEffectCreationCount;
114  /// <summary>
115  /// The number of Instances Created so far by this SoundEffect. Used only to give a unique name to the SoundEffectInstance.
116  /// </summary>
117  private int intancesCreationCount;
118 
119  /// <summary>
120  /// Create a new sound effect instance of the sound effect.
121  /// The audio data are shared between the instances so that useless memory copies is avoided.
122  /// Each instance that can be played and localized independently from others.
123  /// </summary>
124  /// <returns>A new sound instance</returns>
125  /// <exception cref="ObjectDisposedException">The sound has already been disposed</exception>
127  {
128  CheckNotDisposed();
129 
130  var newInstance = new SoundEffectInstance(this) { Name = Name + " - Instance " + intancesCreationCount };
131 
132  RegisterInstance(newInstance);
133 
134  ++intancesCreationCount;
135 
136  return newInstance;
137  }
138 
139  /// <summary>
140  /// Current instances of the SoundEffect.
141  /// We need to keep track of them to stop and dispose them when the soundEffect is disposed.
142  /// </summary>
143  internal readonly List<SoundEffectInstance> Instances = new List<SoundEffectInstance>();
144 
145  /// <summary>
146  /// Register a new instance to the soundEffect.
147  /// </summary>
148  /// <param name="instance">new instance to register.</param>
149  private void RegisterInstance(SoundEffectInstance instance)
150  {
151  Instances.Add(instance);
152  }
153 
154  /// <summary>
155  /// Stop all registered instances of the <see cref="SoundEffect"/>.
156  /// </summary>
157  internal void StopAllInstances()
158  {
159  foreach (var instance in Instances)
160  instance.Stop();
161  }
162 
163  /// <summary>
164  /// Stop all registered instances different from the provided main instance
165  /// </summary>
166  /// <param name="mainInstance">The main instance of the sound effect</param>
167  internal void StopConcurrentInstances(SoundEffectInstance mainInstance)
168  {
169  foreach (var instance in Instances)
170  {
171  if(instance != mainInstance)
172  instance.Stop();
173  }
174  }
175 
176  /// <summary>
177  /// Unregister a disposed Instance.
178  /// </summary>
179  /// <param name="instance"></param>
180  internal void UnregisterInstance(SoundEffectInstance instance)
181  {
182  if(!Instances.Remove(instance))
183  throw new AudioSystemInternalException("Tried to unregister soundEffectInstance while not contained in the instance list.");
184  }
185 
186  // Create an underlying Instance to avoid rewritting Interface functions.
187  private SoundEffectInstance DefaultInstance
188  {
189  get { return defaultInstance ?? (defaultInstance = CreateInstance()); }
190  }
191  private SoundEffectInstance defaultInstance;
192 
193  private SoundEffect(AudioEngine engine)
194  :base(engine)
195  {
196  }
197 
198  #region Interface Implementation using underlying SoundEffectInstance
199 
200  public float Pan
201  {
202  get { return DefaultInstance.Pan; }
203  set { DefaultInstance.Pan = value; }
204  }
205 
206  public float Volume
207  {
208  get { return DefaultInstance.Volume; }
209  set { DefaultInstance.Volume = value; }
210  }
211 
212  public void Apply3D(AudioListener listener, AudioEmitter emitter)
213  {
214  DefaultInstance.Apply3D(listener, emitter);
215  }
216 
217  public SoundPlayState PlayState
218  {
219  get { return DefaultInstance.PlayState; }
220  }
221 
222  public bool IsLooped
223  {
224  get { return DefaultInstance.IsLooped; }
225  set { DefaultInstance.IsLooped = value; }
226  }
227 
228  public void Play()
229  {
230  DefaultInstance.Play();
231  }
232 
233  public void Pause()
234  {
235  DefaultInstance.Pause();
236  }
237 
238  public void Stop()
239  {
240  DefaultInstance.Stop();
241  }
242 
243  public void ExitLoop()
244  {
245  DefaultInstance.ExitLoop();
246  }
247 
248  public void Reset3D()
249  {
250  DefaultInstance.Reset3D();
251  }
252 
253  protected override void Destroy()
254  {
255  base.Destroy();
256 
257  if (IsDisposed)
258  return;
259 
260  // Stop and dispose all the instances
261  foreach (var seInstance in Instances.ToArray())
262  seInstance.Dispose();
263 
264  DestroyImpl();
265 
266  // free the audio data.
267  if (nativeDataBuffer != IntPtr.Zero)
268  {
269  Utilities.FreeMemory(nativeDataBuffer);
270  nativeDataBuffer = IntPtr.Zero;
271  WaveDataPtr = IntPtr.Zero;
272  WaveDataSize = 0;
273  }
274 
275  // Unregister this from the sound to dispose by the audio engine
276  AudioEngine.UnregisterSound(this);
277  }
278 
279  private unsafe void DuplicateTracks(IntPtr waveDataPtr, IntPtr newWaveDataPtr, int newWaveDataSize)
280  {
281  var pInputCurrent = (short*)waveDataPtr;
282  var pOutputCurrent = (short*)newWaveDataPtr;
283  var numberOfIters = newWaveDataSize / sizeof(short);
284 
285  var count = 0;
286  while (count < numberOfIters)
287  {
288  *pOutputCurrent++ = *pInputCurrent;
289  *pOutputCurrent++ = *pInputCurrent;
290 
291  count += 2;
292  ++pInputCurrent;
293  }
294  }
295 
296  private unsafe void UpSampleByTwo(IntPtr waveDataPtr, IntPtr newWaveDataPtr, int newWaveDataSize, int nbOfChannels, bool convertToStereo)
297  {
298  var pInputCurrent = (short*)waveDataPtr;
299  var pOutputCurrent = (short*)newWaveDataPtr;
300  var numberOfIters = newWaveDataSize / sizeof(short);
301 
302  var count = 0;
303  while (true)
304  {
305  for (int i = 0; i < nbOfChannels; i++)
306  {
307  if (count >= numberOfIters)
308  return;
309 
310  *pOutputCurrent = *pInputCurrent;
311 
312  ++count;
313  ++pOutputCurrent;
314  ++pInputCurrent;
315  }
316  if (convertToStereo)
317  {
318  *pOutputCurrent = pOutputCurrent[-1];
319  ++count;
320  ++pOutputCurrent;
321  }
322  for (int i = 0; i < nbOfChannels; i++)
323  {
324  if (count >= numberOfIters)
325  return;
326 
327  *pOutputCurrent = (short)((pInputCurrent[0] + pInputCurrent[-nbOfChannels]) >> 1);
328 
329  ++count;
330  ++pOutputCurrent;
331  ++pInputCurrent;
332  }
333  if (convertToStereo)
334  {
335  *pOutputCurrent = pOutputCurrent[-1];
336  ++count;
337  ++pOutputCurrent;
338  }
339 
340  pInputCurrent -= nbOfChannels;
341  }
342  }
343 
344  private unsafe void UpSample(IntPtr waveDataPtr, IntPtr newWaveDataPtr, int newWaveDataSize, float sampleRateRatio, int nbOfChannels, bool convertToStereo)
345  {
346  var pInputCurrent = (short*)waveDataPtr;
347  var pOutputCurrent = (short*)newWaveDataPtr;
348  var numberOfIters = newWaveDataSize / sizeof(short);
349 
350  var count = 0;
351  var currentPosition = 0f;
352  while (true)
353  {
354  for (int i = 0; i < nbOfChannels; i++)
355  {
356  if (count >= numberOfIters)
357  return;
358 
359  *pOutputCurrent = (short)((1 - currentPosition) * pInputCurrent[i] + currentPosition * pInputCurrent[i + nbOfChannels]);
360 
361  ++count;
362  ++pOutputCurrent;
363  }
364  if (convertToStereo)
365  {
366  *pOutputCurrent = pOutputCurrent[-1];
367  ++count;
368  ++pOutputCurrent;
369  }
370 
371  currentPosition += sampleRateRatio;
372 
373  if (currentPosition >= 1)
374  {
375  pInputCurrent += nbOfChannels;
376  currentPosition -= 1;
377  }
378  }
379  }
380 
381  #endregion
382  }
383 }
Represents a 3D audio listener in the audio scene. This object, used in combination with an AudioEmit...
void ExitLoop()
Stop looping. That is, do not start over at the end of the current loop, continue to play until the e...
Definition: SoundEffect.cs:243
This class provides a loaded sound resource which is localizable in the 3D scene. ...
Definition: SoundEffect.cs:46
SoundEffectInstance CreateInstance()
Create a new sound effect instance of the sound effect. The audio data are shared between the instanc...
Definition: SoundEffect.cs:126
void Apply3D(AudioListener listener, AudioEmitter emitter)
Applies 3D positioning to the sound. More precisely adjust the channel volumes and pitch of the sound...
Definition: SoundEffect.cs:212
SoundPlayState
Current state (playing, paused, or stopped) of a sound implementing the IPlayableSound interface...
void Play()
Start or resume playing the sound.
Definition: SoundEffect.cs:228
bool IsDisposed
Has the component been disposed or not yet.
_In_ size_t count
Definition: DirectXTexP.h:174
Represents the audio engine. In current version, the audio engine necessarily creates its context on ...
Definition: AudioEngine.cs:80
void Reset3D()
Cancel the effect of possible previous calls to Apply3D.
Definition: SoundEffect.cs:248
static SoundEffect Load(AudioEngine engine, Stream stream)
Create and Load a sound effect from an input wav stream.
Definition: SoundEffect.cs:68
override void Destroy()
Disposes of object resources.
Definition: SoundEffect.cs:253
Base class for all the sounds and sound instances.
Definition: SoundBase.cs:15
A MemoryStream over a native memory region.
Represents a 3D audio emitter in the audio scene. This object, used in combination with an AudioListe...
Definition: AudioEmitter.cs:17
void Stop()
Stop playing the sound immediately and reset the sound to the beginning of the track.
Definition: SoundEffect.cs:238
Instance of a SoundEffect sound which can be independently localized and played.
Interface for 3D localizable sound. The interface currently supports only mono and stereo sounds (ref...
void Pause()
Pause the sounds.
Definition: SoundEffect.cs:233