diff --git a/Runtime/Audio.meta b/Runtime/Audio.meta
new file mode 100644
index 00000000..7f0ce74e
--- /dev/null
+++ b/Runtime/Audio.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 0ef98c6cf0eb71e4da673fdda0eb7d7d
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/Audio/AudioSourceDataObserver.cs b/Runtime/Audio/AudioSourceDataObserver.cs
new file mode 100644
index 00000000..6fa40b6f
--- /dev/null
+++ b/Runtime/Audio/AudioSourceDataObserver.cs
@@ -0,0 +1,104 @@
+namespace Zinnia.Audio
+{
+ using UnityEngine;
+ using UnityEngine.Events;
+ using System;
+ using Malimbe.PropertySerializationAttribute;
+ using Malimbe.XmlDocumentationAttribute;
+
+ ///
+ /// Observes the and emits the audio data.
+ ///
+ [RequireComponent(typeof(AudioSource))]
+ 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();
+
+ ///
+ /// Returns whether the is player.
+ ///
+ public virtual bool IsAudioSourcePlaying() => audioSource != null && audioSource.isPlaying;
+
+ ///
+ /// The to observe.
+ ///
+ protected AudioSource audioSource;
+
+ ///
+ /// Caches the .
+ ///
+ protected virtual void Awake()
+ {
+ audioSource = GetComponent();
+ }
+
+ ///
+ /// 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)
+ {
+ DataObserved?.Invoke(eventData.Set(AudioSettings.dspTime, data, channels));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Audio/AudioSourceDataObserver.cs.meta b/Runtime/Audio/AudioSourceDataObserver.cs.meta
new file mode 100644
index 00000000..c18fb1b5
--- /dev/null
+++ b/Runtime/Audio/AudioSourceDataObserver.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 001ca6370643f01489fab678a6f730de
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/Haptics/AudioSourceHapticPulser.cs b/Runtime/Haptics/AudioSourceHapticPulser.cs
index 8c6991ed..b80f4d13 100644
--- a/Runtime/Haptics/AudioSourceHapticPulser.cs
+++ b/Runtime/Haptics/AudioSourceHapticPulser.cs
@@ -2,8 +2,11 @@
{
using UnityEngine;
using System.Collections;
+ using Malimbe.MemberChangeMethod;
+ using Malimbe.MemberClearanceMethod;
using Malimbe.PropertySerializationAttribute;
using Malimbe.XmlDocumentationAttribute;
+ using Zinnia.Audio;
///
/// Creates a haptic pattern based on the waveform of an and utilizes a to create the effect.
@@ -11,29 +14,47 @@
public class AudioSourceHapticPulser : RoutineHapticPulser
{
///
- /// The waveform to represent the haptic pattern.
+ /// Observer that provides audio data from a .
///
- [Serialized]
+ [Serialized, Cleared]
[field: DocumentedByXml]
- public AudioSource AudioSource { get; set; }
+ public AudioSourceDataObserver Observer { get; set; }
///
- /// of the last .
+ /// A reused data instance.
///
- protected double filterReadDspTime;
+ protected readonly AudioSourceDataObserver.EventData audioData = new AudioSourceDataObserver.EventData();
+
///
- /// Audio data array of the last .
+ /// Subscribes as a listener to the .
///
- protected float[] filterReadData;
+ protected virtual void SubscribeToObserver()
+ {
+ if (Observer == null)
+ {
+ return;
+ }
+
+ Observer.DataObserved.AddListener(Receive);
+ }
+
///
- /// Number of channels of the last .
+ /// Unsubscribes from listening to the .
///
- protected int filterReadChannels;
+ protected virtual void UnsubscribeFromObserver()
+ {
+ if (Observer == null)
+ {
+ return;
+ }
+
+ Observer.DataObserved.RemoveListener(Receive);
+ }
///
public override bool IsActive()
{
- return base.IsActive() && AudioSource != null;
+ return base.IsActive() && Observer != null;
}
///
@@ -42,36 +63,63 @@ public override bool IsActive()
/// An Enumerator to manage the running of the Coroutine.
protected override IEnumerator HapticProcessRoutine()
{
+ SubscribeToObserver();
int outputSampleRate = AudioSettings.outputSampleRate;
- while (AudioSource.isPlaying)
+ while (Observer != null && Observer.IsAudioSourcePlaying())
{
- 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;
}
+ UnsubscribeFromObserver();
ResetIntensity();
}
///
- /// Store currently playing audio data and additional data.
+ /// Receive audio data from .
+ ///
+ protected virtual void Receive(AudioSourceDataObserver.EventData eventData)
+ {
+ audioData.Set(eventData);
+ }
+
+ ///
+ /// Called before has been changed.
+ ///
+ [CalledBeforeChangeOf(nameof(Observer))]
+ protected virtual void OnBeforeObserverChange()
+ {
+ if (hapticRoutine == null)
+ {
+ return;
+ }
+
+ UnsubscribeFromObserver();
+ }
+
+ ///
+ /// Called after has been changed.
///
- /// 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)
+ [CalledAfterChangeOf(nameof(Observer))]
+ protected virtual void OnAfterObserverChange()
{
- filterReadDspTime = AudioSettings.dspTime;
- filterReadData = data;
- filterReadChannels = channels;
+ if (hapticRoutine == null)
+ {
+ return;
+ }
+
+ SubscribeToObserver();
}
}
}
\ No newline at end of file