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,65 +17,200 @@ 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 ( )
3535 {
3636 return base . IsActive ( ) && AudioSource != null ;
3737 }
3838
39+ /// <inheritdoc />
40+ protected override void DoCancel ( )
41+ {
42+ RemoveDataObserver ( ) ;
43+ base . DoCancel ( ) ;
44+ }
45+
3946 /// <summary>
4047 /// Enumerates through <see cref="AudioSource"/> and pulses for each amplitude of the wave.
4148 /// </summary>
4249 /// <returns>An Enumerator to manage the running of the Coroutine.</returns>
4350 protected override IEnumerator HapticProcessRoutine ( )
4451 {
52+ AddDataObserver ( ) ;
4553 int outputSampleRate = AudioSettings . outputSampleRate ;
46- while ( AudioSource . isPlaying )
54+ while ( AudioSource != null && AudioSource . isPlaying )
4755 {
48- int sampleIndex = ( int ) ( ( AudioSettings . dspTime - filterReadDspTime ) * outputSampleRate ) ;
4956 float currentSample = 0 ;
50- if ( filterReadData != null && sampleIndex * filterReadChannels < filterReadData . Length )
57+ if ( audioData . Data != null )
5158 {
52- for ( int i = 0 ; i < filterReadChannels ; ++ i )
59+ int sampleIndex = ( int ) ( ( AudioSettings . dspTime - audioData . DspTime ) * outputSampleRate ) * audioData . Channels ;
60+ sampleIndex = Mathf . Min ( sampleIndex , audioData . Data . Length - audioData . Channels ) ;
61+ for ( int i = 0 ; i < audioData . Channels ; ++ i )
5362 {
54- currentSample += filterReadData [ sampleIndex + i ] ;
63+ currentSample += Mathf . Abs ( audioData . Data [ sampleIndex + i ] ) ;
5564 }
56- currentSample /= filterReadChannels ;
65+ currentSample /= audioData . Channels ;
5766 }
5867 HapticPulser . Intensity = currentSample * IntensityMultiplier ;
5968 HapticPulser . Begin ( ) ;
6069 yield return null ;
6170 }
71+ RemoveDataObserver ( ) ;
6272 ResetIntensity ( ) ;
6373 }
6474
6575 /// <summary>
66- /// Store currently playing audio data and additional data.
76+ /// Adds a <see cref="AudioSourceHapticPulserDataObserver"/> to the <see cref="AudioSource"/>.
77+ /// </summary>
78+ protected virtual void AddDataObserver ( )
79+ {
80+ if ( AudioSource == null )
81+ {
82+ return ;
83+ }
84+
85+ observer = AudioSource . gameObject . AddComponent < AudioSourceDataObserver > ( ) ;
86+ observer . DataObserved . AddListener ( Receive ) ;
87+ }
88+
89+ /// <summary>
90+ /// Remove the <see cref="AudioSourceHapticPulserDataObserver"/> from the <see cref="AudioSource"/>.
91+ /// </summary>
92+ protected virtual void RemoveDataObserver ( )
93+ {
94+ if ( observer == null )
95+ {
96+ return ;
97+ }
98+
99+ observer . DataObserved . RemoveListener ( Receive ) ;
100+ Destroy ( observer ) ;
101+ observer = null ;
102+ }
103+
104+ /// <summary>
105+ /// Receive audio data from <see cref="AudioSourceHapticPulserDataObserver"/>.
106+ /// </summary>
107+ protected virtual void Receive ( AudioSourceDataObserver . EventData eventData )
108+ {
109+ audioData . Set ( eventData ) ;
110+ }
111+
112+ /// <summary>
113+ /// Called before <see cref="AudioSource"/> has been changed.
114+ /// </summary>
115+ [ CalledBeforeChangeOf ( nameof ( AudioSource ) ) ]
116+ protected virtual void OnBeforeAudioSourceChange ( )
117+ {
118+ if ( hapticRoutine == null )
119+ {
120+ return ;
121+ }
122+
123+ RemoveDataObserver ( ) ;
124+ }
125+
126+ /// <summary>
127+ /// Called after <see cref="AudioSource"/> has been changed.
128+ /// </summary>
129+ [ CalledAfterChangeOf ( nameof ( AudioSource ) ) ]
130+ protected virtual void OnAfterAudioSourceChange ( )
131+ {
132+ if ( hapticRoutine == null )
133+ {
134+ return ;
135+ }
136+
137+ AddDataObserver ( ) ;
138+ }
139+ }
140+
141+ /// <summary>
142+ /// Observes the <see cref="AudioSource"/> and emits the audio data.
143+ /// </summary>
144+ public class AudioSourceDataObserver : MonoBehaviour
145+ {
146+ /// <summary>
147+ /// Holds data about a <see cref="AudioSourceDataObserver"/> event.
148+ /// </summary>
149+ [ Serializable ]
150+ public class EventData
151+ {
152+ /// <summary>
153+ /// <see cref="AudioSettings.dspTime"/> of the last <see cref="OnAudioFilterRead"/>.
154+ /// </summary>
155+ [ Serialized ]
156+ [ field: DocumentedByXml ]
157+ public double DspTime { get ; set ; }
158+ /// <summary>
159+ /// Audio data array of the last <see cref="OnAudioFilterRead"/>.
160+ /// </summary>
161+ [ Serialized ]
162+ [ field: DocumentedByXml ]
163+ public float [ ] Data { get ; set ; }
164+ /// <summary>
165+ /// Number of channels of the last <see cref="OnAudioFilterRead"/>.
166+ /// </summary>
167+ [ Serialized ]
168+ [ field: DocumentedByXml ]
169+ public int Channels { get ; set ; }
170+
171+ public EventData Set ( EventData source )
172+ {
173+ return Set ( source . DspTime , source . Data , source . Channels ) ;
174+ }
175+
176+ public EventData Set ( double dspTime , float [ ] data , int channels )
177+ {
178+ DspTime = dspTime ;
179+ Data = data ;
180+ Channels = channels ;
181+ return this ;
182+ }
183+
184+ public void Clear ( )
185+ {
186+ Set ( default , default , default ) ;
187+ }
188+ }
189+
190+ /// <summary>
191+ /// Defines the event with the <see cref="EventData"/>.
192+ /// </summary>
193+ [ Serializable ]
194+ public class UnityEvent : UnityEvent < EventData > { }
195+
196+ /// <summary>
197+ /// Emitted whenever the audio data is observed.
198+ /// </summary>
199+ [ DocumentedByXml ]
200+ public UnityEvent DataObserved = new UnityEvent ( ) ;
201+ /// <summary>
202+ /// The data to emit with an event.
203+ /// </summary>
204+ protected readonly EventData eventData = new EventData ( ) ;
205+
206+ /// <summary>
207+ /// Emits audio data.
67208 /// </summary>
68209 /// <param name="data">An array of floats comprising the audio data.</param>
69210 /// <param name="channels">An int that stores the number of channels of audio data passed to this delegate.</param>
70211 protected virtual void OnAudioFilterRead ( float [ ] data , int channels )
71212 {
72- filterReadDspTime = AudioSettings . dspTime ;
73- filterReadData = data ;
74- filterReadChannels = channels ;
213+ DataObserved ? . Invoke ( eventData . Set ( AudioSettings . dspTime , data , channels ) ) ;
75214 }
76215 }
77216}
0 commit comments