Skip to content

Commit 0144f60

Browse files
committed
XXX WIP
1 parent 5e5a873 commit 0144f60

File tree

8 files changed

+364
-54
lines changed

8 files changed

+364
-54
lines changed

src/audio_history.rs

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
1+
use crate::envelope_iterator::ENVELOPE_MIN_DURATION_MS;
12
use core::cmp::Ordering;
23
use core::time::Duration;
34
use ringbuffer::{ConstGenericRingBuffer, RingBuffer};
45

5-
/// Default buffer size for [`AudioHistory`]. For a typical (and to be expected)
6-
/// sampling frequency of 44100Hz, this corresponds to 1000ms which is more than
7-
/// enough to detect beats that are typically at 60Hz, with a period duration
8-
/// of 20ms (or 20Hz at 50ms). This results in roughly 35kib of stack usage
9-
/// for the audio buffer.
10-
///
11-
/// TODO I plan to reduce this to 500, 300, or even 200ms to reduce memory usage.
12-
/// However, during development, a large ubffer size is necessary to tune the
13-
/// algorithm in a way that it reliably works for data analysis when the full
14-
/// sample is in memory. If that works reliably, I can test the library in a
15-
/// "data streaming approach" and discover beats one by one. However, for now,
16-
/// I keep it very basic and easy.
17-
pub const DEFAULT_BUFFER_SIZE: usize = 44100;
6+
const SAFE_MIN_DURATION_MS: usize = (ENVELOPE_MIN_DURATION_MS as f64 * 2.5) as usize;
7+
8+
/// Based on the de-facto default sampling rate of 44100 Hz / 44.1 kHz.
9+
const DEFAULT_SAMPLES_PER_SECOND: usize = 44100;
10+
const MS_PER_SECOND: usize = 1000;
11+
12+
/// Default buffer size for [`AudioHistory`]. The size is a trade-off between
13+
/// memory efficiency and effectiveness in detecting envelops properly.
14+
pub const DEFAULT_BUFFER_SIZE: usize =
15+
(SAFE_MIN_DURATION_MS * DEFAULT_SAMPLES_PER_SECOND) / MS_PER_SECOND;
1816

1917
/// Sample info with time context.
2018
#[derive(Copy, Clone, Debug)]
@@ -87,6 +85,7 @@ impl AudioHistory {
8785

8886
/// Update the audio history with fresh samples. The audio samples are
8987
/// expected to be in mono channel, i.e., no stereo interleaving
88+
/// TODO: Update consume iterator and consume enum: Interleaved or Mono
9089
pub fn update(&mut self, mono_samples: &[f32]) {
9190
if mono_samples.len() >= self.audio_buffer.capacity() {
9291
log::warn!(
@@ -183,9 +182,18 @@ impl AudioHistory {
183182
#[cfg(test)]
184183
mod tests {
185184
use super::*;
186-
use crate::test_util::{sample_1_test_data, single_beat_test_data};
185+
use crate::test_util::{sample_1_test_data};
187186
use std::prelude::v1::Vec;
188187

188+
#[test]
189+
fn buffer_len_sane() {
190+
let sampling_rate = 1.0 / DEFAULT_SAMPLES_PER_SECOND as f32;
191+
let duration = Duration::from_secs_f32(sampling_rate * DEFAULT_BUFFER_SIZE as f32);
192+
dbg!(duration);
193+
assert!(duration.as_millis() > 10);
194+
assert!(duration.as_millis() <= 1000);
195+
}
196+
189197
#[test]
190198
fn audio_duration_is_updated_properly() {
191199
let mut hist = AudioHistory::new(2.0);
@@ -277,18 +285,37 @@ mod tests {
277285
let mut hist = AudioHistory::new(1.0);
278286

279287
hist.update(&[0.0]);
280-
assert_eq!(hist.index_to_sample_info(0).duration_behind, Duration::from_secs(0));
288+
assert_eq!(
289+
hist.index_to_sample_info(0).duration_behind,
290+
Duration::from_secs(0)
291+
);
281292
hist.update(&[0.0]);
282-
assert_eq!(hist.index_to_sample_info(0).duration_behind, Duration::from_secs(1));
283-
assert_eq!(hist.index_to_sample_info(1).duration_behind, Duration::from_secs(0));
284-
293+
assert_eq!(
294+
hist.index_to_sample_info(0).duration_behind,
295+
Duration::from_secs(1)
296+
);
297+
assert_eq!(
298+
hist.index_to_sample_info(1).duration_behind,
299+
Duration::from_secs(0)
300+
);
285301

286302
hist.update(&[0.0].repeat(hist.data().capacity() * 2));
287303

288304
let sample = hist.index_to_sample_info(0);
289-
assert_eq!(hist.index_to_sample_info(0).duration_behind, Duration::from_secs_f32((DEFAULT_BUFFER_SIZE - 1) as f32));
290-
assert_eq!(hist.index_to_sample_info(DEFAULT_BUFFER_SIZE - 10).duration_behind, Duration::from_secs_f32(9.0));
291-
assert_eq!(hist.index_to_sample_info(DEFAULT_BUFFER_SIZE - 1).duration_behind, Duration::from_secs(0));
305+
assert_eq!(
306+
hist.index_to_sample_info(0).duration_behind,
307+
Duration::from_secs_f32((DEFAULT_BUFFER_SIZE - 1) as f32)
308+
);
309+
assert_eq!(
310+
hist.index_to_sample_info(DEFAULT_BUFFER_SIZE - 10)
311+
.duration_behind,
312+
Duration::from_secs_f32(9.0)
313+
);
314+
assert_eq!(
315+
hist.index_to_sample_info(DEFAULT_BUFFER_SIZE - 1)
316+
.duration_behind,
317+
Duration::from_secs(0)
318+
);
292319
}
293320

294321
#[test]

src/audio_input.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
pub enum AudioInput<'a> {
2+
/// The audio input stream only consists of mono samples.
3+
Mono(&'a [f32]),
4+
/// The audio input streams consists of interleaved samples following a
5+
/// LRLRLR scheme. This is typically the case for stereo channel audio.
6+
InterleavedLR(&'a [f32]),
7+
}
8+
9+
impl<'a> AudioInput<'a> {
10+
pub fn iter(&self) -> impl Iterator<Item = f32> {
11+
match self {
12+
AudioInput::Mono(samples) => samples.iter(),
13+
AudioInput::InterleavedLR(samples) => samples.chunks(2).map(|lr| (lr[0] + lr[1]) / 2.0)
14+
}
15+
}
16+
}
17+
18+
#[cfg(test)]
19+
mod tests {
20+
21+
fn stereo_to:
22+
23+
}

src/envelope_iterator.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ const ENVELOPE_MIN_VALUE: f32 = 0.2;
88
/// The factor by that a peak must be higher than the begin and end so that we
99
/// in fact found an envelope that represents a beat.
1010
const ENVELOPE_PEAK_RATIO: f32 = 2.5;
11-
const ENVELOPE_MIN_DURATION: Duration = Duration::from_millis(100);
11+
12+
pub(crate) const ENVELOPE_MIN_DURATION_MS: u64 = 160;
13+
14+
/// Minimum realistic duration of an envelope. This value is the result of
15+
/// analyzing some waveforms in Audacity. Specifically, this results from an
16+
/// envelope of two beats very close to each other.
17+
const ENVELOPE_MIN_DURATION: Duration = Duration::from_millis(ENVELOPE_MIN_DURATION_MS);
1218

1319
/// Iterates the envelopes of an audio signal. An envelope is the set of
1420
/// vibrations(? - german: Schwingungen) that characterize a beat. Its waveform
@@ -164,7 +170,6 @@ impl PartialEq for EnvelopeInfo {
164170
mod tests {
165171
use super::*;
166172
use crate::test_util::*;
167-
use std::collections::{BTreeMap, BTreeSet};
168173
use std::vec::Vec;
169174

170175
#[test]
@@ -244,12 +249,12 @@ mod tests {
244249
let mut hist = AudioHistory::new(header.sampling_rate as f32);
245250

246251
// 5ms at 44.1kHz - realistic value for an audio input device
247-
let mut begin_index = None;
252+
/*let mut begin_index = None;
248253
for chunk in vec.chunks(250) {
249254
hist.update(chunk);
250255
let iter = EnvelopeIterator::new(&hist, begin_index);
251256
let maybe_envelop = iter.next();
252-
}
257+
}*/
253258
/*.flat_map(|chunks| {
254259
hist.update(chunks);
255260
let iter = EnvelopeIterator::new(&hist, None);

src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,14 @@ SOFTWARE.
8383
extern crate std;
8484

8585
mod audio_history;
86-
mod root_iterator;
8786
mod envelope_iterator;
8887
mod max_min_iterator;
88+
mod root_iterator;
8989

9090
// Todo export function to combine large audio stream with my code lololo
9191

92+
mod sample;
9293
/// PRIVATE. For tests and helper binaries.
9394
#[cfg(test)]
9495
mod test_util;
95-
96+
mod audio_input;

src/max_min_iterator.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ mod tests {
8484
// I checked in Audacity whether the values returned by the code
8585
// make sense. Then, they became the reference for the test.
8686
[
87-
(0, -0.07258828),
87+
(0, -0.07258828), // TODO should not have zero here?!
8888
(71, 0.5884732),
8989
(323, -0.7123936),
9090
(608, 0.599353),

src/root_iterator.rs

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -51,40 +51,25 @@ mod tests {
5151
use crate::test_util::*;
5252
use std::vec::Vec;
5353

54-
#[test]
55-
fn find_roots_simple() {
56-
let history = simple_test_data();
57-
let iter = RootIterator::new(&history, None);
58-
#[rustfmt::skip]
59-
assert_eq!(
60-
iter.map(|info| (info.value, info.index)).collect::<Vec<_>>(),
61-
[
62-
(-0.5, 5),
63-
(0.5, 9),
64-
(-0.5, 13),
65-
]
66-
);
67-
}
68-
6954
#[test]
7055
fn find_roots_in_real_sample() {
7156
let history = single_beat_excerpt_test_data();
7257
let iter = RootIterator::new(&history, None);
7358
#[rustfmt::skip]
7459
assert_eq!(
75-
iter.map(|info| (info.timestamp.as_secs_f32(), info.value)).collect::<Vec<_>>(),
60+
iter.map(|info| (info.total_index, info.value)).collect::<Vec<_>>(),
7661
// I checked in Audacity whether the values returned by the code
7762
// make sense. Then, they became the reference for the test.
7863
[
79-
(0.000181406, -0.002929777),
80-
(0.004444445, 0.005798517),
81-
(0.009818594, -0.004074221),
82-
(0.017823128, 0.0008850368),
83-
(0.025170067, -0.0017700735),
84-
(0.032743763, 0.004379406),
85-
(0.04111111, -0.0031891842),
86-
(0.04918367, 0.0013428144),
87-
(0.05732426, -0.0026245918)
64+
(8, -0.002929777),
65+
(196, 0.005798517),
66+
(433, -0.004074221),
67+
(786, 0.0008850368),
68+
(1110, -0.0017700735),
69+
(1444, 0.004379406),
70+
(1813, -0.0031891842),
71+
(2169, 0.0013428144),
72+
(2528, -0.0026245918)
8873
]
8974
);
9075
}

0 commit comments

Comments
 (0)