forked from MoonsideGames/MoonWorks
Audio Improvements (#47)
- Audio is now processed on a background thread instead of the main thread - Audio tick rate is now ~200Hz - MoonWorks.Math.Easings class completely rewritten to be easier to understand and use - SoundInstance properties no longer call into FAudio unless the value actually changed - SoundInstance property values can now be interpolated over time (tweens) - SoundInstance tweens can be delayed - SoundInstance sets a sane filter frequency default when switching filter type - StreamingSound classes can be designated to update automatically on the audio thread or manually - StreamingSound buffer consumption should now set Stopped state in a more sane way - Added ReverbEffect, which creates a submix voice for a reverb effect - SoundInstance can apply a ReverbEffect, which enables the Reverb property - Audio resource tracking improvements - Some tweaks to VideoPlayer to make its behavior more consistent Reviewed-on: MoonsideGames/MoonWorks#47remotes/1695061714407202320/main
parent
f8b14ea94f
commit
1f0e3b5040
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
|
@ -26,13 +26,25 @@ namespace MoonWorks.Audio
|
|||
}
|
||||
}
|
||||
|
||||
private readonly List<WeakReference<AudioResource>> resources = new List<WeakReference<AudioResource>>();
|
||||
private readonly List<WeakReference<StreamingSound>> streamingSounds = new List<WeakReference<StreamingSound>>();
|
||||
private readonly HashSet<WeakReference> resources = new HashSet<WeakReference>();
|
||||
private readonly HashSet<WeakReference> autoUpdateStreamingSoundReferences = new HashSet<WeakReference>();
|
||||
|
||||
private AudioTweenManager AudioTweenManager;
|
||||
|
||||
private const int Step = 200;
|
||||
private TimeSpan UpdateInterval;
|
||||
private System.Diagnostics.Stopwatch TickStopwatch = new System.Diagnostics.Stopwatch();
|
||||
private long previousTickTime;
|
||||
private Thread Thread;
|
||||
private AutoResetEvent WakeSignal;
|
||||
internal readonly object StateLock = new object();
|
||||
|
||||
private bool IsDisposed;
|
||||
|
||||
public unsafe AudioDevice()
|
||||
{
|
||||
UpdateInterval = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / Step);
|
||||
|
||||
FAudio.FAudioCreate(out var handle, 0, FAudio.FAUDIO_DEFAULT_PROCESSOR);
|
||||
Handle = handle;
|
||||
|
||||
|
@ -90,8 +102,8 @@ namespace MoonWorks.Audio
|
|||
) != 0)
|
||||
{
|
||||
Logger.LogError("No mastering voice found!");
|
||||
Handle = IntPtr.Zero;
|
||||
FAudio.FAudio_Release(Handle);
|
||||
Handle = IntPtr.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -105,22 +117,52 @@ namespace MoonWorks.Audio
|
|||
SpeedOfSound,
|
||||
Handle3D
|
||||
);
|
||||
|
||||
AudioTweenManager = new AudioTweenManager();
|
||||
|
||||
Logger.LogInfo("Setting up audio thread...");
|
||||
WakeSignal = new AutoResetEvent(true);
|
||||
|
||||
Thread = new Thread(ThreadMain);
|
||||
Thread.IsBackground = true;
|
||||
Thread.Start();
|
||||
|
||||
TickStopwatch.Start();
|
||||
previousTickTime = 0;
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
private void ThreadMain()
|
||||
{
|
||||
for (var i = streamingSounds.Count - 1; i >= 0; i--)
|
||||
while (!IsDisposed)
|
||||
{
|
||||
var weakReference = streamingSounds[i];
|
||||
if (weakReference.TryGetTarget(out var streamingSound))
|
||||
lock (StateLock)
|
||||
{
|
||||
ThreadMainTick();
|
||||
}
|
||||
|
||||
WakeSignal.WaitOne(UpdateInterval);
|
||||
}
|
||||
}
|
||||
|
||||
private void ThreadMainTick()
|
||||
{
|
||||
long tickDelta = TickStopwatch.Elapsed.Ticks - previousTickTime;
|
||||
previousTickTime = TickStopwatch.Elapsed.Ticks;
|
||||
float elapsedSeconds = (float) tickDelta / System.TimeSpan.TicksPerSecond;
|
||||
|
||||
foreach (var weakReference in autoUpdateStreamingSoundReferences)
|
||||
{
|
||||
if (weakReference.Target is StreamingSound streamingSound)
|
||||
{
|
||||
streamingSound.Update();
|
||||
}
|
||||
else
|
||||
{
|
||||
streamingSounds.RemoveAt(i);
|
||||
autoUpdateStreamingSoundReferences.Remove(weakReference);
|
||||
}
|
||||
}
|
||||
|
||||
AudioTweenManager.Update(elapsedSeconds);
|
||||
}
|
||||
|
||||
public void SyncPlay()
|
||||
|
@ -128,40 +170,95 @@ namespace MoonWorks.Audio
|
|||
FAudio.FAudio_CommitChanges(Handle, 1);
|
||||
}
|
||||
|
||||
internal void AddDynamicSoundInstance(StreamingSound instance)
|
||||
internal void CreateTween(
|
||||
SoundInstance soundInstance,
|
||||
AudioTweenProperty property,
|
||||
System.Func<float, float> easingFunction,
|
||||
float start,
|
||||
float end,
|
||||
float duration,
|
||||
float delayTime
|
||||
) {
|
||||
lock (StateLock)
|
||||
{
|
||||
streamingSounds.Add(new WeakReference<StreamingSound>(instance));
|
||||
}
|
||||
|
||||
internal void AddResourceReference(WeakReference<AudioResource> resourceReference)
|
||||
{
|
||||
lock (resources)
|
||||
{
|
||||
resources.Add(resourceReference);
|
||||
AudioTweenManager.CreateTween(
|
||||
soundInstance,
|
||||
property,
|
||||
easingFunction,
|
||||
start,
|
||||
end,
|
||||
duration,
|
||||
delayTime
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
internal void RemoveResourceReference(WeakReference<AudioResource> resourceReference)
|
||||
internal void ClearTweens(
|
||||
WeakReference soundReference,
|
||||
AudioTweenProperty property
|
||||
) {
|
||||
lock (StateLock)
|
||||
{
|
||||
lock (resources)
|
||||
{
|
||||
resources.Remove(resourceReference);
|
||||
AudioTweenManager.ClearTweens(soundReference, property);
|
||||
}
|
||||
}
|
||||
|
||||
internal void WakeThread()
|
||||
{
|
||||
WakeSignal.Set();
|
||||
}
|
||||
|
||||
internal void AddResourceReference(AudioResource resource)
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
resources.Add(resource.weakReference);
|
||||
|
||||
if (resource is StreamingSound streamingSound && streamingSound.AutoUpdate)
|
||||
{
|
||||
AddAutoUpdateStreamingSoundInstance(streamingSound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void RemoveResourceReference(AudioResource resource)
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
resources.Remove(resource.weakReference);
|
||||
|
||||
if (resource is StreamingSound streamingSound && streamingSound.AutoUpdate)
|
||||
{
|
||||
RemoveAutoUpdateStreamingSoundInstance(streamingSound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddAutoUpdateStreamingSoundInstance(StreamingSound instance)
|
||||
{
|
||||
autoUpdateStreamingSoundReferences.Add(instance.weakReference);
|
||||
}
|
||||
|
||||
private void RemoveAutoUpdateStreamingSoundInstance(StreamingSound instance)
|
||||
{
|
||||
autoUpdateStreamingSoundReferences.Remove(instance.weakReference);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
for (var i = resources.Count - 1; i >= 0; i--)
|
||||
foreach (var weakReference in resources)
|
||||
{
|
||||
var weakReference = resources[i];
|
||||
var target = weakReference.Target;
|
||||
|
||||
if (weakReference.TryGetTarget(out var resource))
|
||||
if (target != null)
|
||||
{
|
||||
resource.Dispose();
|
||||
(target as IDisposable).Dispose();
|
||||
}
|
||||
}
|
||||
resources.Clear();
|
||||
|
@ -173,8 +270,8 @@ namespace MoonWorks.Audio
|
|||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
||||
~AudioDevice()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
|
|
|
@ -8,14 +8,14 @@ namespace MoonWorks.Audio
|
|||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
private WeakReference<AudioResource> selfReference;
|
||||
internal WeakReference weakReference;
|
||||
|
||||
public AudioResource(AudioDevice device)
|
||||
{
|
||||
Device = device;
|
||||
|
||||
selfReference = new WeakReference<AudioResource>(this);
|
||||
Device.AddResourceReference(selfReference);
|
||||
weakReference = new WeakReference(this);
|
||||
Device.AddResourceReference(this);
|
||||
}
|
||||
|
||||
protected abstract void Destroy();
|
||||
|
@ -26,10 +26,10 @@ namespace MoonWorks.Audio
|
|||
{
|
||||
Destroy();
|
||||
|
||||
if (selfReference != null)
|
||||
if (weakReference != null)
|
||||
{
|
||||
Device.RemoveResourceReference(selfReference);
|
||||
selfReference = null;
|
||||
Device.RemoveResourceReference(this);
|
||||
weakReference = null;
|
||||
}
|
||||
|
||||
IsDisposed = true;
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
using System.Collections.Generic;
|
||||
using EasingFunction = System.Func<float, float>;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
internal enum AudioTweenProperty
|
||||
{
|
||||
Pan,
|
||||
Pitch,
|
||||
Volume,
|
||||
FilterFrequency,
|
||||
Reverb
|
||||
}
|
||||
|
||||
internal class AudioTween
|
||||
{
|
||||
public System.WeakReference SoundInstanceReference;
|
||||
public AudioTweenProperty Property;
|
||||
public EasingFunction EasingFunction;
|
||||
public float Time;
|
||||
public float StartValue;
|
||||
public float EndValue;
|
||||
public float DelayTime;
|
||||
public float Duration;
|
||||
}
|
||||
|
||||
internal class AudioTweenPool
|
||||
{
|
||||
private Queue<AudioTween> Tweens = new Queue<AudioTween>(16);
|
||||
|
||||
public AudioTweenPool()
|
||||
{
|
||||
for (int i = 0; i < 16; i += 1)
|
||||
{
|
||||
Tweens.Enqueue(new AudioTween());
|
||||
}
|
||||
}
|
||||
|
||||
public AudioTween Obtain()
|
||||
{
|
||||
if (Tweens.Count > 0)
|
||||
{
|
||||
var tween = Tweens.Dequeue();
|
||||
return tween;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new AudioTween();
|
||||
}
|
||||
}
|
||||
|
||||
public void Free(AudioTween tween)
|
||||
{
|
||||
Tweens.Enqueue(tween);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
internal class AudioTweenManager
|
||||
{
|
||||
private AudioTweenPool AudioTweenPool = new AudioTweenPool();
|
||||
private readonly Dictionary<(WeakReference, AudioTweenProperty), AudioTween> AudioTweens = new Dictionary<(WeakReference, AudioTweenProperty), AudioTween>();
|
||||
private readonly List<AudioTween> DelayedAudioTweens = new List<AudioTween>();
|
||||
|
||||
public void Update(float elapsedSeconds)
|
||||
{
|
||||
for (var i = DelayedAudioTweens.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var audioTween = DelayedAudioTweens[i];
|
||||
if (audioTween.SoundInstanceReference.Target is SoundInstance soundInstance)
|
||||
{
|
||||
audioTween.Time += elapsedSeconds;
|
||||
|
||||
if (audioTween.Time >= audioTween.DelayTime)
|
||||
{
|
||||
// set the tween start value to the current value of the property
|
||||
switch (audioTween.Property)
|
||||
{
|
||||
case AudioTweenProperty.Pan:
|
||||
audioTween.StartValue = soundInstance.Pan;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.Pitch:
|
||||
audioTween.StartValue = soundInstance.Pitch;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.Volume:
|
||||
audioTween.StartValue = soundInstance.Volume;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.FilterFrequency:
|
||||
audioTween.StartValue = soundInstance.FilterFrequency;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.Reverb:
|
||||
audioTween.StartValue = soundInstance.Reverb;
|
||||
break;
|
||||
}
|
||||
|
||||
audioTween.Time = 0;
|
||||
DelayedAudioTweens.RemoveAt(i);
|
||||
|
||||
AddTween(audioTween);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AudioTweenPool.Free(audioTween);
|
||||
DelayedAudioTweens.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (key, audioTween) in AudioTweens)
|
||||
{
|
||||
bool finished = true;
|
||||
if (audioTween.SoundInstanceReference.Target is SoundInstance soundInstance)
|
||||
{
|
||||
finished = UpdateAudioTween(audioTween, soundInstance, elapsedSeconds);
|
||||
}
|
||||
|
||||
if (finished)
|
||||
{
|
||||
AudioTweenPool.Free(audioTween);
|
||||
AudioTweens.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateTween(
|
||||
SoundInstance soundInstance,
|
||||
AudioTweenProperty property,
|
||||
System.Func<float, float> easingFunction,
|
||||
float start,
|
||||
float end,
|
||||
float duration,
|
||||
float delayTime
|
||||
) {
|
||||
var tween = AudioTweenPool.Obtain();
|
||||
tween.SoundInstanceReference = soundInstance.weakReference;
|
||||
tween.Property = property;
|
||||
tween.EasingFunction = easingFunction;
|
||||
tween.StartValue = start;
|
||||
tween.EndValue = end;
|
||||
tween.Duration = duration;
|
||||
tween.Time = 0;
|
||||
tween.DelayTime = delayTime;
|
||||
|
||||
if (delayTime == 0)
|
||||
{
|
||||
AddTween(tween);
|
||||
}
|
||||
else
|
||||
{
|
||||
DelayedAudioTweens.Add(tween);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearTweens(WeakReference soundReference, AudioTweenProperty property)
|
||||
{
|
||||
AudioTweens.Remove((soundReference, property));
|
||||
}
|
||||
|
||||
private void AddTween(
|
||||
AudioTween audioTween
|
||||
) {
|
||||
// if a tween with the same sound and property already exists, get rid of it
|
||||
if (AudioTweens.TryGetValue((audioTween.SoundInstanceReference, audioTween.Property), out var currentTween))
|
||||
{
|
||||
AudioTweenPool.Free(currentTween);
|
||||
}
|
||||
|
||||
AudioTweens[(audioTween.SoundInstanceReference, audioTween.Property)] = audioTween;
|
||||
}
|
||||
|
||||
private static bool UpdateAudioTween(AudioTween audioTween, SoundInstance soundInstance, float delta)
|
||||
{
|
||||
float value;
|
||||
audioTween.Time += delta;
|
||||
|
||||
var finished = audioTween.Time >= audioTween.Duration;
|
||||
if (finished)
|
||||
{
|
||||
value = audioTween.EndValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = MoonWorks.Math.Easing.Interp(
|
||||
audioTween.StartValue,
|
||||
audioTween.EndValue,
|
||||
audioTween.Time,
|
||||
audioTween.Duration,
|
||||
audioTween.EasingFunction
|
||||
);
|
||||
}
|
||||
|
||||
switch (audioTween.Property)
|
||||
{
|
||||
case AudioTweenProperty.Pan:
|
||||
soundInstance.Pan = value;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.Pitch:
|
||||
soundInstance.Pitch = value;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.Volume:
|
||||
soundInstance.Volume = value;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.FilterFrequency:
|
||||
soundInstance.FilterFrequency = value;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.Reverb:
|
||||
soundInstance.Reverb = value;
|
||||
break;
|
||||
}
|
||||
|
||||
return finished;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,14 +4,12 @@ using System.Runtime.InteropServices;
|
|||
namespace MoonWorks.Audio
|
||||
{
|
||||
// sound instances can send their audio to this voice to add reverb
|
||||
public unsafe class ReverbEffect : IDisposable
|
||||
public unsafe class ReverbEffect : AudioResource
|
||||
{
|
||||
private IntPtr voice;
|
||||
public IntPtr Voice => voice;
|
||||
|
||||
private bool disposedValue;
|
||||
|
||||
public ReverbEffect(AudioDevice audioDevice)
|
||||
public ReverbEffect(AudioDevice audioDevice) : base(audioDevice)
|
||||
{
|
||||
/* Init reverb */
|
||||
|
||||
|
@ -97,32 +95,9 @@ namespace MoonWorks.Audio
|
|||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
protected override void Destroy()
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// TODO: dispose managed state (managed objects)
|
||||
}
|
||||
|
||||
FAudio.FAudioVoice_DestroyVoice(voice);
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
~ReverbEffect()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
FAudio.FAudioVoice_DestroyVoice(Voice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using EasingFunction = System.Func<float, float>;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
public abstract class SoundInstance : AudioResource
|
||||
{
|
||||
internal IntPtr Voice;
|
||||
internal FAudio.FAudioWaveFormatEx Format;
|
||||
|
||||
private FAudio.FAudioWaveFormatEx format;
|
||||
public FAudio.FAudioWaveFormatEx Format => format;
|
||||
|
||||
protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings;
|
||||
|
||||
|
@ -21,7 +24,10 @@ namespace MoonWorks.Audio
|
|||
public float Pan
|
||||
{
|
||||
get => pan;
|
||||
set
|
||||
internal set
|
||||
{
|
||||
value = Math.MathHelper.Clamp(value, -1f, 1f);
|
||||
if (pan != value)
|
||||
{
|
||||
pan = value;
|
||||
|
||||
|
@ -47,28 +53,37 @@ namespace MoonWorks.Audio
|
|||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float pitch = 0;
|
||||
public float Pitch
|
||||
{
|
||||
get => pitch;
|
||||
set
|
||||
internal set
|
||||
{
|
||||
pitch = Math.MathHelper.Clamp(value, -1f, 1f);
|
||||
value = Math.MathHelper.Clamp(value, -1f, 1f);
|
||||
if (pitch != value)
|
||||
{
|
||||
pitch = value;
|
||||
UpdatePitch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float volume = 1;
|
||||
public float Volume
|
||||
{
|
||||
get => volume;
|
||||
set
|
||||
internal set
|
||||
{
|
||||
value = Math.MathHelper.Max(0, value);
|
||||
if (volume != value)
|
||||
{
|
||||
volume = value;
|
||||
FAudio.FAudioVoice_SetVolume(Voice, volume, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const float MAX_FILTER_FREQUENCY = 1f;
|
||||
private const float MAX_FILTER_ONEOVERQ = 1.5f;
|
||||
|
@ -80,12 +95,14 @@ namespace MoonWorks.Audio
|
|||
OneOverQ = 1f
|
||||
};
|
||||
|
||||
private float FilterFrequency
|
||||
public float FilterFrequency
|
||||
{
|
||||
get => filterParameters.Frequency;
|
||||
set
|
||||
internal set
|
||||
{
|
||||
value = System.Math.Clamp(value, 0.01f, MAX_FILTER_FREQUENCY);
|
||||
if (filterParameters.Frequency != value)
|
||||
{
|
||||
filterParameters.Frequency = value;
|
||||
|
||||
FAudio.FAudioVoice_SetFilterParameters(
|
||||
|
@ -95,13 +112,16 @@ namespace MoonWorks.Audio
|
|||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float FilterOneOverQ
|
||||
public float FilterOneOverQ
|
||||
{
|
||||
get => filterParameters.OneOverQ;
|
||||
set
|
||||
internal set
|
||||
{
|
||||
value = System.Math.Clamp(value, 0.01f, MAX_FILTER_ONEOVERQ);
|
||||
if (filterParameters.OneOverQ != value)
|
||||
{
|
||||
filterParameters.OneOverQ = value;
|
||||
|
||||
FAudio.FAudioVoice_SetFilterParameters(
|
||||
|
@ -111,12 +131,15 @@ namespace MoonWorks.Audio
|
|||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private FilterType filterType;
|
||||
public FilterType FilterType
|
||||
{
|
||||
get => filterType;
|
||||
set
|
||||
{
|
||||
if (filterType != value)
|
||||
{
|
||||
filterType = value;
|
||||
|
||||
|
@ -133,6 +156,7 @@ namespace MoonWorks.Audio
|
|||
|
||||
case FilterType.LowPass:
|
||||
filterParameters.Type = FAudio.FAudioFilterType.FAudioLowPassFilter;
|
||||
filterParameters.Frequency = 1f;
|
||||
break;
|
||||
|
||||
case FilterType.BandPass:
|
||||
|
@ -141,6 +165,7 @@ namespace MoonWorks.Audio
|
|||
|
||||
case FilterType.HighPass:
|
||||
filterParameters.Type = FAudio.FAudioFilterType.FAudioHighPassFilter;
|
||||
filterParameters.Frequency = 0f;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -151,14 +176,18 @@ namespace MoonWorks.Audio
|
|||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float reverb;
|
||||
public unsafe float Reverb
|
||||
{
|
||||
get => reverb;
|
||||
set
|
||||
internal set
|
||||
{
|
||||
if (ReverbEffect != null)
|
||||
{
|
||||
value = MathF.Max(0, value);
|
||||
if (reverb != value)
|
||||
{
|
||||
reverb = value;
|
||||
|
||||
|
@ -178,6 +207,7 @@ namespace MoonWorks.Audio
|
|||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
if (ReverbEffect == null)
|
||||
|
@ -188,7 +218,7 @@ namespace MoonWorks.Audio
|
|||
}
|
||||
}
|
||||
|
||||
public SoundInstance(
|
||||
public unsafe SoundInstance(
|
||||
AudioDevice device,
|
||||
ushort formatTag,
|
||||
ushort bitsPerSample,
|
||||
|
@ -197,7 +227,7 @@ namespace MoonWorks.Audio
|
|||
uint samplesPerSecond
|
||||
) : base(device)
|
||||
{
|
||||
var format = new FAudio.FAudioWaveFormatEx
|
||||
format = new FAudio.FAudioWaveFormatEx
|
||||
{
|
||||
wFormatTag = formatTag,
|
||||
wBitsPerSample = bitsPerSample,
|
||||
|
@ -207,12 +237,10 @@ namespace MoonWorks.Audio
|
|||
nAvgBytesPerSec = blockAlign * samplesPerSecond
|
||||
};
|
||||
|
||||
Format = format;
|
||||
|
||||
FAudio.FAudio_CreateSourceVoice(
|
||||
Device.Handle,
|
||||
out Voice,
|
||||
ref Format,
|
||||
ref format,
|
||||
FAudio.FAUDIO_VOICE_USEFILTER,
|
||||
FAudio.FAUDIO_DEFAULT_FREQ_RATIO,
|
||||
IntPtr.Zero,
|
||||
|
@ -277,6 +305,91 @@ namespace MoonWorks.Audio
|
|||
ReverbEffect = reverbEffect;
|
||||
}
|
||||
|
||||
public void SetPan(float targetValue)
|
||||
{
|
||||
Pan = targetValue;
|
||||
Device.ClearTweens(weakReference, AudioTweenProperty.Pan);
|
||||
}
|
||||
|
||||
public void SetPan(float targetValue, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, 0);
|
||||
}
|
||||
|
||||
public void SetPan(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, delayTime);
|
||||
}
|
||||
|
||||
public void SetPitch(float targetValue)
|
||||
{
|
||||
Pitch = targetValue;
|
||||
Device.ClearTweens(weakReference, AudioTweenProperty.Pitch);
|
||||
}
|
||||
|
||||
public void SetPitch(float targetValue, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.Pitch, easingFunction, Pan, targetValue, duration, 0);
|
||||
}
|
||||
|
||||
public void SetPitch(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.Pitch, easingFunction, Pan, targetValue, duration, delayTime);
|
||||
}
|
||||
|
||||
public void SetVolume(float targetValue)
|
||||
{
|
||||
Volume = targetValue;
|
||||
Device.ClearTweens(weakReference, AudioTweenProperty.Volume);
|
||||
}
|
||||
|
||||
public void SetVolume(float targetValue, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.Volume, easingFunction, Volume, targetValue, duration, 0);
|
||||
}
|
||||
|
||||
public void SetVolume(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.Volume, easingFunction, Volume, targetValue, duration, delayTime);
|
||||
}
|
||||
|
||||
public void SetFilterFrequency(float targetValue)
|
||||
{
|
||||
FilterFrequency = targetValue;
|
||||
Device.ClearTweens(weakReference, AudioTweenProperty.FilterFrequency);
|
||||
}
|
||||
|
||||
public void SetFilterFrequency(float targetValue, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.FilterFrequency, easingFunction, FilterFrequency, targetValue, duration, 0);
|
||||
}
|
||||
|
||||
public void SetFilterFrequency(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.FilterFrequency, easingFunction, FilterFrequency, targetValue, duration, delayTime);
|
||||
}
|
||||
|
||||
public void SetFilterOneOverQ(float targetValue)
|
||||
{
|
||||
FilterOneOverQ = targetValue;
|
||||
}
|
||||
|
||||
public void SetReverb(float targetValue)
|
||||
{
|
||||
Reverb = targetValue;
|
||||
Device.ClearTweens(weakReference, AudioTweenProperty.Reverb);
|
||||
}
|
||||
|
||||
public void SetReverb(float targetValue, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, 0);
|
||||
}
|
||||
|
||||
public void SetReverb(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, delayTime);
|
||||
}
|
||||
|
||||
public abstract void Play();
|
||||
public abstract void QueueSyncPlay();
|
||||
public abstract void Pause();
|
||||
|
@ -297,14 +410,12 @@ namespace MoonWorks.Audio
|
|||
);
|
||||
|
||||
dspSettings.pMatrixCoefficients = (nint) NativeMemory.Alloc(memsize);
|
||||
unsafe
|
||||
{
|
||||
byte* memPtr = (byte*) dspSettings.pMatrixCoefficients;
|
||||
for (uint i = 0; i < memsize; i += 1)
|
||||
{
|
||||
memPtr[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
SetPanMatrixCoefficients();
|
||||
}
|
||||
|
||||
|
|
|
@ -10,11 +10,21 @@ namespace MoonWorks.Audio
|
|||
/// </summary>
|
||||
public abstract class StreamingSound : SoundInstance
|
||||
{
|
||||
// How big should each buffer we consume be?
|
||||
protected abstract int BUFFER_SIZE { get; }
|
||||
|
||||
// Should the AudioDevice thread automatically update this class?
|
||||
public abstract bool AutoUpdate { get; }
|
||||
|
||||
// Are we actively consuming buffers?
|
||||
protected bool ConsumingBuffers = false;
|
||||
|
||||
private const int BUFFER_COUNT = 3;
|
||||
private readonly IntPtr[] buffers;
|
||||
private int nextBufferIndex = 0;
|
||||
private uint queuedBufferCount = 0;
|
||||
protected abstract int BUFFER_SIZE { get; }
|
||||
|
||||
private readonly object StateLock = new object();
|
||||
|
||||
public unsafe StreamingSound(
|
||||
AudioDevice device,
|
||||
|
@ -25,8 +35,6 @@ namespace MoonWorks.Audio
|
|||
uint samplesPerSecond
|
||||
) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond)
|
||||
{
|
||||
device.AddDynamicSoundInstance(this);
|
||||
|
||||
buffers = new IntPtr[BUFFER_COUNT];
|
||||
for (int i = 0; i < BUFFER_COUNT; i += 1)
|
||||
{
|
||||
|
@ -45,6 +53,8 @@ namespace MoonWorks.Audio
|
|||
}
|
||||
|
||||
private void PlayUsingOperationSet(uint operationSet)
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
if (State == SoundState.Playing)
|
||||
{
|
||||
|
@ -53,40 +63,65 @@ namespace MoonWorks.Audio
|
|||
|
||||
State = SoundState.Playing;
|
||||
|
||||
Update();
|
||||
ConsumingBuffers = true;
|
||||
QueueBuffers();
|
||||
FAudio.FAudioSourceVoice_Start(Voice, 0, operationSet);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Pause()
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
if (State == SoundState.Playing)
|
||||
{
|
||||
ConsumingBuffers = false;
|
||||
FAudio.FAudioSourceVoice_Stop(Voice, 0, 0);
|
||||
State = SoundState.Paused;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
ConsumingBuffers = false;
|
||||
State = SoundState.Stopped;
|
||||
}
|
||||
}
|
||||
|
||||
public override void StopImmediate()
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
ConsumingBuffers = false;
|
||||
FAudio.FAudioSourceVoice_Stop(Voice, 0, 0);
|
||||
FAudio.FAudioSourceVoice_FlushSourceBuffers(Voice);
|
||||
ClearBuffers();
|
||||
|
||||
State = SoundState.Stopped;
|
||||
}
|
||||
}
|
||||
|
||||
internal unsafe void Update()
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (State != SoundState.Playing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QueueBuffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void QueueBuffers()
|
||||
{
|
||||
FAudio.FAudioSourceVoice_GetState(
|
||||
Voice,
|
||||
out var state,
|
||||
|
@ -95,16 +130,18 @@ namespace MoonWorks.Audio
|
|||
|
||||
queuedBufferCount = state.BuffersQueued;
|
||||
|
||||
QueueBuffers();
|
||||
}
|
||||
|
||||
protected void QueueBuffers()
|
||||
if (ConsumingBuffers)
|
||||
{
|
||||
for (int i = 0; i < BUFFER_COUNT - queuedBufferCount; i += 1)
|
||||
{
|
||||
AddBuffer();
|
||||
}
|
||||
}
|
||||
else if (queuedBufferCount == 0)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
|
||||
protected unsafe void ClearBuffers()
|
||||
{
|
||||
|
@ -124,6 +161,8 @@ namespace MoonWorks.Audio
|
|||
out bool reachedEnd
|
||||
);
|
||||
|
||||
if (filledLengthInBytes > 0)
|
||||
{
|
||||
FAudio.FAudioBuffer buf = new FAudio.FAudioBuffer
|
||||
{
|
||||
AudioBytes = (uint) filledLengthInBytes,
|
||||
|
@ -142,19 +181,16 @@ namespace MoonWorks.Audio
|
|||
);
|
||||
|
||||
queuedBufferCount += 1;
|
||||
}
|
||||
|
||||
/* We have reached the end of the file, what do we do? */
|
||||
if (reachedEnd)
|
||||
{
|
||||
/* We have reached the end of the data, what do we do? */
|
||||
ConsumingBuffers = false;
|
||||
OnReachedEnd();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnReachedEnd()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
protected unsafe abstract void FillBuffer(
|
||||
void* buffer,
|
||||
int bufferLengthInBytes, /* in bytes */
|
||||
|
@ -162,7 +198,13 @@ namespace MoonWorks.Audio
|
|||
out bool reachedEnd
|
||||
);
|
||||
|
||||
protected abstract void OnReachedEnd();
|
||||
|
||||
protected unsafe override void Destroy()
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
StopImmediate();
|
||||
|
||||
|
@ -172,4 +214,6 @@ namespace MoonWorks.Audio
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace MoonWorks.Audio
|
|||
private FAudio.stb_vorbis_info Info;
|
||||
|
||||
protected override int BUFFER_SIZE => 32768;
|
||||
public override bool AutoUpdate => true;
|
||||
|
||||
public unsafe static StreamingSoundOgg Load(AudioDevice device, string filePath)
|
||||
{
|
||||
|
@ -35,7 +36,7 @@ namespace MoonWorks.Audio
|
|||
);
|
||||
}
|
||||
|
||||
internal StreamingSoundOgg(
|
||||
internal unsafe StreamingSoundOgg(
|
||||
AudioDevice device,
|
||||
IntPtr fileDataPtr, // MUST BE A NATIVE MEMORY HANDLE!!
|
||||
IntPtr vorbisHandle,
|
||||
|
@ -47,8 +48,7 @@ namespace MoonWorks.Audio
|
|||
(ushort) (4 * info.channels),
|
||||
(ushort) info.channels,
|
||||
info.sample_rate
|
||||
)
|
||||
{
|
||||
) {
|
||||
FileDataPtr = fileDataPtr;
|
||||
VorbisHandle = vorbisHandle;
|
||||
Info = info;
|
||||
|
@ -64,8 +64,7 @@ namespace MoonWorks.Audio
|
|||
int bufferLengthInBytes,
|
||||
out int filledLengthInBytes,
|
||||
out bool reachedEnd
|
||||
)
|
||||
{
|
||||
) {
|
||||
var lengthInFloats = bufferLengthInBytes / sizeof(float);
|
||||
|
||||
/* NOTE: this function returns samples per channel, not total samples */
|
||||
|
@ -82,9 +81,14 @@ namespace MoonWorks.Audio
|
|||
}
|
||||
|
||||
protected unsafe override void Destroy()
|
||||
{
|
||||
base.Destroy();
|
||||
|
||||
if (!IsDisposed)
|
||||
{
|
||||
FAudio.stb_vorbis_close(VorbisHandle);
|
||||
NativeMemory.Free((void*) FileDataPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,12 +14,9 @@ namespace MoonWorks.Audio
|
|||
{
|
||||
if (Loop)
|
||||
{
|
||||
ConsumingBuffers = true;
|
||||
Seek(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -168,9 +168,8 @@ namespace MoonWorks
|
|||
while (accumulatedUpdateTime >= Timestep)
|
||||
{
|
||||
Inputs.Update();
|
||||
AudioDevice.Update();
|
||||
|
||||
Update(Timestep);
|
||||
AudioDevice.WakeThread();
|
||||
|
||||
accumulatedUpdateTime -= Timestep;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace MoonWorks.Graphics
|
|||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
private readonly List<WeakReference<GraphicsResource>> resources = new List<WeakReference<GraphicsResource>>();
|
||||
private readonly HashSet<WeakReference<GraphicsResource>> resources = new HashSet<WeakReference<GraphicsResource>>();
|
||||
|
||||
public GraphicsDevice(
|
||||
Backend preferredBackend,
|
||||
|
@ -237,10 +237,9 @@ namespace MoonWorks.Graphics
|
|||
{
|
||||
lock (resources)
|
||||
{
|
||||
for (var i = resources.Count - 1; i >= 0; i--)
|
||||
foreach (var weakReference in resources)
|
||||
{
|
||||
var resource = resources[i];
|
||||
if (resource.TryGetTarget(out var target))
|
||||
if (weakReference.TryGetTarget(out var target))
|
||||
{
|
||||
target.Dispose();
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace MoonWorks.Graphics
|
|||
public bool IsDisposed { get; private set; }
|
||||
protected abstract Action<IntPtr, IntPtr> QueueDestroyFunction { get; }
|
||||
|
||||
private WeakReference<GraphicsResource> selfReference;
|
||||
internal WeakReference<GraphicsResource> weakReference;
|
||||
|
||||
public GraphicsResource(GraphicsDevice device, bool trackResource = true)
|
||||
{
|
||||
|
@ -18,8 +18,8 @@ namespace MoonWorks.Graphics
|
|||
|
||||
if (trackResource)
|
||||
{
|
||||
selfReference = new WeakReference<GraphicsResource>(this);
|
||||
Device.AddResourceReference(selfReference);
|
||||
weakReference = new WeakReference<GraphicsResource>(this);
|
||||
Device.AddResourceReference(weakReference);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,11 +27,11 @@ namespace MoonWorks.Graphics
|
|||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (selfReference != null)
|
||||
if (weakReference != null)
|
||||
{
|
||||
QueueDestroyFunction(Device.Handle, Handle);
|
||||
Device.RemoveResourceReference(selfReference);
|
||||
selfReference = null;
|
||||
Device.RemoveResourceReference(weakReference);
|
||||
weakReference = null;
|
||||
}
|
||||
|
||||
IsDisposed = true;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -7,6 +7,8 @@ namespace MoonWorks.Video
|
|||
{
|
||||
private IntPtr VideoHandle;
|
||||
protected override int BUFFER_SIZE => 8192;
|
||||
// Theorafile is not thread safe, so let's update on the main thread.
|
||||
public override bool AutoUpdate => false;
|
||||
|
||||
internal StreamingSoundTheora(
|
||||
AudioDevice device,
|
||||
|
@ -32,6 +34,11 @@ namespace MoonWorks.Video
|
|||
) {
|
||||
var lengthInFloats = bufferLengthInBytes / sizeof(float);
|
||||
|
||||
// FIXME: this gets gnarly with theorafile being not thread safe
|
||||
// is there some way we could just manually update in VideoPlayer
|
||||
// instead of going through AudioDevice?
|
||||
lock (Device.StateLock)
|
||||
{
|
||||
int samples = Theorafile.tf_readaudio(
|
||||
VideoHandle,
|
||||
(IntPtr) buffer,
|
||||
|
@ -42,4 +49,7 @@ namespace MoonWorks.Video
|
|||
reachedEnd = Theorafile.tf_eos(VideoHandle) == 1;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnReachedEnd() { }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ namespace MoonWorks.Video
|
|||
private int yWidth;
|
||||
private int yHeight;
|
||||
|
||||
private bool disposed;
|
||||
private bool IsDisposed;
|
||||
|
||||
public Video(string filename)
|
||||
{
|
||||
|
@ -89,7 +89,7 @@ namespace MoonWorks.Video
|
|||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposed)
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
|
@ -100,7 +100,7 @@ namespace MoonWorks.Video
|
|||
Theorafile.tf_close(ref Handle);
|
||||
NativeMemory.Free(videoData);
|
||||
|
||||
disposed = true;
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using MoonWorks.Audio;
|
||||
|
@ -130,6 +130,8 @@ namespace MoonWorks.Video
|
|||
|
||||
public void Play()
|
||||
{
|
||||
if (Video == null) { return; }
|
||||
|
||||
if (State == VideoState.Playing)
|
||||
{
|
||||
return;
|
||||
|
@ -147,6 +149,8 @@ namespace MoonWorks.Video
|
|||
|
||||
public void Pause()
|
||||
{
|
||||
if (Video == null) { return; }
|
||||
|
||||
if (State != VideoState.Playing)
|
||||
{
|
||||
return;
|
||||
|
@ -164,6 +168,8 @@ namespace MoonWorks.Video
|
|||
|
||||
public void Stop()
|
||||
{
|
||||
if (Video == null) { return; }
|
||||
|
||||
if (State == VideoState.Stopped)
|
||||
{
|
||||
return;
|
||||
|
@ -172,20 +178,32 @@ namespace MoonWorks.Video
|
|||
timer.Stop();
|
||||
timer.Reset();
|
||||
|
||||
Theorafile.tf_reset(Video.Handle);
|
||||
lastTimestamp = 0;
|
||||
timeElapsed = 0;
|
||||
|
||||
if (audioStream != null)
|
||||
{
|
||||
audioStream.StopImmediate();
|
||||
audioStream.Dispose();
|
||||
audioStream = null;
|
||||
}
|
||||
DestroyAudioStream();
|
||||
|
||||
Theorafile.tf_reset(Video.Handle);
|
||||
|
||||
State = VideoState.Stopped;
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
Stop();
|
||||
Video = null;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (Video == null) { return; }
|
||||
|
||||
if (audioStream != null)
|
||||
{
|
||||
audioStream.Update();
|
||||
}
|
||||
}
|
||||
|
||||
public void Render()
|
||||
{
|
||||
if (Video == null || State == VideoState.Stopped)
|
||||
|
@ -203,7 +221,8 @@ namespace MoonWorks.Video
|
|||
Video.Handle,
|
||||
(IntPtr) yuvData,
|
||||
thisFrame - currentFrame
|
||||
) == 1 || currentFrame == -1) {
|
||||
) == 1 || currentFrame == -1)
|
||||
{
|
||||
UpdateRenderTexture();
|
||||
}
|
||||
|
||||
|
@ -216,12 +235,7 @@ namespace MoonWorks.Video
|
|||
timer.Stop();
|
||||
timer.Reset();
|
||||
|
||||
if (audioStream != null)
|
||||
{
|
||||
audioStream.Stop();
|
||||
audioStream.Dispose();
|
||||
audioStream = null;
|
||||
}
|
||||
DestroyAudioStream();
|
||||
|
||||
Theorafile.tf_reset(Video.Handle);
|
||||
|
||||
|
@ -299,14 +313,27 @@ namespace MoonWorks.Video
|
|||
// Grab the first bit of audio. We're trying to start the decoding ASAP.
|
||||
if (AudioDevice != null && Theorafile.tf_hasaudio(Video.Handle) == 1)
|
||||
{
|
||||
DestroyAudioStream();
|
||||
|
||||
int channels, sampleRate;
|
||||
Theorafile.tf_audioinfo(Video.Handle, out channels, out sampleRate);
|
||||
|
||||
audioStream = new StreamingSoundTheora(AudioDevice, Video.Handle, channels, (uint) sampleRate);
|
||||
}
|
||||
|
||||
currentFrame = -1;
|
||||
}
|
||||
|
||||
private void DestroyAudioStream()
|
||||
{
|
||||
if (audioStream != null)
|
||||
{
|
||||
audioStream.StopImmediate();
|
||||
audioStream.Dispose();
|
||||
audioStream = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposed)
|
||||
|
|
Loading…
Reference in New Issue