use std::{sync::Arc, time::Duration};
use crate::{
clock::{
clock_info::{ClockInfo, MockClockInfoProviderBuilder},
ClockTime,
},
dsp::Frame,
sound::{
static_sound::{PlaybackState, StaticSoundData, StaticSoundSettings},
Sound,
},
tween::Tween,
LoopBehavior, StartTime, Volume,
};
use super::StaticSound;
#[test]
fn plays_all_samples() {
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new(vec![
Frame::from_mono(1.0),
Frame::from_mono(2.0),
Frame::from_mono(3.0),
]),
settings: StaticSoundSettings::new(),
};
let (mut sound, _) = data.split();
assert!(!sound.finished());
for i in 1..=3 {
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(i as f32).panned(0.5)
);
assert!(!sound.finished());
}
for _ in 0..10 {
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(0.0).panned(0.5)
);
}
assert!(sound.finished());
assert_eq!(sound.state, PlaybackState::Stopped);
}
#[test]
fn reports_playback_state() {
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new(vec![Frame::from_mono(0.0); 10]),
settings: StaticSoundSettings::new(),
};
let (mut sound, handle) = data.split();
for _ in 0..20 {
assert_eq!(handle.state(), sound.state);
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build());
}
}
#[test]
#[allow(clippy::float_cmp)]
fn reports_playback_position() {
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new(vec![Frame::from_mono(0.0); 10]),
settings: StaticSoundSettings::new(),
};
let (mut sound, handle) = data.split();
for i in 0..20 {
assert_eq!(
handle.position(),
i.clamp(0, 9) as f64 / sound.data.sample_rate as f64
);
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build());
sound.on_start_processing();
}
}
#[test]
#[allow(clippy::float_cmp)]
fn pauses_and_resumes_with_fades() {
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new(vec![Frame::from_mono(1.0); 100]),
settings: StaticSoundSettings::new(),
};
let (mut sound, mut handle) = data.split();
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build());
assert_eq!(sound.state, PlaybackState::Playing);
handle
.pause(Tween {
duration: Duration::from_secs(4),
..Default::default()
})
.unwrap();
sound.on_start_processing();
assert_eq!(sound.state, PlaybackState::Pausing);
expect_frame_soon(
Frame::from_mono(Volume::Decibels(-15.0).as_amplitude() as f32).panned(0.5),
&mut sound,
);
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(Volume::Decibels(-30.0).as_amplitude() as f32).panned(0.5)
);
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(Volume::Decibels(-45.0).as_amplitude() as f32).panned(0.5)
);
sound.on_start_processing();
let position = handle.position();
for _ in 0..10 {
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(0.0).panned(0.5)
);
sound.on_start_processing();
assert_eq!(handle.position(), position);
assert_eq!(sound.state, PlaybackState::Paused);
}
handle
.resume(Tween {
duration: Duration::from_secs(4),
..Default::default()
})
.unwrap();
sound.on_start_processing();
expect_frame_soon(
Frame::from_mono(Volume::Decibels(-45.0).as_amplitude() as f32).panned(0.5),
&mut sound,
);
assert_eq!(sound.state, PlaybackState::Playing);
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(Volume::Decibels(-30.0).as_amplitude() as f32).panned(0.5)
);
assert_eq!(sound.state, PlaybackState::Playing);
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(Volume::Decibels(-15.0).as_amplitude() as f32).panned(0.5)
);
assert_eq!(sound.state, PlaybackState::Playing);
for _ in 0..3 {
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(1.0).panned(0.5)
);
assert_eq!(sound.state, PlaybackState::Playing);
}
}
#[test]
#[allow(clippy::float_cmp)]
fn stops_with_fade_out() {
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new(vec![Frame::from_mono(1.0); 100]),
settings: StaticSoundSettings::new(),
};
let (mut sound, mut handle) = data.split();
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build());
assert_eq!(sound.state, PlaybackState::Playing);
handle
.stop(Tween {
duration: Duration::from_secs(4),
..Default::default()
})
.unwrap();
sound.on_start_processing();
assert_eq!(sound.state, PlaybackState::Stopping);
expect_frame_soon(
Frame::from_mono(Volume::Decibels(-15.0).as_amplitude() as f32).panned(0.5),
&mut sound,
);
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(Volume::Decibels(-30.0).as_amplitude() as f32).panned(0.5)
);
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(Volume::Decibels(-45.0).as_amplitude() as f32).panned(0.5)
);
sound.on_start_processing();
let position = handle.position();
for _ in 0..3 {
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(0.0).panned(0.5)
);
sound.on_start_processing();
assert_eq!(handle.position(), position);
assert_eq!(sound.state, PlaybackState::Stopped);
assert!(sound.finished());
}
}
#[test]
#[allow(clippy::float_cmp)]
fn waits_for_start_time() {
let (clock_info_provider, clock_id_1) = {
let mut builder = MockClockInfoProviderBuilder::new(2);
let clock_id_1 = builder
.add(ClockInfo {
ticking: true,
ticks: 0,
fractional_position: 0.0,
})
.unwrap();
builder
.add(ClockInfo {
ticking: true,
ticks: 0,
fractional_position: 0.0,
})
.unwrap();
(builder.build(), clock_id_1)
};
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new((1..100).map(|i| Frame::from_mono(i as f32)).collect()),
settings: StaticSoundSettings::new().start_time(ClockTime {
clock: clock_id_1,
ticks: 2,
}),
};
let (mut sound, _) = data.split();
for _ in 0..3 {
assert_eq!(
sound.process(1.0, &clock_info_provider),
Frame::from_mono(0.0)
);
}
let clock_info_provider = {
let mut builder = MockClockInfoProviderBuilder::new(2);
builder
.add(ClockInfo {
ticking: true,
ticks: 1,
fractional_position: 0.0,
})
.unwrap();
builder
.add(ClockInfo {
ticking: true,
ticks: 0,
fractional_position: 0.0,
})
.unwrap();
builder.build()
};
for _ in 0..3 {
assert_eq!(
sound.process(1.0, &clock_info_provider),
Frame::from_mono(0.0)
);
}
let clock_info_provider = {
let mut builder = MockClockInfoProviderBuilder::new(2);
builder
.add(ClockInfo {
ticking: true,
ticks: 1,
fractional_position: 0.0,
})
.unwrap();
builder
.add(ClockInfo {
ticking: true,
ticks: 2,
fractional_position: 0.0,
})
.unwrap();
builder.build()
};
for _ in 0..3 {
assert_eq!(
sound.process(1.0, &clock_info_provider),
Frame::from_mono(0.0)
);
}
let clock_info_provider = {
let mut builder = MockClockInfoProviderBuilder::new(2);
builder
.add(ClockInfo {
ticking: true,
ticks: 2,
fractional_position: 0.0,
})
.unwrap();
builder
.add(ClockInfo {
ticking: true,
ticks: 2,
fractional_position: 0.0,
})
.unwrap();
builder.build()
};
for i in 1..10 {
assert_eq!(
sound.process(1.0, &clock_info_provider),
Frame::from_mono(i as f32).panned(0.5)
);
}
}
#[test]
fn stops_if_depending_on_missing_clock() {
let (clock_info_provider, clock_id) = {
let mut builder = MockClockInfoProviderBuilder::new(1);
let clock_id = builder
.add(ClockInfo {
ticking: true,
ticks: 0,
fractional_position: 0.0,
})
.unwrap();
(builder.build(), clock_id)
};
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new((1..100).map(|i| Frame::from_mono(i as f32)).collect()),
settings: StaticSoundSettings::new().start_time(ClockTime {
clock: clock_id,
ticks: 2,
}),
};
let (mut sound, handle) = data.split();
sound.process(1.0, &clock_info_provider);
sound.on_start_processing();
assert_eq!(handle.state(), PlaybackState::Playing);
let clock_info_provider = MockClockInfoProviderBuilder::new(1).build();
sound.process(1.0, &clock_info_provider);
sound.process(1.0, &clock_info_provider);
sound.on_start_processing();
assert_eq!(handle.state(), PlaybackState::Stopped);
}
#[test]
fn immediate_pause_resume_and_stop_with_clock_start_time() {
let (clock_info_provider, clock_id) = {
let mut builder = MockClockInfoProviderBuilder::new(2);
let clock_id = builder
.add(ClockInfo {
ticking: true,
ticks: 0,
fractional_position: 0.0,
})
.unwrap();
(builder.build(), clock_id)
};
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new((1..100).map(|i| Frame::from_mono(i as f32)).collect()),
settings: StaticSoundSettings::new().start_time(StartTime::ClockTime(ClockTime {
clock: clock_id,
ticks: 2,
})),
};
let (mut sound, _) = data.split();
sound.pause(Tween {
duration: Duration::from_secs(0),
..Default::default()
});
sound.process(1.0, &clock_info_provider);
assert!(sound.state == PlaybackState::Paused);
sound.resume(Tween {
duration: Duration::from_secs(0),
..Default::default()
});
sound.process(1.0, &clock_info_provider);
assert!(sound.state == PlaybackState::Playing);
sound.stop(Tween {
duration: Duration::from_secs(0),
..Default::default()
});
sound.process(1.0, &clock_info_provider);
assert!(sound.state == PlaybackState::Stopped);
}
#[test]
#[allow(clippy::float_cmp)]
fn start_position() {
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new((0..10).map(|i| Frame::from_mono(i as f32)).collect()),
settings: StaticSoundSettings::new().start_position(3.0),
};
let (mut sound, handle) = data.split();
assert_eq!(handle.position(), 3.0);
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(3.0).panned(0.5)
);
}
#[test]
#[allow(clippy::float_cmp)]
fn negative_start_position() {
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new((0..10).map(|i| Frame::from_mono(i as f32)).collect()),
settings: StaticSoundSettings::new().start_position(-5.0),
};
let (mut sound, _) = data.split();
for _ in 0..5 {
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::ZERO
);
}
expect_frame_soon(Frame::from_mono(1.0), &mut sound);
}
#[test]
#[allow(clippy::float_cmp)]
fn out_of_bounds_start_position() {
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new((0..10).map(|i| Frame::from_mono(i as f32)).collect()),
settings: StaticSoundSettings::new().start_position(15.0),
};
let (mut sound, _) = data.split();
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build());
}
#[test]
#[allow(clippy::float_cmp)]
fn reverse() {
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new((0..10).map(|i| Frame::from_mono(i as f32)).collect()),
settings: StaticSoundSettings::new().reverse(true).start_position(2.0),
};
let (mut sound, _) = data.split();
for i in (0..=7).rev() {
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(i as f32).panned(0.5)
);
}
}
#[test]
#[allow(clippy::float_cmp)]
fn loops_forward() {
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new((0..10).map(|i| Frame::from_mono(i as f32)).collect()),
settings: StaticSoundSettings::new().loop_behavior(LoopBehavior {
start_position: 3.0,
}),
};
let (mut sound, _) = data.split();
for i in 0..10 {
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(i as f32).panned(0.5)
);
}
assert_eq!(
sound.process(3.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(3.0).panned(0.5)
);
assert_eq!(
sound.process(3.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(6.0).panned(0.5)
);
assert_eq!(
sound.process(3.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(9.0).panned(0.5)
);
assert_eq!(
sound.process(3.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(5.0).panned(0.5)
);
}
#[test]
#[allow(clippy::float_cmp)]
fn loops_backward() {
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new((0..10).map(|i| Frame::from_mono(i as f32)).collect()),
settings: StaticSoundSettings::new()
.loop_behavior(LoopBehavior {
start_position: 3.0,
})
.reverse(true),
};
let (mut sound, _) = data.split();
for i in (3..10).rev() {
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(i as f32).panned(0.5)
);
}
assert_eq!(
sound.process(4.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(9.0).panned(0.5)
);
assert_eq!(
sound.process(4.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(5.0).panned(0.5)
);
assert_eq!(
sound.process(4.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(8.0).panned(0.5)
);
}
#[test]
#[allow(clippy::float_cmp)]
fn volume() {
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new(vec![Frame::from_mono(1.0); 10]),
settings: StaticSoundSettings::new().volume(0.5),
};
let (mut sound, _) = data.split();
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(0.5).panned(0.5)
);
}
#[test]
fn set_volume() {
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new(vec![Frame::from_mono(1.0); 10]),
settings: StaticSoundSettings::new(),
};
let (mut sound, mut handle) = data.split();
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(1.0).panned(0.5)
);
handle
.set_volume(
0.5,
Tween {
duration: Duration::ZERO,
..Default::default()
},
)
.unwrap();
sound.on_start_processing();
expect_frame_soon(Frame::from_mono(0.5).panned(0.5), &mut sound);
}
#[test]
#[allow(clippy::float_cmp)]
fn panning() {
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new(vec![Frame::from_mono(1.0); 10]),
settings: StaticSoundSettings::new().panning(0.0),
};
let (mut sound, _) = data.split();
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(1.0).panned(0.0)
);
}
#[test]
fn set_panning() {
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new(vec![Frame::from_mono(1.0); 10]),
settings: StaticSoundSettings::new(),
};
let (mut sound, mut handle) = data.split();
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(1.0).panned(0.5)
);
handle
.set_panning(
0.0,
Tween {
duration: Duration::ZERO,
..Default::default()
},
)
.unwrap();
sound.on_start_processing();
expect_frame_soon(Frame::from_mono(1.0).panned(0.0), &mut sound);
}
#[test]
#[allow(clippy::float_cmp)]
fn playback_rate() {
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new((0..10).map(|i| Frame::from_mono(i as f32)).collect()),
settings: StaticSoundSettings::new().playback_rate(2.0),
};
let (mut sound, _) = data.split();
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(0.0).panned(0.5)
);
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(2.0).panned(0.5)
);
}
#[test]
#[allow(clippy::float_cmp)]
fn set_playback_rate() {
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new((0..100).map(|i| Frame::from_mono(i as f32)).collect()),
settings: StaticSoundSettings::new(),
};
let (mut sound, mut handle) = data.split();
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(0.0).panned(0.5)
);
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(1.0).panned(0.5)
);
handle
.set_playback_rate(
2.0,
Tween {
duration: Duration::ZERO,
..Default::default()
},
)
.unwrap();
sound.on_start_processing();
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(2.0).panned(0.5)
);
assert_eq!(
sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(4.0).panned(0.5)
);
}
#[test]
fn interpolates_samples() {
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new(vec![
Frame::from_mono(0.0),
Frame::from_mono(1.0),
Frame::from_mono(1.0),
Frame::from_mono(1.0),
Frame::from_mono(1.0),
Frame::from_mono(1.0),
Frame::from_mono(-10.0),
]),
settings: Default::default(),
};
let (mut sound, _) = data.split();
assert_eq!(
sound.process(0.5, &MockClockInfoProviderBuilder::new(0).build()),
Frame::from_mono(0.0).panned(0.5)
);
let frame = sound.process(5.0, &MockClockInfoProviderBuilder::new(0).build());
assert!(frame.left > 0.0 && frame.left < 1.0);
let frame = sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build());
assert!(frame.left < 0.0 && frame.left > -10.0);
}
#[test]
fn interpolates_samples_when_looping() {
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new(vec![Frame::from_mono(10.0), Frame::from_mono(9.0)]),
settings: StaticSoundSettings::new().loop_behavior(LoopBehavior {
start_position: 0.0,
}),
};
let (mut sound, _) = data.split();
sound.process(1.5, &MockClockInfoProviderBuilder::new(0).build());
let frame = sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build());
assert!(frame.left > 9.0 && frame.left < 10.0);
}
#[test]
fn seek_to() {
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new((0..100).map(|i| Frame::from_mono(i as f32)).collect()),
settings: StaticSoundSettings::new(),
};
let (mut sound, mut handle) = data.split();
handle.seek_to(15.0).unwrap();
sound.on_start_processing();
expect_frame_soon(Frame::from_mono(15.0).panned(0.5), &mut sound);
}
#[test]
fn seek_to_while_looping() {
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new((0..100).map(|i| Frame::from_mono(i as f32)).collect()),
settings: StaticSoundSettings::new().loop_behavior(LoopBehavior {
start_position: 5.0,
}),
};
let (mut sound, mut handle) = data.split();
handle.seek_to(120.0).unwrap();
sound.on_start_processing();
expect_frame_soon(Frame::from_mono(25.0).panned(0.5), &mut sound);
}
#[test]
fn seek_by() {
let data = StaticSoundData {
sample_rate: 1,
frames: Arc::new((0..100).map(|i| Frame::from_mono(i as f32)).collect()),
settings: StaticSoundSettings::new().start_position(10.0),
};
let (mut sound, mut handle) = data.split();
handle.seek_by(5.0).unwrap();
sound.on_start_processing();
expect_frame_soon(Frame::from_mono(20.0).panned(0.5), &mut sound);
}
fn expect_frame_soon(expected_frame: Frame, sound: &mut StaticSound) {
const NUM_SAMPLES_TO_WAIT: usize = 10;
for _ in 0..NUM_SAMPLES_TO_WAIT {
let frame = sound.process(1.0, &MockClockInfoProviderBuilder::new(0).build());
if frame == expected_frame {
return;
}
}
panic!(
"Sound did not output frame with value {:?} within {} samples",
expected_frame, NUM_SAMPLES_TO_WAIT
);
}