Skip to content

Commit 8204220

Browse files
committed
(re)add tests for skip_duration, fixes skip duration
also adds a new mod test support with a special test source that can be used to test span boundary/parameters_changed handling. adds two extra tests for `skip_duration` testing - parameters_changing during skip - ending on frame boundary
1 parent 637642b commit 8204220

File tree

7 files changed

+324
-41
lines changed

7 files changed

+324
-41
lines changed

src/math.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,30 @@ pub fn lerp(first: &f32, second: &f32, numerator: u32, denominator: u32) -> f32
1010
first + (second - first) * numerator as f32 / denominator as f32
1111
}
1212

13+
/// will hopefully get stabilized, this is slightly different to the future
14+
/// std's version since it does some casting already. When the std's version gets
15+
/// stable remove this trait.
16+
pub(crate) trait PrevMultipleOf {
17+
fn prev_multiple_of(self, n: u16) -> Self;
18+
}
19+
20+
macro_rules! impl_prev_multiple_of {
21+
($type:ty) => {
22+
impl PrevMultipleOf for $type {
23+
fn prev_multiple_of(self, n: u16) -> $type {
24+
if self.next_multiple_of(n as $type) > self {
25+
self.next_multiple_of(n as $type) - n as $type
26+
} else {
27+
self.next_multiple_of(n as $type)
28+
}
29+
}
30+
}
31+
};
32+
}
33+
34+
impl_prev_multiple_of! {usize}
35+
impl_prev_multiple_of! {u64}
36+
1337
#[cfg(test)]
1438
mod test {
1539
use super::*;

src/source/from_iter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ where
8787
// If the `size_hint` is `None` as well, we are in the worst case scenario. To handle this
8888
// situation we force a span to have a maximum number of samples indicate by this
8989
// constant.
90-
90+
9191
todo!()
9292
// const THRESHOLD: usize = 10240;
9393
//

src/source/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub use self::repeat::Repeat;
3333
pub use self::sawtooth::SawtoothWave;
3434
pub use self::signal_generator::{Function, SignalGenerator};
3535
pub use self::sine::SineWave;
36-
pub use self::skip::SkipDuration;
36+
pub use self::skip_duration::SkipDuration;
3737
pub use self::skippable::Skippable;
3838
pub use self::spatial::Spatial;
3939
pub use self::speed::Speed;
@@ -69,7 +69,7 @@ mod repeat;
6969
mod sawtooth;
7070
mod signal_generator;
7171
mod sine;
72-
mod skip;
72+
mod skip_duration;
7373
mod skippable;
7474
mod spatial;
7575
mod speed;
@@ -252,7 +252,7 @@ pub trait Source: Iterator<Item = Sample> {
252252
where
253253
Self: Sized,
254254
{
255-
skip::skip_duration(self, duration)
255+
SkipDuration::new(self, duration)
256256
}
257257

258258
/// Amplifies the sound by the given value.

src/source/skip.rs renamed to src/source/skip_duration.rs

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,29 @@ use std::time::Duration;
22

33
use super::SeekError;
44
use crate::common::{ChannelCount, SampleRate};
5+
use crate::math::PrevMultipleOf;
56
use crate::Source;
67

7-
const US_PER_SECOND: u64 = 1_000_000;
8-
9-
/// Internal function that builds a `SkipDuration` object.
10-
pub fn skip_duration<I>(mut input: I, duration: Duration) -> SkipDuration<I>
11-
where
12-
I: Source,
13-
{
14-
do_skip_duration(&mut input, duration);
15-
SkipDuration {
16-
input,
17-
skipped_duration: duration,
18-
}
19-
}
20-
218
/// A source that skips specified duration of the given source from it's current position.
229
#[derive(Clone, Debug)]
2310
pub struct SkipDuration<I> {
2411
input: I,
2512
skipped_duration: Duration,
2613
}
2714

15+
impl<I> SkipDuration<I> {
16+
pub(crate) fn new(mut input: I, duration: Duration) -> SkipDuration<I>
17+
where
18+
I: Source,
19+
{
20+
do_skip_duration(&mut input, duration);
21+
Self {
22+
input,
23+
skipped_duration: duration,
24+
}
25+
}
26+
}
27+
2828
impl<I> SkipDuration<I>
2929
where
3030
I: Source,
@@ -99,26 +99,46 @@ where
9999
}
100100

101101
/// Skips specified `duration` of the given `input` source from it's current position.
102-
fn do_skip_duration<I>(input: &mut I, mut duration: Duration)
102+
///
103+
/// # Panics
104+
/// If trying to skip more than 584 days ahead. If you need that functionality
105+
/// please open an issue I would love to know why (and help out).
106+
fn do_skip_duration<I>(input: &mut I, duration: Duration)
103107
where
104108
I: Source,
105109
{
106-
while !duration.is_zero() {
107-
let us_per_sample: u64 =
108-
US_PER_SECOND / input.sample_rate() as u64 / input.channels() as u64;
109-
let mut samples_to_skip = duration.as_micros() as u64 / us_per_sample;
110-
111-
while samples_to_skip > 0 && !input.parameters_changed() {
112-
samples_to_skip -= 1;
110+
const NS_PER_SECOND: u64 = 1_000_000_000;
111+
112+
// `u64::MAX` can store 584 days of nanosecond precision time. To not be off by
113+
// a single sample (that would be regression) we first multiply the time by
114+
// `samples_per second`. Which for a 96kHz 10 channel audio stream is
115+
// 960_000 samples. That would only leave 0.87 hour of potential skip time. Hence
116+
// we use an `u128` to calculate samples to skip.
117+
let mut duration: u64 = duration
118+
.as_nanos()
119+
.try_into()
120+
.expect("can not skip more then 584 days of audio");
121+
let mut ns_per_frame: u64 = 0;
122+
123+
while duration > ns_per_frame {
124+
ns_per_frame = NS_PER_SECOND / input.sample_rate() as u64;
125+
126+
let samples_per_second = input.sample_rate() as u64 * input.channels() as u64;
127+
let samples_to_skip =
128+
(duration as u128 * samples_per_second as u128 / NS_PER_SECOND as u128) as u64;
129+
let samples_to_skip = samples_to_skip.prev_multiple_of(input.channels());
130+
131+
let mut skipped = 0;
132+
while skipped < samples_to_skip {
113133
if input.next().is_none() {
114134
return;
115135
}
136+
skipped += 1;
137+
if input.parameters_changed() {
138+
break;
139+
}
116140
}
117141

118-
if samples_to_skip == 0 {
119-
return;
120-
} else {
121-
duration -= Duration::from_micros(samples_to_skip * us_per_sample);
122-
}
142+
duration -= skipped * NS_PER_SECOND / samples_per_second;
123143
}
124144
}

src/source/take_samples.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::time::Duration;
22

33
use super::SeekError;
44
use crate::common::{ChannelCount, SampleRate};
5+
use crate::math::PrevMultipleOf;
56
use crate::Source;
67

78
/// A source that truncates the given source to a certain duration.
@@ -84,13 +85,3 @@ where
8485
self.input.try_seek(pos)
8586
}
8687
}
87-
88-
trait PrevMultipleOf {
89-
fn prev_multiple_of(self, n: u16) -> usize;
90-
}
91-
92-
impl PrevMultipleOf for usize {
93-
fn prev_multiple_of(self, n: u16) -> usize {
94-
self.next_multiple_of(n as usize).saturating_sub(n as usize)
95-
}
96-
}

tests/skip_duration.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use std::time::Duration;
2+
3+
use rodio::buffer::SamplesBuffer;
4+
use rodio::source::Source;
5+
use rodio::{ChannelCount, SampleRate};
6+
use rstest::rstest;
7+
8+
mod test_support;
9+
use test_support::{TestSource, TestSpan};
10+
11+
#[rstest]
12+
fn ends_on_frame_boundary(#[values(1.483, 2.999)] seconds_to_skip: f32) {
13+
let source = TestSource::new().with_span(
14+
TestSpan::silence(30)
15+
.with_channel_count(10)
16+
.with_sample_rate(1),
17+
);
18+
let leftover = source
19+
.clone()
20+
.skip_duration(Duration::from_secs_f32(seconds_to_skip))
21+
.count();
22+
assert!(leftover % source.channels() as usize == 0)
23+
}
24+
25+
#[rstest]
26+
fn param_changes_during_skip(#[values(6, 11)] seconds_to_skip: u64) {
27+
let source = TestSource::new() // each span is 5 seconds
28+
.with_span(
29+
TestSpan::silence(5 * 10 * 1)
30+
.with_sample_rate(10)
31+
.with_channel_count(1),
32+
)
33+
.with_span(
34+
TestSpan::silence(5 * 20 * 2)
35+
.with_sample_rate(20)
36+
.with_channel_count(2),
37+
)
38+
.with_span(
39+
TestSpan::silence(5 * 5 * 3)
40+
.with_sample_rate(5)
41+
.with_channel_count(3),
42+
);
43+
44+
let leftover = source
45+
.clone()
46+
.skip_duration(Duration::from_secs(seconds_to_skip))
47+
.count();
48+
49+
let spans = source.spans;
50+
let expected_leftover = match seconds_to_skip {
51+
6 => 4 * spans[1].sample_rate as usize * spans[1].channels as usize + spans[2].len(),
52+
11 => 4 * spans[2].sample_rate as usize * spans[2].channels as usize,
53+
_ => unreachable!(),
54+
};
55+
56+
assert_eq!(leftover, expected_leftover);
57+
}
58+
59+
#[rstest]
60+
fn samples_left(
61+
#[values(1, 2, 4)] channels: ChannelCount,
62+
#[values(100_000)] sample_rate: SampleRate,
63+
#[values(0, 5)] seconds: u32,
64+
#[values(0, 3, 5)] seconds_to_skip: u32,
65+
) {
66+
println!(
67+
"channels: {channels}, sample_rate: {sample_rate}, \
68+
seconds: {seconds}, seconds_to_skip: {seconds_to_skip}"
69+
);
70+
let buf_len = (sample_rate * channels as u32 * seconds) as usize;
71+
assert!(buf_len < 10 * 1024 * 1024);
72+
let data: Vec<f32> = vec![0f32; buf_len];
73+
let test_buffer = SamplesBuffer::new(channels, sample_rate, data);
74+
let seconds_left = seconds.saturating_sub(seconds_to_skip);
75+
76+
let samples_left_expected = (sample_rate * channels as u32 * seconds_left) as usize;
77+
let samples_left = test_buffer
78+
.skip_duration(Duration::from_secs(seconds_to_skip as u64))
79+
.count();
80+
81+
assert!(
82+
samples_left == samples_left_expected,
83+
"expected {samples_left_expected} samples left, counted: {samples_left}"
84+
);
85+
}

0 commit comments

Comments
 (0)