Skip to content

Commit

Permalink
Merge pull request #444 from fight4dream/feat/audioSourceHapticPulser
Browse files Browse the repository at this point in the history
feat(Haptics): create haptic pattern based on AudioSource
  • Loading branch information
Christopher-Marcel Böddecker authored Oct 29, 2019
2 parents a05961a + a5dfce7 commit 93272b7
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 53 deletions.
57 changes: 4 additions & 53 deletions Runtime/Haptics/AudioClipHapticPulser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,90 +2,41 @@
{
using UnityEngine;
using System.Collections;
using Malimbe.XmlDocumentationAttribute;
using Malimbe.PropertySerializationAttribute;
using Malimbe.XmlDocumentationAttribute;

/// <summary>
/// Creates a haptic pattern based on the waveform of an <see cref="UnityEngine.AudioClip"/> and utilizes a <see cref="Haptics.HapticPulser"/> to create the effect.
/// </summary>
public class AudioClipHapticPulser : HapticProcess
public class AudioClipHapticPulser : RoutineHapticPulser
{
/// <summary>
/// The pulse process to utilize.
/// </summary>
[Serialized]
[field: DocumentedByXml]
public HapticPulser HapticPulser { get; set; }
/// <summary>
/// The waveform to represent the haptic pattern.
/// </summary>
[Serialized]
[field: DocumentedByXml]
public AudioClip AudioClip { get; set; }
/// <summary>
/// Multiplies the current audio peak to affect the wave intensity.
/// </summary>
[Serialized]
[field: DocumentedByXml]
public float IntensityMultiplier { get; set; } = 1f;

/// <summary>
/// The size of the audio buffer.
/// </summary>
protected const int BufferSize = 8192;
/// <summary>
/// A reference to the started routine.
/// </summary>
protected Coroutine hapticRoutine;
/// <summary>
/// The original intensity of <see cref="HapticPulser"/> to reset back to after the process is complete.
/// </summary>
protected float cachedIntensity;
/// <summary>
/// The audio data buffer.
/// </summary>
protected readonly float[] audioData = new float[BufferSize];

/// <inheritdoc />
public override bool IsActive()
{
return base.IsActive() && HapticPulser != null && AudioClip != null && HapticPulser.IsActive();
}

/// <inheritdoc />
protected override void DoBegin()
{
cachedIntensity = HapticPulser.Intensity;
hapticRoutine = StartCoroutine(HapticProcessRoutine());
}

/// <inheritdoc />
protected override void DoCancel()
{
if (hapticRoutine == null)
{
return;
}

StopCoroutine(hapticRoutine);
hapticRoutine = null;
HapticPulser.Cancel();
ResetIntensity();
}

/// <summary>
/// Resets the <see cref="Haptics.HapticPulser.Intensity"/> back to it's original value.
/// </summary>
protected virtual void ResetIntensity()
{
HapticPulser.Intensity = cachedIntensity;
return base.IsActive() && AudioClip != null;
}

/// <summary>
/// Enumerates through <see cref="AudioClip"/> and pulses for each amplitude of the wave.
/// </summary>
/// <returns>An Enumerator to manage the running of the Coroutine.</returns>
protected virtual IEnumerator HapticProcessRoutine()
protected override IEnumerator HapticProcessRoutine()
{
int sampleOffset = -BufferSize;
float startTime = Time.time;
Expand Down
77 changes: 77 additions & 0 deletions Runtime/Haptics/AudioSourceHapticPulser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
namespace Zinnia.Haptics
{
using UnityEngine;
using System.Collections;
using Malimbe.PropertySerializationAttribute;
using Malimbe.XmlDocumentationAttribute;

/// <summary>
/// Creates a haptic pattern based on the waveform of an <see cref="UnityEngine.AudioSource"/> and utilizes a <see cref="Haptics.HapticPulser"/> to create the effect.
/// </summary>
public class AudioSourceHapticPulser : RoutineHapticPulser
{
/// <summary>
/// The waveform to represent the haptic pattern.
/// </summary>
[Serialized]
[field: DocumentedByXml]
public AudioSource AudioSource { get; set; }

/// <summary>
/// <see cref="AudioSettings.dspTime"/> of the last <see cref="OnAudioFilterRead"/>.
/// </summary>
protected double filterReadDspTime;
/// <summary>
/// Audio data array of the last <see cref="OnAudioFilterRead"/>.
/// </summary>
protected float[] filterReadData;
/// <summary>
/// Number of channels of the last <see cref="OnAudioFilterRead"/>.
/// </summary>
protected int filterReadChannels;

/// <inheritdoc />
public override bool IsActive()
{
return base.IsActive() && AudioSource != null;
}

/// <summary>
/// Enumerates through <see cref="AudioSource"/> and pulses for each amplitude of the wave.
/// </summary>
/// <returns>An Enumerator to manage the running of the Coroutine.</returns>
protected override IEnumerator HapticProcessRoutine()
{
int outputSampleRate = AudioSettings.outputSampleRate;
while (AudioSource.isPlaying)
{
int sampleIndex = (int)((AudioSettings.dspTime - filterReadDspTime) * outputSampleRate);
float currentSample = 0;
if (filterReadData != null && sampleIndex * filterReadChannels < filterReadData.Length)
{
for (int i = 0; i < filterReadChannels; ++i)
{
currentSample += filterReadData[sampleIndex + i];
}
currentSample /= filterReadChannels;
}
HapticPulser.Intensity = currentSample * IntensityMultiplier;
HapticPulser.Begin();
yield return null;
}
ResetIntensity();
}

/// <summary>
/// Store currently playing audio data and additional data.
/// </summary>
/// <param name="data">An array of floats comprising the audio data.</param>
/// <param name="channels">An int that stores the number of channels of audio data passed to this delegate.</param>
protected virtual void OnAudioFilterRead(float[] data, int channels)
{
filterReadDspTime = AudioSettings.dspTime;
filterReadData = data;
filterReadChannels = channels;
}
}
}
11 changes: 11 additions & 0 deletions Runtime/Haptics/AudioSourceHapticPulser.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

76 changes: 76 additions & 0 deletions Runtime/Haptics/RoutineHapticPulser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
namespace Zinnia.Haptics
{
using UnityEngine;
using System.Collections;
using Malimbe.PropertySerializationAttribute;
using Malimbe.XmlDocumentationAttribute;

/// <summary>
/// Creates a haptic pattern based on a custom routine and utilizes a <see cref="Haptics.HapticPulser"/> to create the effect.
/// </summary>
public abstract class RoutineHapticPulser : HapticProcess
{
/// <summary>
/// The pulse process to utilize.
/// </summary>
[Serialized]
[field: DocumentedByXml]
public HapticPulser HapticPulser { get; set; }
/// <summary>
/// Multiplies the current audio peak to affect the wave intensity.
/// </summary>
[Serialized]
[field: DocumentedByXml]
public float IntensityMultiplier { get; set; } = 1f;

/// <summary>
/// A reference to the started routine.
/// </summary>
protected Coroutine hapticRoutine;
/// <summary>
/// The original intensity of <see cref="HapticPulser"/> to reset back to after the process is complete.
/// </summary>
protected float cachedIntensity;

/// <inheritdoc />
public override bool IsActive()
{
return base.IsActive() && HapticPulser != null && HapticPulser.IsActive();
}

/// <inheritdoc />
protected override void DoBegin()
{
cachedIntensity = HapticPulser.Intensity;
hapticRoutine = StartCoroutine(HapticProcessRoutine());
}

/// <inheritdoc />
protected override void DoCancel()
{
if (hapticRoutine == null)
{
return;
}

StopCoroutine(hapticRoutine);
hapticRoutine = null;
HapticPulser.Cancel();
ResetIntensity();
}

/// <summary>
/// Resets the <see cref="Haptics.HapticPulser.Intensity"/> back to its original value.
/// </summary>
protected virtual void ResetIntensity()
{
HapticPulser.Intensity = cachedIntensity;
}

/// <summary>
/// A custom routine to generate a haptic pattern.
/// </summary>
/// <returns>An Enumerator to manage the running of the Coroutine.</returns>
protected abstract IEnumerator HapticProcessRoutine();
}
}
11 changes: 11 additions & 0 deletions Runtime/Haptics/RoutineHapticPulser.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 93272b7

Please sign in to comment.