diff --git a/Runtime/Haptics/AudioSourceHapticPulser.cs b/Runtime/Haptics/AudioSourceHapticPulser.cs
index 8c6991ed..cbf5627d 100644
--- a/Runtime/Haptics/AudioSourceHapticPulser.cs
+++ b/Runtime/Haptics/AudioSourceHapticPulser.cs
@@ -1,7 +1,11 @@
namespace Zinnia.Haptics
{
using UnityEngine;
+ using UnityEngine.Events;
+ using System;
using System.Collections;
+ using Malimbe.MemberChangeMethod;
+ using Malimbe.MemberClearanceMethod;
using Malimbe.PropertySerializationAttribute;
using Malimbe.XmlDocumentationAttribute;
@@ -13,22 +17,18 @@ public class AudioSourceHapticPulser : RoutineHapticPulser
///
/// The waveform to represent the haptic pattern.
///
- [Serialized]
+ [Serialized, Cleared]
[field: DocumentedByXml]
public AudioSource AudioSource { get; set; }
///
- /// of the last .
+ /// Observer added to .
///
- protected double filterReadDspTime;
+ protected AudioSourceDataObserver observer;
///
- /// Audio data array of the last .
+ /// The observed audio data.
///
- protected float[] filterReadData;
- ///
- /// Number of channels of the last .
- ///
- protected int filterReadChannels;
+ protected readonly AudioSourceDataObserver.EventData audioData = new AudioSourceDataObserver.EventData();
///
public override bool IsActive()
@@ -36,42 +36,181 @@ public override bool IsActive()
return base.IsActive() && AudioSource != null;
}
+ ///
+ protected override void DoCancel()
+ {
+ RemoveDataObserver();
+ base.DoCancel();
+ }
+
///
/// Enumerates through and pulses for each amplitude of the wave.
///
/// An Enumerator to manage the running of the Coroutine.
protected override IEnumerator HapticProcessRoutine()
{
+ AddDataObserver();
int outputSampleRate = AudioSettings.outputSampleRate;
- while (AudioSource.isPlaying)
+ while (AudioSource != null && AudioSource.isPlaying)
{
- int sampleIndex = (int)((AudioSettings.dspTime - filterReadDspTime) * outputSampleRate);
float currentSample = 0;
- if (filterReadData != null && sampleIndex * filterReadChannels < filterReadData.Length)
+ if (audioData.Data != null)
{
- for (int i = 0; i < filterReadChannels; ++i)
+ int sampleIndex = (int)((AudioSettings.dspTime - audioData.DspTime) * outputSampleRate) * audioData.Channels;
+ sampleIndex = Mathf.Min(sampleIndex, audioData.Data.Length - audioData.Channels);
+ for (int i = 0; i < audioData.Channels; ++i)
{
- currentSample += filterReadData[sampleIndex + i];
+ currentSample += Mathf.Abs(audioData.Data[sampleIndex + i]);
}
- currentSample /= filterReadChannels;
+ currentSample /= audioData.Channels;
}
HapticPulser.Intensity = currentSample * IntensityMultiplier;
HapticPulser.Begin();
yield return null;
}
+ RemoveDataObserver();
ResetIntensity();
}
///
- /// Store currently playing audio data and additional data.
+ /// Adds a to the .
+ ///
+ protected virtual void AddDataObserver()
+ {
+ if (AudioSource == null)
+ {
+ return;
+ }
+
+ observer = AudioSource.gameObject.AddComponent();
+ observer.DataObserved.AddListener(Receive);
+ }
+
+ ///
+ /// Remove the from the .
+ ///
+ protected virtual void RemoveDataObserver()
+ {
+ if (observer == null)
+ {
+ return;
+ }
+
+ observer.DataObserved.RemoveListener(Receive);
+ Destroy(observer);
+ observer = null;
+ }
+
+ ///
+ /// Receive audio data from .
+ ///
+ protected virtual void Receive(AudioSourceDataObserver.EventData eventData)
+ {
+ audioData.Set(eventData);
+ }
+
+ ///
+ /// Called before has been changed.
+ ///
+ [CalledBeforeChangeOf(nameof(AudioSource))]
+ protected virtual void OnBeforeAudioSourceChange()
+ {
+ if (hapticRoutine == null)
+ {
+ return;
+ }
+
+ RemoveDataObserver();
+ }
+
+ ///
+ /// Called after has been changed.
+ ///
+ [CalledAfterChangeOf(nameof(AudioSource))]
+ protected virtual void OnAfterAudioSourceChange()
+ {
+ if (hapticRoutine == null)
+ {
+ return;
+ }
+
+ AddDataObserver();
+ }
+ }
+
+ ///
+ /// Observes the and emits the audio data.
+ ///
+ public class AudioSourceDataObserver : MonoBehaviour
+ {
+ ///
+ /// Holds data about a event.
+ ///
+ [Serializable]
+ public class EventData
+ {
+ ///
+ /// of the last .
+ ///
+ [Serialized]
+ [field: DocumentedByXml]
+ public double DspTime { get; set; }
+ ///
+ /// Audio data array of the last .
+ ///
+ [Serialized]
+ [field: DocumentedByXml]
+ public float[] Data { get; set; }
+ ///
+ /// Number of channels of the last .
+ ///
+ [Serialized]
+ [field: DocumentedByXml]
+ public int Channels { get; set; }
+
+ public EventData Set(EventData source)
+ {
+ return Set(source.DspTime, source.Data, source.Channels);
+ }
+
+ public EventData Set(double dspTime, float[] data, int channels)
+ {
+ DspTime = dspTime;
+ Data = data;
+ Channels = channels;
+ return this;
+ }
+
+ public void Clear()
+ {
+ Set(default, default, default);
+ }
+ }
+
+ ///
+ /// Defines the event with the .
+ ///
+ [Serializable]
+ public class UnityEvent : UnityEvent { }
+
+ ///
+ /// Emitted whenever the audio data is observed.
+ ///
+ [DocumentedByXml]
+ public UnityEvent DataObserved = new UnityEvent();
+ ///
+ /// The data to emit with an event.
+ ///
+ protected readonly EventData eventData = new EventData();
+
+ ///
+ /// Emits audio data.
///
/// An array of floats comprising the audio data.
/// An int that stores the number of channels of audio data passed to this delegate.
protected virtual void OnAudioFilterRead(float[] data, int channels)
{
- filterReadDspTime = AudioSettings.dspTime;
- filterReadData = data;
- filterReadChannels = channels;
+ DataObserved?.Invoke(eventData.Set(AudioSettings.dspTime, data, channels));
}
}
}
\ No newline at end of file