11namespace Zinnia . Haptics
22{
33 using UnityEngine ;
4+ using UnityEngine . Events ;
5+ using System ;
46 using System . Collections ;
7+ using Malimbe . MemberChangeMethod ;
8+ using Malimbe . MemberClearanceMethod ;
59 using Malimbe . PropertySerializationAttribute ;
610 using Malimbe . XmlDocumentationAttribute ;
711
@@ -13,22 +17,18 @@ public class AudioSourceHapticPulser : RoutineHapticPulser
1317 /// <summary>
1418 /// The waveform to represent the haptic pattern.
1519 /// </summary>
16- [ Serialized ]
20+ [ Serialized , Cleared ]
1721 [ field: DocumentedByXml ]
1822 public AudioSource AudioSource { get ; set ; }
1923
2024 /// <summary>
21- /// <see cref="AudioSettings.dspTime"/> of the last <see cref="OnAudioFilterRead "/>.
25+ /// Observer added to <see cref="AudioSource "/>.
2226 /// </summary>
23- protected double filterReadDspTime ;
27+ protected AudioSourceDataObserver observer ;
2428 /// <summary>
25- /// Audio data array of the last <see cref="OnAudioFilterRead"/> .
29+ /// The observed audio data .
2630 /// </summary>
27- protected float [ ] filterReadData ;
28- /// <summary>
29- /// Number of channels of the last <see cref="OnAudioFilterRead"/>.
30- /// </summary>
31- protected int filterReadChannels ;
31+ protected readonly AudioSourceDataObserver . EventData audioData = new AudioSourceDataObserver . EventData ( ) ;
3232
3333 /// <inheritdoc />
3434 public override bool IsActive ( )
@@ -42,36 +42,168 @@ public override bool IsActive()
4242 /// <returns>An Enumerator to manage the running of the Coroutine.</returns>
4343 protected override IEnumerator HapticProcessRoutine ( )
4444 {
45+ AddDataObserver ( ) ;
4546 int outputSampleRate = AudioSettings . outputSampleRate ;
46- while ( AudioSource . isPlaying )
47+ while ( AudioSource != null && AudioSource . isPlaying )
4748 {
48- int sampleIndex = ( int ) ( ( AudioSettings . dspTime - filterReadDspTime ) * outputSampleRate ) ;
4949 float currentSample = 0 ;
50- if ( filterReadData != null && sampleIndex * filterReadChannels < filterReadData . Length )
50+ if ( audioData . Data != null )
5151 {
52- for ( int i = 0 ; i < filterReadChannels ; ++ i )
52+ int sampleIndex = ( int ) ( ( AudioSettings . dspTime - audioData . DspTime ) * outputSampleRate ) * audioData . Channels ;
53+ sampleIndex = Mathf . Min ( sampleIndex , audioData . Data . Length - audioData . Channels ) ;
54+ for ( int i = 0 ; i < audioData . Channels ; ++ i )
5355 {
54- currentSample += filterReadData [ sampleIndex + i ] ;
56+ currentSample += Mathf . Abs ( audioData . Data [ sampleIndex + i ] ) ;
5557 }
56- currentSample /= filterReadChannels ;
58+ currentSample /= audioData . Channels ;
5759 }
5860 HapticPulser . Intensity = currentSample * IntensityMultiplier ;
5961 HapticPulser . Begin ( ) ;
6062 yield return null ;
6163 }
64+ RemoveDataObserver ( ) ;
6265 ResetIntensity ( ) ;
6366 }
6467
6568 /// <summary>
66- /// Store currently playing audio data and additional data.
69+ /// Adds a <see cref="AudioSourceHapticPulserDataObserver"/> to the <see cref="AudioSource"/>.
70+ /// </summary>
71+ protected virtual void AddDataObserver ( )
72+ {
73+ if ( AudioSource == null )
74+ {
75+ return ;
76+ }
77+
78+ observer = AudioSource . gameObject . AddComponent < AudioSourceDataObserver > ( ) ;
79+ observer . DataObserved . AddListener ( Receive ) ;
80+ }
81+
82+ /// <summary>
83+ /// Remove the <see cref="AudioSourceHapticPulserDataObserver"/> from the <see cref="AudioSource"/>.
84+ /// </summary>
85+ protected virtual void RemoveDataObserver ( )
86+ {
87+ if ( observer == null )
88+ {
89+ return ;
90+ }
91+
92+ observer . DataObserved . RemoveListener ( Receive ) ;
93+ Destroy ( observer ) ;
94+ observer = null ;
95+ }
96+
97+ /// <summary>
98+ /// Receive audio data from <see cref="AudioSourceHapticPulserDataObserver"/>.
99+ /// </summary>
100+ protected virtual void Receive ( AudioSourceDataObserver . EventData eventData )
101+ {
102+ audioData . Set ( eventData ) ;
103+ }
104+
105+ /// <summary>
106+ /// Called before <see cref="AudioSource"/> has been changed.
107+ /// </summary>
108+ [ CalledBeforeChangeOf ( nameof ( AudioSource ) ) ]
109+ protected virtual void OnBeforeAudioSourceChange ( )
110+ {
111+ if ( hapticRoutine == null )
112+ {
113+ return ;
114+ }
115+
116+ RemoveDataObserver ( ) ;
117+ }
118+
119+ /// <summary>
120+ /// Called after <see cref="AudioSource"/> has been changed.
121+ /// </summary>
122+ [ CalledAfterChangeOf ( nameof ( AudioSource ) ) ]
123+ protected virtual void OnAfterAudioSourceChange ( )
124+ {
125+ if ( hapticRoutine == null )
126+ {
127+ return ;
128+ }
129+
130+ AddDataObserver ( ) ;
131+ }
132+ }
133+
134+ /// <summary>
135+ /// Observes the <see cref="AudioSource"/> and emits the audio data.
136+ /// </summary>
137+ public class AudioSourceDataObserver : MonoBehaviour
138+ {
139+ /// <summary>
140+ /// Holds data about a <see cref="AudioSourceDataObserver"/> event.
141+ /// </summary>
142+ [ Serializable ]
143+ public class EventData
144+ {
145+ /// <summary>
146+ /// <see cref="AudioSettings.dspTime"/> of the last <see cref="OnAudioFilterRead"/>.
147+ /// </summary>
148+ [ Serialized ]
149+ [ field: DocumentedByXml ]
150+ public double DspTime { get ; set ; }
151+ /// <summary>
152+ /// Audio data array of the last <see cref="OnAudioFilterRead"/>.
153+ /// </summary>
154+ [ Serialized ]
155+ [ field: DocumentedByXml ]
156+ public float [ ] Data { get ; set ; }
157+ /// <summary>
158+ /// Number of channels of the last <see cref="OnAudioFilterRead"/>.
159+ /// </summary>
160+ [ Serialized ]
161+ [ field: DocumentedByXml ]
162+ public int Channels { get ; set ; }
163+
164+ public EventData Set ( EventData source )
165+ {
166+ return Set ( source . DspTime , source . Data , source . Channels ) ;
167+ }
168+
169+ public EventData Set ( double dspTime , float [ ] data , int channels )
170+ {
171+ DspTime = dspTime ;
172+ Data = data ;
173+ Channels = channels ;
174+ return this ;
175+ }
176+
177+ public void Clear ( )
178+ {
179+ Set ( default , default , default ) ;
180+ }
181+ }
182+
183+ /// <summary>
184+ /// Defines the event with the <see cref="EventData"/>.
185+ /// </summary>
186+ [ Serializable ]
187+ public class UnityEvent : UnityEvent < EventData > { }
188+
189+ /// <summary>
190+ /// Emitted whenever the audio data is observed.
191+ /// </summary>
192+ [ DocumentedByXml ]
193+ public UnityEvent DataObserved = new UnityEvent ( ) ;
194+ /// <summary>
195+ /// The data to emit with an event.
196+ /// </summary>
197+ protected readonly EventData eventData = new EventData ( ) ;
198+
199+ /// <summary>
200+ /// Emits audio data.
67201 /// </summary>
68202 /// <param name="data">An array of floats comprising the audio data.</param>
69203 /// <param name="channels">An int that stores the number of channels of audio data passed to this delegate.</param>
70204 protected virtual void OnAudioFilterRead ( float [ ] data , int channels )
71205 {
72- filterReadDspTime = AudioSettings . dspTime ;
73- filterReadData = data ;
74- filterReadChannels = channels ;
206+ DataObserved ? . Invoke ( eventData . Set ( AudioSettings . dspTime , data , channels ) ) ;
75207 }
76208 }
77209}
0 commit comments