From 1f0e3b5040624d49f708e2ecde550d6830fe40d3 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Tue, 7 Mar 2023 23:28:57 +0000 Subject: [PATCH] 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: https://gitea.moonside.games/MoonsideGames/MoonWorks/pulls/47 --- src/Audio/AudioDevice.cs | 167 +++-- src/Audio/AudioResource.cs | 12 +- src/Audio/AudioTween.cs | 57 ++ src/Audio/AudioTweenManager.cs | 169 +++++ src/Audio/ReverbEffect.cs | 33 +- src/Audio/SoundInstance.cs | 303 ++++++--- src/Audio/StreamingSound.cs | 150 +++-- src/Audio/StreamingSoundOgg.cs | 18 +- src/Audio/StreamingSoundSeekable.cs | 5 +- src/Game.cs | 3 +- src/Graphics/GraphicsDevice.cs | 7 +- src/Graphics/GraphicsResource.cs | 12 +- src/Math/Easing.cs | 918 +++++++++++++--------------- src/Video/StreamingSoundTheora.cs | 24 +- src/Video/Video.cs | 6 +- src/Video/VideoPlayer.cs | 57 +- 16 files changed, 1181 insertions(+), 760 deletions(-) create mode 100644 src/Audio/AudioTween.cs create mode 100644 src/Audio/AudioTweenManager.cs diff --git a/src/Audio/AudioDevice.cs b/src/Audio/AudioDevice.cs index aec8b721..a0e4bf74 100644 --- a/src/Audio/AudioDevice.cs +++ b/src/Audio/AudioDevice.cs @@ -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> resources = new List>(); - private readonly List> streamingSounds = new List>(); + private readonly HashSet resources = new HashSet(); + private readonly HashSet autoUpdateStreamingSoundReferences = new HashSet(); + + 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,53 +170,108 @@ namespace MoonWorks.Audio FAudio.FAudio_CommitChanges(Handle, 1); } - internal void AddDynamicSoundInstance(StreamingSound instance) - { - streamingSounds.Add(new WeakReference(instance)); - } - - internal void AddResourceReference(WeakReference resourceReference) - { - lock (resources) + internal void CreateTween( + SoundInstance soundInstance, + AudioTweenProperty property, + System.Func easingFunction, + float start, + float end, + float duration, + float delayTime + ) { + lock (StateLock) { - resources.Add(resourceReference); + AudioTweenManager.CreateTween( + soundInstance, + property, + easingFunction, + start, + end, + duration, + delayTime + ); } } - internal void RemoveResourceReference(WeakReference resourceReference) - { - lock (resources) + internal void ClearTweens( + WeakReference soundReference, + AudioTweenProperty property + ) { + lock (StateLock) { - 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) { - if (disposing) + lock (StateLock) { - for (var i = resources.Count - 1; i >= 0; i--) + if (disposing) { - var weakReference = resources[i]; - - if (weakReference.TryGetTarget(out var resource)) + foreach (var weakReference in resources) { - resource.Dispose(); + var target = weakReference.Target; + + if (target != null) + { + (target as IDisposable).Dispose(); + } } + resources.Clear(); } - resources.Clear(); + + FAudio.FAudioVoice_DestroyVoice(MasteringVoice); + FAudio.FAudio_Release(Handle); + + IsDisposed = true; } - - FAudio.FAudioVoice_DestroyVoice(MasteringVoice); - FAudio.FAudio_Release(Handle); - - 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 diff --git a/src/Audio/AudioResource.cs b/src/Audio/AudioResource.cs index edf8b497..045fe264 100644 --- a/src/Audio/AudioResource.cs +++ b/src/Audio/AudioResource.cs @@ -8,14 +8,14 @@ namespace MoonWorks.Audio public bool IsDisposed { get; private set; } - private WeakReference selfReference; + internal WeakReference weakReference; public AudioResource(AudioDevice device) { Device = device; - selfReference = new WeakReference(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; diff --git a/src/Audio/AudioTween.cs b/src/Audio/AudioTween.cs new file mode 100644 index 00000000..45853908 --- /dev/null +++ b/src/Audio/AudioTween.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using EasingFunction = System.Func; + +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 Tweens = new Queue(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); + } + } +} diff --git a/src/Audio/AudioTweenManager.cs b/src/Audio/AudioTweenManager.cs new file mode 100644 index 00000000..2f5932c0 --- /dev/null +++ b/src/Audio/AudioTweenManager.cs @@ -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 DelayedAudioTweens = new List(); + + 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 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; + } + } +} diff --git a/src/Audio/ReverbEffect.cs b/src/Audio/ReverbEffect.cs index 303bda74..42fab2ce 100644 --- a/src/Audio/ReverbEffect.cs +++ b/src/Audio/ReverbEffect.cs @@ -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); } } } diff --git a/src/Audio/SoundInstance.cs b/src/Audio/SoundInstance.cs index b82fbf2b..10e36503 100644 --- a/src/Audio/SoundInstance.cs +++ b/src/Audio/SoundInstance.cs @@ -1,12 +1,15 @@ using System; using System.Runtime.InteropServices; +using EasingFunction = System.Func; 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,30 +24,34 @@ namespace MoonWorks.Audio public float Pan { get => pan; - set + internal set { - pan = value; - - if (pan < -1f) + value = Math.MathHelper.Clamp(value, -1f, 1f); + if (pan != value) { - pan = -1f; - } - if (pan > 1f) - { - pan = 1f; - } + pan = value; - if (Is3D) { return; } + if (pan < -1f) + { + pan = -1f; + } + if (pan > 1f) + { + pan = 1f; + } - SetPanMatrixCoefficients(); - FAudio.FAudioVoice_SetOutputMatrix( - Voice, - Device.MasteringVoice, - dspSettings.SrcChannelCount, - dspSettings.DstChannelCount, - dspSettings.pMatrixCoefficients, - 0 - ); + if (Is3D) { return; } + + SetPanMatrixCoefficients(); + FAudio.FAudioVoice_SetOutputMatrix( + Voice, + Device.MasteringVoice, + dspSettings.SrcChannelCount, + dspSettings.DstChannelCount, + dspSettings.pMatrixCoefficients, + 0 + ); + } } } @@ -52,10 +59,14 @@ namespace MoonWorks.Audio public float Pitch { get => pitch; - set + internal set { - pitch = Math.MathHelper.Clamp(value, -1f, 1f); - UpdatePitch(); + value = Math.MathHelper.Clamp(value, -1f, 1f); + if (pitch != value) + { + pitch = value; + UpdatePitch(); + } } } @@ -63,10 +74,14 @@ namespace MoonWorks.Audio public float Volume { get => volume; - set + internal set { - volume = value; - FAudio.FAudioVoice_SetVolume(Voice, volume, 0); + value = Math.MathHelper.Max(0, value); + if (volume != value) + { + volume = value; + FAudio.FAudioVoice_SetVolume(Voice, volume, 0); + } } } @@ -80,35 +95,41 @@ 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); - filterParameters.Frequency = value; + if (filterParameters.Frequency != value) + { + filterParameters.Frequency = value; - FAudio.FAudioVoice_SetFilterParameters( - Voice, - ref filterParameters, - 0 - ); + FAudio.FAudioVoice_SetFilterParameters( + Voice, + ref filterParameters, + 0 + ); + } } } - private float FilterOneOverQ + public float FilterOneOverQ { get => filterParameters.OneOverQ; - set + internal set { value = System.Math.Clamp(value, 0.01f, MAX_FILTER_ONEOVERQ); - filterParameters.OneOverQ = value; + if (filterParameters.OneOverQ != value) + { + filterParameters.OneOverQ = value; - FAudio.FAudioVoice_SetFilterParameters( - Voice, - ref filterParameters, - 0 - ); + FAudio.FAudioVoice_SetFilterParameters( + Voice, + ref filterParameters, + 0 + ); + } } } @@ -118,37 +139,42 @@ namespace MoonWorks.Audio get => filterType; set { - filterType = value; - - switch (filterType) + if (filterType != value) { - case FilterType.None: - filterParameters = new FAudio.FAudioFilterParameters - { - Type = FAudio.FAudioFilterType.FAudioLowPassFilter, - Frequency = 1f, - OneOverQ = 1f - }; - break; + filterType = value; - case FilterType.LowPass: - filterParameters.Type = FAudio.FAudioFilterType.FAudioLowPassFilter; - break; + switch (filterType) + { + case FilterType.None: + filterParameters = new FAudio.FAudioFilterParameters + { + Type = FAudio.FAudioFilterType.FAudioLowPassFilter, + Frequency = 1f, + OneOverQ = 1f + }; + break; - case FilterType.BandPass: - filterParameters.Type = FAudio.FAudioFilterType.FAudioBandPassFilter; - break; + case FilterType.LowPass: + filterParameters.Type = FAudio.FAudioFilterType.FAudioLowPassFilter; + filterParameters.Frequency = 1f; + break; - case FilterType.HighPass: - filterParameters.Type = FAudio.FAudioFilterType.FAudioHighPassFilter; - break; + case FilterType.BandPass: + filterParameters.Type = FAudio.FAudioFilterType.FAudioBandPassFilter; + break; + + case FilterType.HighPass: + filterParameters.Type = FAudio.FAudioFilterType.FAudioHighPassFilter; + filterParameters.Frequency = 0f; + break; + } + + FAudio.FAudioVoice_SetFilterParameters( + Voice, + ref filterParameters, + 0 + ); } - - FAudio.FAudioVoice_SetFilterParameters( - Voice, - ref filterParameters, - 0 - ); } } @@ -156,27 +182,31 @@ namespace MoonWorks.Audio public unsafe float Reverb { get => reverb; - set + internal set { if (ReverbEffect != null) { - reverb = value; - - float* outputMatrix = (float*) dspSettings.pMatrixCoefficients; - outputMatrix[0] = reverb; - if (dspSettings.SrcChannelCount == 2) + value = MathF.Max(0, value); + if (reverb != value) { - outputMatrix[1] = reverb; - } + reverb = value; - FAudio.FAudioVoice_SetOutputMatrix( - Voice, - ReverbEffect.Voice, - dspSettings.SrcChannelCount, - 1, - dspSettings.pMatrixCoefficients, - 0 - ); + float* outputMatrix = (float*) dspSettings.pMatrixCoefficients; + outputMatrix[0] = reverb; + if (dspSettings.SrcChannelCount == 2) + { + outputMatrix[1] = reverb; + } + + FAudio.FAudioVoice_SetOutputMatrix( + Voice, + ReverbEffect.Voice, + dspSettings.SrcChannelCount, + 1, + dspSettings.pMatrixCoefficients, + 0 + ); + } } #if DEBUG @@ -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) { - byte* memPtr = (byte*) dspSettings.pMatrixCoefficients; - for (uint i = 0; i < memsize; i += 1) - { - memPtr[i] = 0; - } + memPtr[i] = 0; } + SetPanMatrixCoefficients(); } diff --git a/src/Audio/StreamingSound.cs b/src/Audio/StreamingSound.cs index 6d88f68f..2f3630da 100644 --- a/src/Audio/StreamingSound.cs +++ b/src/Audio/StreamingSound.cs @@ -10,11 +10,21 @@ namespace MoonWorks.Audio /// 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) { @@ -46,47 +54,74 @@ namespace MoonWorks.Audio private void PlayUsingOperationSet(uint operationSet) { - if (State == SoundState.Playing) + lock (StateLock) { - return; + if (State == SoundState.Playing) + { + return; + } + + State = SoundState.Playing; + + ConsumingBuffers = true; + QueueBuffers(); + FAudio.FAudioSourceVoice_Start(Voice, 0, operationSet); } - - State = SoundState.Playing; - - Update(); - FAudio.FAudioSourceVoice_Start(Voice, 0, operationSet); } public override void Pause() { - if (State == SoundState.Playing) + lock (StateLock) { - FAudio.FAudioSourceVoice_Stop(Voice, 0, 0); - State = SoundState.Paused; + if (State == SoundState.Playing) + { + ConsumingBuffers = false; + FAudio.FAudioSourceVoice_Stop(Voice, 0, 0); + State = SoundState.Paused; + } } } public override void Stop() { - State = SoundState.Stopped; + lock (StateLock) + { + ConsumingBuffers = false; + State = SoundState.Stopped; + } } public override void StopImmediate() { - FAudio.FAudioSourceVoice_Stop(Voice, 0, 0); - FAudio.FAudioSourceVoice_FlushSourceBuffers(Voice); - ClearBuffers(); + lock (StateLock) + { + ConsumingBuffers = false; + FAudio.FAudioSourceVoice_Stop(Voice, 0, 0); + FAudio.FAudioSourceVoice_FlushSourceBuffers(Voice); + ClearBuffers(); - State = SoundState.Stopped; + State = SoundState.Stopped; + } } internal unsafe void Update() { - if (State != SoundState.Playing) + lock (StateLock) { - return; - } + if (!IsDisposed) + { + if (State != SoundState.Playing) + { + return; + } + QueueBuffers(); + } + } + } + + protected void QueueBuffers() + { FAudio.FAudioSourceVoice_GetState( Voice, out var state, @@ -95,14 +130,16 @@ namespace MoonWorks.Audio queuedBufferCount = state.BuffersQueued; - QueueBuffers(); - } - - protected void QueueBuffers() - { - for (int i = 0; i < BUFFER_COUNT - queuedBufferCount; i += 1) + if (ConsumingBuffers) { - AddBuffer(); + for (int i = 0; i < BUFFER_COUNT - queuedBufferCount; i += 1) + { + AddBuffer(); + } + } + else if (queuedBufferCount == 0) + { + Stop(); } } @@ -124,37 +161,36 @@ namespace MoonWorks.Audio out bool reachedEnd ); - FAudio.FAudioBuffer buf = new FAudio.FAudioBuffer + if (filledLengthInBytes > 0) { - AudioBytes = (uint) filledLengthInBytes, - pAudioData = (IntPtr) buffer, - PlayLength = ( - (uint) (filledLengthInBytes / - Format.nChannels / - (uint) (Format.wBitsPerSample / 8)) - ) - }; + FAudio.FAudioBuffer buf = new FAudio.FAudioBuffer + { + AudioBytes = (uint) filledLengthInBytes, + pAudioData = (IntPtr) buffer, + PlayLength = ( + (uint) (filledLengthInBytes / + Format.nChannels / + (uint) (Format.wBitsPerSample / 8)) + ) + }; - FAudio.FAudioSourceVoice_SubmitSourceBuffer( - Voice, - ref buf, - IntPtr.Zero - ); + FAudio.FAudioSourceVoice_SubmitSourceBuffer( + Voice, + ref buf, + IntPtr.Zero + ); - queuedBufferCount += 1; + 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,13 +198,21 @@ namespace MoonWorks.Audio out bool reachedEnd ); + protected abstract void OnReachedEnd(); + protected unsafe override void Destroy() { - StopImmediate(); - - for (int i = 0; i < BUFFER_COUNT; i += 1) + lock (StateLock) { - NativeMemory.Free((void*) buffers[i]); + if (!IsDisposed) + { + StopImmediate(); + + for (int i = 0; i < BUFFER_COUNT; i += 1) + { + NativeMemory.Free((void*) buffers[i]); + } + } } } } diff --git a/src/Audio/StreamingSoundOgg.cs b/src/Audio/StreamingSoundOgg.cs index 1d37c09d..882d00ec 100644 --- a/src/Audio/StreamingSoundOgg.cs +++ b/src/Audio/StreamingSoundOgg.cs @@ -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 */ @@ -83,8 +82,13 @@ namespace MoonWorks.Audio protected unsafe override void Destroy() { - FAudio.stb_vorbis_close(VorbisHandle); - NativeMemory.Free((void*) FileDataPtr); + base.Destroy(); + + if (!IsDisposed) + { + FAudio.stb_vorbis_close(VorbisHandle); + NativeMemory.Free((void*) FileDataPtr); + } } } } diff --git a/src/Audio/StreamingSoundSeekable.cs b/src/Audio/StreamingSoundSeekable.cs index 7ca563b6..cf8beb4f 100644 --- a/src/Audio/StreamingSoundSeekable.cs +++ b/src/Audio/StreamingSoundSeekable.cs @@ -14,12 +14,9 @@ namespace MoonWorks.Audio { if (Loop) { + ConsumingBuffers = true; Seek(0); } - else - { - Stop(); - } } } } diff --git a/src/Game.cs b/src/Game.cs index b148d443..56b2cf9a 100644 --- a/src/Game.cs +++ b/src/Game.cs @@ -168,9 +168,8 @@ namespace MoonWorks while (accumulatedUpdateTime >= Timestep) { Inputs.Update(); - AudioDevice.Update(); - Update(Timestep); + AudioDevice.WakeThread(); accumulatedUpdateTime -= Timestep; } diff --git a/src/Graphics/GraphicsDevice.cs b/src/Graphics/GraphicsDevice.cs index f821a6d3..c1057d61 100644 --- a/src/Graphics/GraphicsDevice.cs +++ b/src/Graphics/GraphicsDevice.cs @@ -18,7 +18,7 @@ namespace MoonWorks.Graphics public bool IsDisposed { get; private set; } - private readonly List> resources = new List>(); + private readonly HashSet> resources = new HashSet>(); 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(); } diff --git a/src/Graphics/GraphicsResource.cs b/src/Graphics/GraphicsResource.cs index 2e685461..0819a637 100644 --- a/src/Graphics/GraphicsResource.cs +++ b/src/Graphics/GraphicsResource.cs @@ -10,7 +10,7 @@ namespace MoonWorks.Graphics public bool IsDisposed { get; private set; } protected abstract Action QueueDestroyFunction { get; } - private WeakReference selfReference; + internal WeakReference weakReference; public GraphicsResource(GraphicsDevice device, bool trackResource = true) { @@ -18,8 +18,8 @@ namespace MoonWorks.Graphics if (trackResource) { - selfReference = new WeakReference(this); - Device.AddResourceReference(selfReference); + weakReference = new WeakReference(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; diff --git a/src/Math/Easing.cs b/src/Math/Easing.cs index d052c816..7a40cf9e 100644 --- a/src/Math/Easing.cs +++ b/src/Math/Easing.cs @@ -1,45 +1,53 @@ using MoonWorks.Math.Fixed; -using EasingFunctionFloat = System.Func; -using EasingFunctionFixed = System.Func; using System.Collections.Generic; namespace MoonWorks.Math { public static class Easing { + private const float C1 = 1.70158f; + private const float C2 = C1 * 1.525f; + private const float C3 = C1 + 1; + private const float C4 = (2 * System.MathF.PI) / 3; + private const float C5 = (2 * System.MathF.PI) / 4.5f; + + private static readonly Fix64 HALF = Fix64.FromFraction(1, 2); + private static readonly Fix64 FIXED_C1 = Fix64.FromFraction(170158, 100000); + private static readonly Fix64 FIXED_C2 = FIXED_C1 * Fix64.FromFraction(61, 40); + private static readonly Fix64 FIXED_C3 = FIXED_C1 + Fix64.One; + private static readonly Fix64 FIXED_C4 = Fix64.PiTimes2 / new Fix64(3); + private static readonly Fix64 FIXED_C5 = Fix64.PiTimes2 / Fix64.FromFraction(9, 2); + + private static readonly Fix64 FIXED_N1 = Fix64.FromFraction(121, 16); + private static readonly Fix64 FIXED_D1 = Fix64.FromFraction(11, 4); + private static float OutIn( - EasingFunctionFloat outFunc, - EasingFunctionFloat inFunc, - float start, - float end, - float time, - float duration + System.Func outFunc, + System.Func inFunc, + float t ) { - if (time < duration / 2) + if (t < 0.5f) { - return outFunc(start, end / 2, time * 2, duration); + return outFunc(t); } else { - return inFunc(start + (end / 2), end / 2, (time * 2) - duration, duration); + return inFunc(t); } } private static Fix64 OutIn( - EasingFunctionFixed outFunc, - EasingFunctionFixed inFunc, - Fix64 start, - Fix64 end, - Fix64 time, - Fix64 duration + System.Func outFunc, + System.Func inFunc, + Fix64 t ) { - if (time < duration / 2) + if (t < HALF) { - return outFunc(start, end / 2, time * 2, duration); + return outFunc(t); } else { - return inFunc(start + (end / 2), end / 2, (time * 2) - duration, duration); + return inFunc(t); } } @@ -51,14 +59,14 @@ namespace MoonWorks.Math float end, float time, float attackDuration, - EasingFunctionFloat attackEasingFunction, + Function.Float attackEasingFunction, float holdDuration, float releaseDuration, - EasingFunctionFloat releaseEasingFunction + Function.Float releaseEasingFunction ) { if (time < attackDuration) { - return attackEasingFunction.Invoke(start, hold, time, attackDuration); + return Interp(start, hold, time, attackDuration, Function.Get(attackEasingFunction)); } else if (time >= attackDuration && time < holdDuration) { @@ -66,7 +74,7 @@ namespace MoonWorks.Math } else // time >= attackDuration + holdDuration { - return releaseEasingFunction.Invoke(hold, end, time - holdDuration - attackDuration, releaseDuration); + return Interp(hold, end, time - holdDuration - attackDuration, releaseDuration, Function.Get(releaseEasingFunction)); } } @@ -76,14 +84,14 @@ namespace MoonWorks.Math Fix64 end, Fix64 time, Fix64 attackDuration, - EasingFunctionFixed attackEasingFunction, + Function.Fixed attackEasingFunction, Fix64 holdDuration, Fix64 releaseDuration, - EasingFunctionFixed releaseEasingFunction + Function.Fixed releaseEasingFunction ) { if (time < attackDuration) { - return attackEasingFunction.Invoke(start, hold, time, attackDuration); + return Interp(start, hold, time, attackDuration, Function.Get(attackEasingFunction)); } else if (time >= attackDuration && time < holdDuration) { @@ -91,688 +99,612 @@ namespace MoonWorks.Math } else // time >= attackDuration + holdDuration { - return releaseEasingFunction.Invoke(hold, end, time - holdDuration - attackDuration, releaseDuration); + return Interp(hold, end, time - holdDuration - attackDuration, releaseDuration, Function.Get(releaseEasingFunction)); } } - /* EASING FUNCTIONS */ + public static float Lerp(float start, float end, float time) + { + return (start + (end - start) * time); + } + + public static float Interp(float start, float end, float time, float duration, System.Func easingFunc) + { + return Lerp(start, end, easingFunc(time / duration)); + } + + public static float Interp(float start, float end, float time, float duration, MoonWorks.Math.Easing.Function.Float easingFunc) + { + return Interp(start, end, time, duration, Function.Get(easingFunc)); + } + + public static Fix64 Lerp(Fix64 start, Fix64 end, Fix64 time) + { + return (start + (end - start) * time); + } + + public static Fix64 Interp(Fix64 start, Fix64 end, Fix64 time, Fix64 duration, System.Func easingFunc) + { + return Lerp(start, end, easingFunc(time / duration)); + } + + public static Fix64 Interp(Fix64 start, Fix64 end, Fix64 time, Fix64 duration, MoonWorks.Math.Easing.Function.Fixed easingFunc) + { + return Interp(start, end, time, duration, Function.Get(easingFunc)); + } + + /* FLOAT EASING FUNCTIONS */ // LINEAR - public static float Linear(float start, float end, float time, float duration) + public static float Linear(float t) { - return (end * (time / duration)) + start; - } - - public static Fix64 Linear(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) - { - return (end * (time / duration)) + start; + return t; } // QUADRATIC - public static float InQuad(float start, float end, float time, float duration) + public static float InQuad(float t) { - time /= duration; - return (end * (time * time)) + start; + return t * t; } - public static float OutQuad(float start, float end, float time, float duration) + public static float OutQuad(float t) { - time /= duration; - return (-end * time * (time - 2)) + start; + return 1 - (1 - t) * (1 - t); } - public static float InOutQuad(float start, float end, float time, float duration) + public static float InOutQuad(float t) { - time = time / duration * 2; - if (time < 1) + if (t < 0.5f) { - return (end / 2 * (time * time)) + start; + return 2 * t * t; } else { - return (-end / 2 * (((time - 1) * (time - 3)) - 1)) + start; + var x = (-2 * t + 2); + return 1 - ((x * x) / 2); } } - public static float OutInQuad(float start, float end, float time, float duration) => OutIn(OutQuad, InQuad, time, start, end, duration); - - public static Fix64 InQuad(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) - { - time /= duration; - return (end * (time * time)) + start; - } - - public static Fix64 OutQuad(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) - { - time /= duration; - return (-end * time * (time - 2)) + start; - } - - public static Fix64 InOutQuad(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) - { - time = time / duration * 2; - if (time < 1) - { - return (end / 2 * (time * time)) + start; - } - else - { - return (-end / 2 * (((time - 1) * (time - 3)) - 1)) + start; - } - } - - public static Fix64 OutInQuad(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) => OutIn(OutQuad, InQuad, time, start, end, duration); - + public static float OutInQuad(float t) => OutIn(OutQuad, InQuad, t); // CUBIC - public static float InCubic(float start, float end, float time, float duration) + public static float InCubic(float t) { - time /= duration; - return (end * (time * time * time)) + start; + return t * t * t; } - public static float OutCubic(float start, float end, float time, float duration) + public static float OutCubic(float t) { - time = (time / duration) - 1; - return (end * ((time * time * time) + 1)) + start; + var x = 1 - t; + return 1 - (x * x * x); } - public static float InOutCubic(float start, float end, float time, float duration) + public static float InOutCubic(float t) { - time = time / duration * 2; - if (time < 1) + if (t < 0.5f) { - return (end / 2 * time * time * time) + start; + return 4 * t * t * t; } else { - time -= 2; - return (end / 2 * ((time * time * time) + 2)) + start; + var x = -2 * t + 2; + return 1 - ((x * x * x) / 2); } } - public static float OutInCubic(float start, float end, float time, float duration) => OutIn(OutCubic, InCubic, start, end, time, duration); - - public static Fix64 InCubic(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) - { - time /= duration; - return (end * (time * time * time)) + start; - } - - public static Fix64 OutCubic(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) - { - time = (time / duration) - 1; - return (end * ((time * time * time) + 1)) + start; - } - - public static Fix64 InOutCubic(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) - { - time = time / duration * 2; - if (time < 1) - { - return (end / 2 * time * time * time) + start; - } - else - { - time -= 2; - return (end / 2 * ((time * time * time) + 2)) + start; - } - } - - public static Fix64 OutInCubic(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) => OutIn(OutCubic, InCubic, start, end, time, duration); + public static float OutInCubic(float t) => OutIn(OutCubic, InCubic, t); // QUARTIC - public static float InQuart(float start, float end, float time, float duration) + public static float InQuart(float t) { - time /= duration; - return (end * (time * time * time * time)) + start; + return t * t * t * t; } - public static float OutQuart(float start, float end, float time, float duration) + public static float OutQuart(float t) { - time = (time / duration) - 1; - return (-end * ((time * time * time * time) - 1)) + start; + var x = 1 - t; + return 1 - (x * x * x * x); } - public static float InOutQuart(float start, float end, float time, float duration) + public static float InOutQuart(float t) { - time = time / duration * 2; - if (time < 1) + if (t < 0.5f) { - return (end / 2 * (time * time * time * time)) + start; + return 8 * t * t * t * t; } else { - time -= 2; - return (-end / 2 * ((time * time * time * time) - 2)) + start; + var x = -2 * t + 2; + return 1 - ((x * x * x * x) / 2); } } - public static float OutInQuart(float start, float end, float time, float duration) => OutIn(OutQuart, InQuart, time, start, end, duration); - - public static Fix64 InQuart(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) - { - time /= duration; - return (end * (time * time * time * time)) + start; - } - - public static Fix64 OutQuart(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) - { - time = (time / duration) - 1; - return (-end * ((time * time * time * time) - 1)) + start; - } - - public static Fix64 InOutQuart(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) - { - time = time / duration * 2; - if (time < 1) - { - return (end / 2 * (time * time * time * time)) + start; - } - else - { - time -= 2; - return (-end / 2 * ((time * time * time * time) - 2)) + start; - } - } - - public static Fix64 OutInQuart(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) => OutIn(OutQuart, InQuart, time, start, end, duration); + public static float OutInQuart(float t) => OutIn(OutQuart, InQuart, t); // QUINTIC - public static float InQuint(float start, float end, float time, float duration) + public static float InQuint(float t) { - time /= duration; - return (end * (time * time * time * time * time)) + start; + return t * t * t * t * t; } - public static float OutQuint(float start, float end, float time, float duration) + public static float OutQuint(float t) { - time = (time / duration) - 1; - return (end * ((time * time * time * time * time) + 1)) + start; + var x = 1 - t; + return 1 - (x * x * x * x * x); } - public static float InOutQuint(float start, float end, float time, float duration) + public static float InOutQuint(float t) { - time = time / duration * 2; - if (time < 1) + if (t < 0.5f) { - return (end / 2 * (time * time * time * time * time)) + start; + return 16 * t * t * t * t * t; } else { - time -= 2; - return (end / 2 * ((time * time * time * time * time) + 2)) + start; + var x = -2 * t + 2; + return 1 - ((x * x * x * x * x) / 2); } } - public static float OutInQuint(float start, float end, float time, float duration) => OutIn(OutQuint, InQuint, time, start, end, duration); - - public static Fix64 InQuint(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) - { - time /= duration; - return (end * (time * time * time * time * time)) + start; - } - - public static Fix64 OutQuint(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) - { - time = (time / duration) - 1; - return (end * ((time * time * time * time * time) + 1)) + start; - } - - public static Fix64 InOutQuint(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) - { - time = time / duration * 2; - if (time < 1) - { - return (end / 2 * (time * time * time * time * time)) + start; - } - else - { - time -= 2; - return (end / 2 * ((time * time * time * time * time) + 2)) + start; - } - } - - public static Fix64 OutInQuint(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) => OutIn(OutQuint, InQuint, time, start, end, duration); - + public static float OutInQuint(float t) => OutIn(OutQuint, InQuint, t); // SINE - public static float InSine(float start, float end, float time, float duration) + public static float InSine(float t) { - return (-end * System.MathF.Cos(time / duration * (System.MathF.PI / 2))) + end + start; + return 1 - System.MathF.Cos((t * System.MathF.PI) / 2); } - public static float OutSine(float start, float end, float time, float duration) + public static float OutSine(float t) { - return (end * System.MathF.Sin(time / duration * (System.MathF.PI / 2))) + start; + return System.MathF.Sin((t * System.MathF.PI) / 2); } - public static float InOutSine(float start, float end, float time, float duration) + public static float InOutSine(float t) { - return (-end / 2 * (System.MathF.Cos(System.MathF.PI * time / duration) - 1)) + start; + return -(System.MathF.Cos(System.MathF.PI * t) - 1) / 2; } - public static float OutInSine(float start, float end, float time, float duration) => OutIn(OutSine, InSine, time, start, end, duration); - - public static Fix64 InSine(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) - { - return (-end * Fix64.Cos((time / duration) * Fix64.PiOver2)) + end + start; - } - - public static Fix64 OutSine(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) - { - return (end * Fix64.Sin((time / duration) * Fix64.PiOver2)) + start; - } - - public static Fix64 InOutSine(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) - { - return (-end / 2 * (Fix64.Cos(Fix64.Pi * time / duration) - 1)) + start; - } - - public static Fix64 OutInSine(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) => OutIn(OutSine, InSine, time, start, end, duration); + public static float OutInSine(float t) => OutIn(OutSine, InSine, t); // EXPONENTIAL - public static float InExpo(float start, float end, float time, float duration) + public static float InExpo(float t) { - if (time == 0) + if (t == 0) { - return start; + return 0; } else { - return (end * System.MathF.Pow(2, 10 * ((time / duration) - 1))) + start - (end * 0.001f); + return System.MathF.Pow(2, 10 * t - 10); } } - public static float OutExpo(float start, float end, float time, float duration) + public static float OutExpo(float t) { - if (time == duration) + if (t == 1) { - return start + end; + return 1; } else { - return (end * 1.001f * (-System.MathF.Pow(2, -10 * time / duration) + 1)) + start; + return 1 - System.MathF.Pow(2, -10 * t); } } - public static float InOutExpo(float start, float end, float time, float duration) + public static float InOutExpo(float t) { - if (time == 0) { return start; } - if (time == duration) { return start + end; } - time = time / duration * 2; - if (time < 1) + if (t == 0) { - return (end / 2 * System.MathF.Pow(2, 10 * (time - 1))) + start - (end * 0.0005f); + return 0; + } + else if (t == 1) + { + return 1; + } + else if (t < 0.5f) + { + return System.MathF.Pow(2, 20 * t - 10) / 2; } else { - time--; - return (end / 2 * 1.0005f * (-System.MathF.Pow(2, -10 * time) + 2)) + start; + return (2 - System.MathF.Pow(2, -20 * t + 10)) / 2; } } - public static float OutInExpo(float start, float end, float time, float duration) => OutIn(OutExpo, InExpo, time, start, end, duration); - - // TODO: need Fix64 power function for Expo + public static float OutInExpo(float t) => OutIn(OutExpo, InExpo, t); // CIRCULAR - public static float InCirc(float start, float end, float time, float duration) + public static float InCirc(float t) { - time /= duration; - return (-end * (System.MathF.Sqrt(1 - (time * time)) - 1)) + start; + return 1 - System.MathF.Sqrt(1 - (t * t)); } - public static float OutCirc(float start, float end, float time, float duration) + public static float OutCirc(float t) { - time = (time / duration) - 1; - return (end * System.MathF.Sqrt(1 - (time * time))) + start; + return System.MathF.Sqrt(1 - ((t - 1) * (t - 1))); } - public static float InOutCirc(float start, float end, float time, float duration) + public static float InOutCirc(float t) { - time = time / duration * 2; - if (time < 1) + if (t < 0.5f) { - return (-end / 2 * (System.MathF.Sqrt(1 - (time * time)) - 1)) + start; + return (1 - System.MathF.Sqrt(1 - ((2 * t) * (2 * t)))) / 2; } else { - time -= 2; - return (end / 2 * (System.MathF.Sqrt(1 - (time * time)) + 1)) + start; + var x = -2 * t + 2; + return (System.MathF.Sqrt(1 - (x * x)) + 1) / 2; } } - public static float OutInCirc(float start, float end, float time, float duration) => OutIn(OutCirc, InCirc, time, start, end, duration); - - public static Fix64 InCirc(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) - { - time /= duration; - return (-end * (Fix64.Sqrt(1 - (time * time)) - 1)) + start; - } - - public static Fix64 OutCirc(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) - { - time = (time / duration) - 1; - return (end * Fix64.Sqrt(1 - (time * time))) + start; - } - - public static Fix64 InOutCirc(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) - { - time = time / duration * 2; - if (time < 1) - { - return (-end / 2 * (Fix64.Sqrt(1 - (time * time)) - 1)) + start; - } - else - { - time -= 2; - return (end / 2 * (Fix64.Sqrt(1 - (time * time)) + 1)) + start; - } - } - - public static Fix64 OutInCirc(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) => OutIn(OutCirc, InCirc, time, start, end, duration); - - // ELASTIC - - public static float InElastic(float start, float end, float time, float duration, float a, float p) - { - if (time == 0) { return start; } - - time /= duration; - - if (time == 1) { return start + end; } - - float s; - if (a < System.MathF.Abs(end)) - { - a = end; - s = p / 4; - } - else - { - s = p / (2 * System.MathF.PI) * System.MathF.Asin(end / a); - } - - time--; - - return -(a * System.MathF.Pow(2, 10 * time) * System.MathF.Sin(((time * duration) - s) * (2 * System.MathF.PI) / p)) + start; - } - - public static float InElastic(float start, float end, float time, float duration) => InElastic(start, end, time, duration, end, duration * 0.3f); - - public static float OutElastic(float start, float end, float time, float duration, float a, float p) - { - if (time == 0) { return start; } - - time /= duration; - - if (time == 1) { return start + end; } - - float s; - - if (a < System.MathF.Abs(end)) - { - a = end; - s = p / 4; - } - else - { - s = p / (2 * System.MathF.PI) * System.MathF.Asin(end / a); - } - - return (a * System.MathF.Pow(2, -10 * time) * System.MathF.Sin(((time * duration) - s) * (2 * System.MathF.PI) / p)) + end + start; - } - - public static float OutElastic(float start, float end, float time, float duration) => OutElastic(start, end, time, duration, end, duration * 0.3f); - - public static float InOutElastic(float start, float end, float time, float duration, float a, float p) - { - if (time == 0) { return start; } - - time = time / duration * 2; - - if (time == 2) { return start + end; } - - float s; - - if (a < System.MathF.Abs(end)) - { - a = end; - s = p / 4; - } - else - { - s = p / (2 * System.MathF.PI) * System.MathF.Asin(end / a); - } - - if (time < 1) - { - time--; - return (-0.5f * (a * System.MathF.Pow(2, 10 * time) * System.MathF.Sin(((time * duration) - s) * (2 * System.MathF.PI) / p))) + start; - } - else - { - time--; - return (a * System.MathF.Pow(2, -10 * time) * System.MathF.Sin(((time * duration) - s) * (2 * System.MathF.PI) / p) * 0.5f) + end + start; - } - } - - public static float InOutElastic(float start, float end, float time, float duration) => InOutElastic(start, end, time, duration, end, duration * 0.3f * 1.5f); - - public static float OutInElastic(float start, float end, float time, float duration, float a, float p) - { - if (time < duration / 2) - { - return OutElastic(time * 2, start, end / 2, duration, a, p); - } - else - { - return InElastic((time * 2) - duration, start + (end / 2), end / 2, duration, a, p); - } - } - - public static float OutInElastic(float start, float end, float time, float duration) => OutInElastic(start, end, time, duration, end, duration * 0.3f); - - // TODO: Need Fix64 Asin for elastic + public static float OutInCirc(float t) => OutIn(OutCirc, InCirc, t); // BACK - public static float InBack(float start, float end, float time, float duration, float s = 1.70158f) + public static float InBack(float t) { - time /= duration; - return (end * time * time * (((s + 1) * time) - s)) + start; + return C3 * t * t * t - C1 * t * t; } - public static float InBack(float start, float end, float time, float duration) => InBack(start, end, time, duration, 1.70158f); - - public static float OutBack(float start, float end, float time, float duration, float s = 1.70158f) + public static float OutBack(float t) { - time = (time / duration) - 1; - return (end * ((time * time * (((s + 1) * time) + s)) + 1)) + start; + return 1 + C3 * (t - 1) * (t - 1) * (t - 1) + C1 * (t - 1) * (t - 1); } - public static float OutBack(float start, float end, float time, float duration) => OutBack(start, end, time, duration, 1.70158f); - - public static float InOutBack(float start, float end, float time, float duration, float s = 1.70158f) + public static float InOutBack(float t) { - s *= 1.525f; - time = time / duration * 2; - if (time < 1) + if (t < 0.5f) { - return (end / 2 * (time * time * (((s + 1) * time) - s))) + start; + return ((2 * t) * (2 * t) * ((C2 + 1) * 2 * t - C2)) / 2; } else { - time -= 2; - return (end / 2 * ((time * time * (((s + 1) * time) + s)) + 2)) + start; + var x = 2 * t - 2; + return ((t * t) * ((C2 + 1) * (x) + C2) + 2) / 2; } } - public static float InOutBack(float start, float end, float time, float duration) => InOutBack(start, end, time, duration, 1.70158f); + public static float OutInBack(float t) => OutIn(OutBack, InBack, t); - public static float OutInBack(float start, float end, float time, float duration, float s = 1.70158f) + // ELASTIC + + public static float InElastic(float t) { - if (time < duration / 2) + if (t == 0) { - return OutBack(time * 2, start, end / 2, duration, s); + return 0; + } + else if (t == 1) + { + return 1; } else { - return InBack((time * 2) - duration, start + (end / 2), end / 2, duration, s); + return -System.MathF.Pow(2, 10 * t - 10) * System.MathF.Sin((t * 10 - 10.75f) * C4); } } - public static float OutInBack(float start, float end, float time, float duration) => OutInBack(start, end, time, duration, 1.70158f); - - private static readonly Fix64 S_DEFAULT = Fix64.FromFraction(170158, 100000); - private static readonly Fix64 S_MULTIPLIER = Fix64.FromFraction(61, 40); - - public static Fix64 InBack(Fix64 start, Fix64 end, Fix64 time, Fix64 duration, Fix64 s) + public static float OutElastic(float t) { - time /= duration; - return (end * time * time * (((s + 1) * time) - s)) + start; - } - - public static Fix64 InBack(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) => InBack(start, end, time, duration, S_DEFAULT); - - public static Fix64 OutBack(Fix64 start, Fix64 end, Fix64 time, Fix64 duration, Fix64 s) - { - time = (time / duration) - 1; - return (end * ((time * time * (((s + 1) * time) + s)) + 1)) + start; - } - - public static Fix64 OutBack(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) => OutBack(start, end, time, duration, S_DEFAULT); - - public static Fix64 InOutBack(Fix64 start, Fix64 end, Fix64 time, Fix64 duration, Fix64 s) - { - s *= S_MULTIPLIER; - time = time / duration * 2; - if (time < 1) + if (t == 0) { - return (end / 2 * (time * time * (((s + 1) * time) - s))) + start; + return 0; + } + else if (t == 1) + { + return 1; } else { - time -= 2; - return (end / 2 * ((time * time * (((s + 1) * time) + s)) + 2)) + start; + return System.MathF.Pow(2, -10 * t) * System.MathF.Sin((t * 10 - 0.75f) * C4) + 1; } } - public static Fix64 InOutBack(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) => InOutBack(start, end, time, duration, S_DEFAULT); - - public static Fix64 OutInBack(Fix64 start, Fix64 end, Fix64 time, Fix64 duration, Fix64 s) + public static float InOutElastic(float t) { - if (time < duration / 2) + if (t == 0) { - return OutBack(time * 2, start, end / 2, duration, s); + return 0; + } + else if (t == 1) + { + return 1; + } + else if (t < 0.5f) + { + return -(System.MathF.Pow(2, 20 * t - 10) * System.MathF.Sin((20 * t - 11.125f) * C5)) / 2; } else { - return InBack((time * 2) - duration, start + (end / 2), end / 2, duration, s); + return (System.MathF.Pow(2, -20 * t + 10) * System.MathF.Sin((20 * t - 11.125f) * C5)) / 2 + 1; } } - public static Fix64 OutInBack(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) => OutInBack(start, end, time, duration, S_DEFAULT); + public static float OutInElastic(float t) => OutIn(OutElastic, InElastic, t); // BOUNCE - public static float InBounce(float start, float end, float time, float duration) + public static float InBounce(float t) { - return end - OutBounce(duration - time, 0, end, duration) + start; + return 1 - OutBounce(1 - t); } - public static float OutBounce(float start, float end, float time, float duration) + public static float OutBounce(float t) { - time /= duration; - if (time < 1 / 2.75f) + const float N1 = 7.5625f; + const float D1 = 2.75f; + + if (t < 1 / D1) { - return (end * (7.5625f * time * time)) + start; + return N1 * t * t; } - else if (time < 2 / 2.75f) - { - time -= (1.5f / 2.75f); - return (end * ((7.5625f * time * time) + 0.75f)) + start; + else if (t < 2 / D1) { + return N1 * (t -= 1.5f / D1) * t + 0.75f; } - else if (time < 2.5 / 2.75) + else if (t < 2.5f / D1) { - time -= (2.25f / 2.75f); - return (end * ((7.5625f * time * time) + 0.9375f)) + start; + return N1 * (t -= 2.25f / D1) * t + 0.9375f; } else { - time -= (2.625f / 2.75f); - return (end * ((7.5625f * time * time) + 0.984375f)) + start; + return N1 * (t -= 2.625f / D1) * t + 0.984375f; } } - public static float InOutBounce(float start, float end, float time, float duration) + public static float InOutBounce(float t) { - if (time < duration / 2) + if (t < 0.5f) { - return (InBounce(time * 2, 0, end, duration) * 0.5f) + start; + return (1 - OutBounce(1 - 2 * t)) / 2; } else { - return (OutBounce((time * 2) - duration, 0, end, duration) * 0.5f) + (end * 0.5f) + start; + return (1 + OutBounce(2 * t - 1)) / 2; } } - public static float OutInBounce(float start, float end, float time, float duration) => OutIn(OutBounce, InBounce, time, start, end, duration); + public static float OutInBounce(float t) => OutIn(OutBounce, InBounce, t); - private static readonly Fix64 BOUNCE_MULTIPLIER = Fix64.FromFraction(121, 16); + /* FIXED EASING FUNCTIONS */ - public static Fix64 InBounce(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) + // LINEAR + + public static Fix64 Linear(Fix64 t) { - return end - OutBounce(duration - time, Fix64.Zero, end, duration) + start; + return t; } - // FIXME: these constants are kinda gnarly, could maybe define them as static readonlys - public static Fix64 OutBounce(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) + // QUADRATIC + + public static Fix64 InQuad(Fix64 t) { - time /= duration; - if (time < Fix64.FromFraction(4, 11)) + return t * t; + } + + public static Fix64 OutQuad(Fix64 t) + { + return 1 - (1 - t) * (1 - t); + } + + public static Fix64 InOutQuad(Fix64 t) + { + if (t < HALF) { - return (end * (BOUNCE_MULTIPLIER * time * time)) + start; - } - else if (time < Fix64.FromFraction(8, 11)) - { - time -= Fix64.FromFraction(6, 11); - return (end * ((BOUNCE_MULTIPLIER * time * time) + Fix64.FromFraction(3, 4))) + start; - } - else if (time < Fix64.FromFraction(10, 11)) - { - time -= Fix64.FromFraction(9, 11); - return (end * ((BOUNCE_MULTIPLIER * time * time) + Fix64.FromFraction(15, 16))) + start; + return 2 * t * t; } else { - time -= Fix64.FromFraction(21, 22); - return (end * ((BOUNCE_MULTIPLIER * time * time) + Fix64.FromFraction(63, 64))) + start; + var x = (-2 * t + 2); + return 1 - ((x * x) / 2); } } - public static Fix64 InOutBounce(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) + public static Fix64 OutInQuad(Fix64 t) => OutIn(OutQuad, InQuad, t); + + // CUBIC + + public static Fix64 InCubic(Fix64 t) { - if (time < duration / 2) + return t * t * t; + } + + public static Fix64 OutCubic(Fix64 t) + { + var x = 1 - t; + return 1 - (x * x * x); + } + + public static Fix64 InOutCubic(Fix64 t) + { + if (t < HALF) { - return (InBounce(time * 2, Fix64.Zero, end, duration) * Fix64.FromFraction(1, 2)) + start; + return 4 * t * t * t; } else { - return (OutBounce((time * 2) - duration, Fix64.Zero, end, duration) * Fix64.FromFraction(1, 2)) + (end * Fix64.FromFraction(1, 2)) + start; + var x = -2 * t + 2; + return 1 - ((x * x * x) / 2); } } - public static Fix64 OutInBounce(Fix64 start, Fix64 end, Fix64 time, Fix64 duration) => OutIn(OutBounce, InBounce, time, start, end, duration); + public static Fix64 OutInCubic(Fix64 t) => OutIn(OutCubic, InCubic, t); + + // QUARTIC + + public static Fix64 InQuart(Fix64 t) + { + return t * t * t * t; + } + + public static Fix64 OutQuart(Fix64 t) + { + var x = 1 - t; + return 1 - (x * x * x * x); + } + + public static Fix64 InOutQuart(Fix64 t) + { + if (t < HALF) + { + return 8 * t * t * t * t; + } + else + { + var x = -2 * t + 2; + return 1 - ((x * x * x * x) / 2); + } + } + + public static Fix64 OutInQuart(Fix64 t) => OutIn(OutQuart, InQuart, t); + + // QUINTIC + + public static Fix64 InQuint(Fix64 t) + { + return t * t * t * t * t; + } + + public static Fix64 OutQuint(Fix64 t) + { + var x = 1 - t; + return 1 - (x * x * x * x * x); + } + + public static Fix64 InOutQuint(Fix64 t) + { + if (t < HALF) + { + return 16 * t * t * t * t * t; + } + else + { + var x = -2 * t + 2; + return 1 - ((x * x * x * x * x) / 2); + } + } + + public static Fix64 OutInQuint(Fix64 t) => OutIn(OutQuint, InQuint, t); + + // SINE + + public static Fix64 InSine(Fix64 t) + { + return 1 - Fix64.Cos((t * Fix64.Pi) / 2); + } + + public static Fix64 OutSine(Fix64 t) + { + return Fix64.Sin((t * Fix64.Pi) / 2); + } + + public static Fix64 InOutSine(Fix64 t) + { + return -(Fix64.Cos(Fix64.Pi * t) - 1) / 2; + } + + public static Fix64 OutInSine(Fix64 t) => OutIn(OutSine, InSine, t); + + // CIRCULAR + + public static Fix64 InCirc(Fix64 t) + { + return 1 - Fix64.Sqrt(1 - (t * t)); + } + + public static Fix64 OutCirc(Fix64 t) + { + return Fix64.Sqrt(1 - ((t - 1) * (t - 1))); + } + + public static Fix64 InOutCirc(Fix64 t) + { + if (t < HALF) + { + return (1 - Fix64.Sqrt(1 - ((2 * t) * (2 * t)))) / 2; + } + else + { + var x = -2 * t + 2; + return (Fix64.Sqrt(1 - (x * x)) + 1) / 2; + } + } + + public static Fix64 OutInCirc(Fix64 t) => OutIn(OutCirc, InCirc, t); + + // BACK + + public static Fix64 InBack(Fix64 t) + { + return FIXED_C3 * t * t * t - FIXED_C1 * t * t; + } + + public static Fix64 OutBack(Fix64 t) + { + return 1 + FIXED_C3 * (t - 1) * (t - 1) * (t - 1) + FIXED_C1 * (t - 1) * (t - 1); + } + + public static Fix64 InOutBack(Fix64 t) + { + if (t < HALF) + { + return ((2 * t) * (2 * t) * ((FIXED_C2 + 1) * 2 * t - FIXED_C2)) / 2; + } + else + { + var x = 2 * t - 2; + return ((t * t) * ((FIXED_C2 + 1) * (x) + FIXED_C2) + 2) / 2; + } + } + + public static Fix64 OutInBack(Fix64 t) => OutIn(OutBack, InBack, t); + + // BOUNCE + + public static Fix64 InBounce(Fix64 t) + { + return 1 - OutBounce(1 - t); + } + + public static Fix64 OutBounce(Fix64 t) + { + if (t < 1 / FIXED_D1) + { + return FIXED_N1 * t * t; + } + else if (t < 2 / FIXED_D1) { + return FIXED_N1 * (t -= Fix64.FromFraction(3, 2) / FIXED_D1) * t + Fix64.FromFraction(3, 4); + } + else if (t < Fix64.FromFraction(5, 2) / FIXED_D1) + { + return FIXED_N1 * (t -= Fix64.FromFraction(9, 4) / FIXED_D1) * t + Fix64.FromFraction(15, 16); + } + else + { + return FIXED_N1 * (t -= Fix64.FromFraction(181, 80) / FIXED_D1) * t + Fix64.FromFraction(63, 64); + } + } + + public static Fix64 InOutBounce(Fix64 t) + { + if (t < HALF) + { + return (1 - OutBounce(1 - 2 * t)) / 2; + } + else + { + return (1 + OutBounce(2 * t - 1)) / 2; + } + } + + public static Fix64 OutInBounce(Fix64 t) => OutIn(OutBounce, InBounce, t); public static class Function { @@ -858,7 +790,7 @@ namespace MoonWorks.Math OutInBounce } - private static Dictionary FloatLookup = new Dictionary + private static Dictionary> FloatLookup = new Dictionary> { { Float.Linear, Linear }, { Float.InQuad, InQuad }, @@ -903,7 +835,7 @@ namespace MoonWorks.Math { Float.OutInBounce, OutInBounce } }; - private static Dictionary FixedLookup = new Dictionary + private static Dictionary> FixedLookup = new Dictionary> { { Fixed.Linear, Linear }, { Fixed.InQuad, InQuad }, @@ -940,12 +872,12 @@ namespace MoonWorks.Math { Fixed.OutInBounce, OutInBounce } }; - public static EasingFunctionFloat Get(Float functionEnum) + public static System.Func Get(Float functionEnum) { return FloatLookup[functionEnum]; } - public static EasingFunctionFixed Get(Fixed functionEnum) + public static System.Func Get(Fixed functionEnum) { return FixedLookup[functionEnum]; } diff --git a/src/Video/StreamingSoundTheora.cs b/src/Video/StreamingSoundTheora.cs index a49c939c..2b111b52 100644 --- a/src/Video/StreamingSoundTheora.cs +++ b/src/Video/StreamingSoundTheora.cs @@ -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,14 +34,22 @@ namespace MoonWorks.Video ) { var lengthInFloats = bufferLengthInBytes / sizeof(float); - int samples = Theorafile.tf_readaudio( - VideoHandle, - (IntPtr) buffer, - lengthInFloats - ); + // 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, + lengthInFloats + ); - filledLengthInBytes = samples * sizeof(float); - reachedEnd = Theorafile.tf_eos(VideoHandle) == 1; + filledLengthInBytes = samples * sizeof(float); + reachedEnd = Theorafile.tf_eos(VideoHandle) == 1; + } } + + protected override void OnReachedEnd() { } } } diff --git a/src/Video/Video.cs b/src/Video/Video.cs index 592da2b3..0c7b6966 100644 --- a/src/Video/Video.cs +++ b/src/Video/Video.cs @@ -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; } } diff --git a/src/Video/VideoPlayer.cs b/src/Video/VideoPlayer.cs index b0405428..d4c52ad1 100644 --- a/src/Video/VideoPlayer.cs +++ b/src/Video/VideoPlayer.cs @@ -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)