-
Notifications
You must be signed in to change notification settings - Fork 57
Expand file tree
/
Copy pathAudioPulseDetector.cs
More file actions
89 lines (76 loc) · 2.85 KB
/
AudioPulseDetector.cs
File metadata and controls
89 lines (76 loc) · 2.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
using System;
using UnityEngine;
namespace LiveKit.PlayModeTests.Utils
{
/// <summary>
/// MonoBehaviour that identifies audio pulses by frequency using the Goertzel algorithm.
/// Attach to the same GameObject as an AudioSource after AudioStream has added its
/// AudioProbe, so this filter runs second and sees the filled audio data.
/// </summary>
public class AudioPulseDetector : MonoBehaviour
{
public int TotalPulses;
public double BaseFrequency;
public double FrequencyStep;
public double MagnitudeThreshold;
/// <summary>
/// Fired on the audio thread when a pulse is detected.
/// Parameters: (pulseIndex, magnitude).
/// </summary>
public event Action<int, double> PulseReceived;
private int _sampleRate;
void OnEnable()
{
_sampleRate = AudioSettings.outputSampleRate;
AudioSettings.OnAudioConfigurationChanged += OnAudioConfigChanged;
}
void OnDisable()
{
AudioSettings.OnAudioConfigurationChanged -= OnAudioConfigChanged;
}
void OnAudioConfigChanged(bool deviceWasChanged)
{
_sampleRate = AudioSettings.outputSampleRate;
}
void OnAudioFilterRead(float[] data, int channels)
{
int sampleRate = _sampleRate;
int samples = data.Length / channels;
if (samples == 0) return;
int bestPulse = -1;
double bestMag = 0;
for (int p = 0; p < TotalPulses; p++)
{
double freq = BaseFrequency + p * FrequencyStep;
double mag = Goertzel(data, channels, samples, sampleRate, freq);
if (mag > bestMag)
{
bestMag = mag;
bestPulse = p;
}
}
// Debug.Log($"bestPulse: {bestPulse} | bestMag: {bestMag}");
if (bestPulse >= 0 && bestMag > MagnitudeThreshold)
PulseReceived?.Invoke(bestPulse, bestMag);
}
/// <summary>
/// Goertzel algorithm — computes the magnitude of a single frequency bin.
/// O(N) per frequency, much cheaper than a full FFT.
/// </summary>
static double Goertzel(float[] data, int channels, int N, int sampleRate, double freq)
{
double k = 0.5 + (double)N * freq / sampleRate;
double w = 2.0 * Math.PI * k / N;
double coeff = 2.0 * Math.Cos(w);
double s0 = 0, s1 = 0, s2 = 0;
for (int i = 0; i < N; i++)
{
s0 = data[i * channels] + coeff * s1 - s2;
s2 = s1;
s1 = s0;
}
double power = s1 * s1 + s2 * s2 - coeff * s1 * s2;
return Math.Sqrt(Math.Abs(power)) / N;
}
}
}