rill_patchbay/sensor/
hearing.rs1use std::collections::VecDeque;
11
12pub trait Hearing: Send + 'static {
14 fn process(&mut self, audio: &[f32]) -> f32;
16
17 fn name(&self) -> &str;
19}
20
21pub struct PitchDetector {
23 sample_rate: f32,
24 min_freq: f32,
25 max_freq: f32,
26 last_pitch: f32,
27 buffer: VecDeque<f32>,
28}
29
30impl PitchDetector {
31 pub fn new(sample_rate: f32) -> Self {
33 Self {
34 sample_rate,
35 min_freq: 20.0,
36 max_freq: 2000.0,
37 last_pitch: 0.0,
38 buffer: VecDeque::with_capacity(2048),
39 }
40 }
41
42 fn autocorrelate(&self, signal: &[f32]) -> Option<f32> {
43 if signal.len() < 100 {
44 return None;
45 }
46 let min_period = (self.sample_rate / self.max_freq) as usize;
47 let max_period = (self.sample_rate / self.min_freq) as usize;
48 let mut best_corr = 0.0;
49 let mut best_period = min_period;
50
51 for period in min_period..max_period.min(signal.len() / 2) {
52 let mut corr = 0.0;
53 let mut energy = 0.0;
54 for i in 0..period {
55 if i + period < signal.len() {
56 corr += signal[i] * signal[i + period];
57 energy += signal[i] * signal[i] + signal[i + period] * signal[i + period];
58 }
59 }
60 if energy > 0.0 {
61 let norm_corr = corr / (energy.sqrt() + 1e-6);
62 if norm_corr > best_corr {
63 best_corr = norm_corr;
64 best_period = period;
65 }
66 }
67 }
68
69 if best_corr > 0.1 {
70 Some(self.sample_rate / best_period as f32)
71 } else {
72 None
73 }
74 }
75}
76
77impl Hearing for PitchDetector {
78 fn process(&mut self, audio: &[f32]) -> f32 {
79 for &sample in audio {
80 self.buffer.push_back(sample);
81 }
82 while self.buffer.len() > 2048 {
83 self.buffer.pop_front();
84 }
85 let signal: Vec<f32> = self.buffer.iter().copied().collect();
86 if let Some(pitch) = self.autocorrelate(&signal) {
87 self.last_pitch = pitch;
88 }
89 (self.last_pitch - self.min_freq) / (self.max_freq - self.min_freq)
90 }
91
92 fn name(&self) -> &str {
93 "pitch"
94 }
95}
96
97pub struct EnvelopeFollower {
99 attack: f32,
100 release: f32,
101 envelope: f32,
102 sample_rate: f32,
103}
104
105impl EnvelopeFollower {
106 pub fn new(sample_rate: f32) -> Self {
108 Self {
109 attack: 0.01,
110 release: 0.1,
111 envelope: 0.0,
112 sample_rate,
113 }
114 }
115
116 pub fn with_attack(mut self, attack_sec: f32) -> Self {
118 self.attack = attack_sec;
119 self
120 }
121
122 pub fn with_release(mut self, release_sec: f32) -> Self {
124 self.release = release_sec;
125 self
126 }
127}
128
129impl Hearing for EnvelopeFollower {
130 fn process(&mut self, audio: &[f32]) -> f32 {
131 let attack_coef = (-1.0 / (self.attack * self.sample_rate)).exp();
132 let release_coef = (-1.0 / (self.release * self.sample_rate)).exp();
133 for &sample in audio {
134 let input = sample.abs();
135 if input > self.envelope {
136 self.envelope = attack_coef * self.envelope + (1.0 - attack_coef) * input;
137 } else {
138 self.envelope = release_coef * self.envelope + (1.0 - release_coef) * input;
139 }
140 }
141 self.envelope
142 }
143
144 fn name(&self) -> &str {
145 "envelope"
146 }
147}
148
149pub struct ZeroCrossing {
151 last_sample: f32,
152 crossings: u32,
153 samples: u32,
154 sample_rate: f32,
155 frequency: f32,
156}
157
158impl ZeroCrossing {
159 pub fn new(sample_rate: f32) -> Self {
161 Self {
162 last_sample: 0.0,
163 crossings: 0,
164 samples: 0,
165 sample_rate,
166 frequency: 0.0,
167 }
168 }
169}
170
171impl Hearing for ZeroCrossing {
172 fn process(&mut self, audio: &[f32]) -> f32 {
173 for &sample in audio {
174 if self.last_sample <= 0.0 && sample > 0.0 {
175 self.crossings += 1;
176 }
177 self.last_sample = sample;
178 self.samples += 1;
179 }
180 if self.samples > self.sample_rate as u32 / 10 {
181 self.frequency = self.crossings as f32 / (self.samples as f32 / self.sample_rate);
182 self.crossings = 0;
183 self.samples = 0;
184 }
185 self.frequency / 1000.0
186 }
187
188 fn name(&self) -> &str {
189 "zero_crossing"
190 }
191}