-
Notifications
You must be signed in to change notification settings - Fork 6
/
beat.rs
224 lines (195 loc) · 6.6 KB
/
beat.rs
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
//! Beat Detection
use crate::analyzer;
/// Builder for BeatDetector
///
/// Your configuration needs to be a tradeoff between quality of beat-detection
/// and latency.
///
/// The latency is roughly: `(fourier_length * downsample) / rate` seconds
#[derive(Debug, Default)]
pub struct BeatBuilder {
/// Decay of the beat volume
///
/// The lower this is, the faster a more silent beat will be detected.
/// Defaults to `2000.0`. Can also be set from config as `"audio.beat.decay"`.
pub decay: Option<analyzer::SignalStrength>,
/// The minimum volume a beat must have, relative to the previous one, to be deteced.
///
/// Defaults to `0.4`. Can also be set from config as `"audio.beat.trigger"`.
pub trigger: Option<analyzer::SignalStrength>,
/// Frequency range to search for beats in.
///
/// Defaults to `50 Hz - 100 Hz`, can also be set from config as `"audio.beat.low"`
/// and `"audio.beat.high"`
pub range: Option<(analyzer::Frequency, analyzer::Frequency)>,
/// Length of the fourier transform for beat detection.
///
/// Keep this as short as possible to minimize delay! Defaults to 16, can also
/// be set from config as `"audio.beat.fourier_length"`.
pub fourier_length: Option<usize>,
/// Downsampling factor
///
/// Defaults to 10 and can be set from config as `"audio.beat.downsample"`.
pub downsample: Option<usize>,
/// Recording rate
///
///
/// Defaults to `8000` or `"audio.rate"`.
pub rate: Option<usize>,
}
impl BeatBuilder {
/// Create new BeatBuilder
pub fn new() -> BeatBuilder {
Default::default()
}
/// Set decay
pub fn decay(&mut self, decay: analyzer::SignalStrength) -> &mut BeatBuilder {
self.decay = Some(decay);
self
}
/// Set trigger
pub fn trigger(&mut self, trigger: analyzer::SignalStrength) -> &mut BeatBuilder {
self.trigger = Some(trigger);
self
}
/// Set frequency range
pub fn range(
&mut self,
low: analyzer::Frequency,
high: analyzer::Frequency,
) -> &mut BeatBuilder {
self.range = Some((low, high));
self
}
/// Set fourier length
pub fn fourier_length(&mut self, length: usize) -> &mut BeatBuilder {
self.fourier_length = Some(length);
self
}
/// Set downsampling factor
pub fn downsample(&mut self, downsample: usize) -> &mut BeatBuilder {
self.downsample = Some(downsample);
self
}
/// Set recording rate
pub fn rate(&mut self, rate: usize) -> &mut BeatBuilder {
self.rate = Some(rate);
self
}
/// Build the detector
pub fn build(&mut self) -> BeatDetector {
BeatDetector::from_builder(self)
}
}
/// A beat detector
///
/// # Example
/// ```
/// # use vis_core::analyzer;
/// # let samples = analyzer::SampleBuffer::new(32000, 8000);
/// let mut beat = analyzer::BeatBuilder::new()
/// .decay(2000.0)
/// .trigger(0.4)
/// .range(50.0, 100.0)
/// .fourier_length(16)
/// .downsample(10)
/// .rate(8000)
/// .build();
///
/// let isbeat = beat.detect(&samples);
/// ```
pub struct BeatDetector {
decay: analyzer::SignalStrength,
trigger: analyzer::SignalStrength,
range: (analyzer::Frequency, analyzer::Frequency),
last_volume: analyzer::SignalStrength,
last_delta: analyzer::SignalStrength,
last_beat_delta: analyzer::SignalStrength,
last_peak: analyzer::SignalStrength,
last_valley: analyzer::SignalStrength,
analyzer: analyzer::FourierAnalyzer,
}
impl BeatDetector {
/// Create a BeatDetector from a builder config
pub fn from_builder(build: &BeatBuilder) -> BeatDetector {
let decay = build
.decay
.unwrap_or_else(|| crate::CONFIG.get_or("audio.beat.decay", 2000.0));
BeatDetector {
decay: 1.0 - 1.0 / decay,
trigger: build
.trigger
.unwrap_or_else(|| crate::CONFIG.get_or("audio.beat.trigger", 0.4)),
range: build.range.unwrap_or_else(|| {
(
crate::CONFIG.get_or("audio.beat.low", 50.0),
crate::CONFIG.get_or("audio.beat.high", 100.0),
)
}),
last_volume: 0.0,
last_delta: 0.0,
last_beat_delta: 0.0,
last_peak: 0.0,
last_valley: 0.0,
analyzer: analyzer::FourierBuilder {
window: Some(analyzer::window::nuttall),
length: Some(
build
.fourier_length
.unwrap_or_else(|| crate::CONFIG.get_or("audio.beat.fourier_length", 16)),
),
downsample: Some(
build
.downsample
.unwrap_or_else(|| crate::CONFIG.get_or("audio.beat.downsample", 10)),
),
rate: Some(
build
.rate
.unwrap_or_else(|| crate::CONFIG.get_or("audio.rate", 8000)),
),
}
.plan(),
}
}
/// Get the volume measured during the last detection cycle
pub fn last_volume(&self) -> analyzer::SignalStrength {
self.last_volume
}
/// Detect a beat
///
/// Returns true if this cycle is a beat and false otherwise.
pub fn detect(&mut self, samples: &analyzer::SampleBuffer) -> bool {
self.analyzer.analyze(samples);
let volume = self
.analyzer
.average()
.slice(self.range.0, self.range.1)
.mean();
// Decay beat_delta to allow quieter beats to be detected
self.last_beat_delta = self.last_beat_delta * self.decay;
let delta = volume - self.last_volume;
let isbeat = if delta < 0.0 && self.last_delta > 0.0 {
self.last_peak = self.last_volume;
let beat_delta = self.last_peak - self.last_valley;
// Check if the peak is big enough
if beat_delta > (self.last_beat_delta * self.trigger) {
self.last_beat_delta = self.last_beat_delta.max(beat_delta);
true
} else {
false
}
} else if delta > 0.0 && self.last_delta < 0.0 {
self.last_valley = self.last_volume;
false
} else {
false
};
self.last_volume = volume;
// Only write delta if the last two volumes weren't the same
if delta != 0.0 {
self.last_delta = delta;
}
isbeat
}
}