game_toolkit_audio/
handle.rs1use std::sync::{Arc, Mutex, MutexGuard};
4use std::time::Duration;
5
6use kira::sound::PlaybackState;
7use kira::sound::static_sound::StaticSoundHandle;
8use kira::{Decibels, Tween};
9
10use crate::amplitude_to_db;
11
12#[derive(Clone, Debug)]
22pub struct SoundHandle {
23 slot: Arc<Mutex<Slot>>,
24}
25
26#[derive(Debug)]
27pub(crate) enum Slot {
28 Pending {
31 volume: Option<(Decibels, Duration)>,
32 paused: bool,
33 },
34 Playing(StaticSoundHandle),
35 Cancelled,
37}
38
39impl SoundHandle {
40 pub(crate) fn playing(handle: StaticSoundHandle) -> Self {
41 Self::from_slot(Slot::Playing(handle))
42 }
43
44 pub(crate) fn pending() -> Self {
45 Self::from_slot(Slot::Pending {
46 volume: None,
47 paused: false,
48 })
49 }
50
51 fn from_slot(slot: Slot) -> Self {
52 Self {
53 slot: Arc::new(Mutex::new(slot)),
54 }
55 }
56
57 pub(crate) fn lock(&self) -> MutexGuard<'_, Slot> {
58 self.slot
60 .lock()
61 .unwrap_or_else(std::sync::PoisonError::into_inner)
62 }
63
64 pub fn set_volume(&self, volume: f32) {
66 self.set_volume_db(amplitude_to_db(volume).0);
67 }
68
69 pub fn set_volume_db(&self, db: f32) {
71 self.fade_volume_db(db, Duration::ZERO);
72 }
73
74 pub fn fade_volume(&self, volume: f32, duration: Duration) {
76 self.fade_volume_db(amplitude_to_db(volume).0, duration);
77 }
78
79 pub fn fade_volume_db(&self, db: f32, duration: Duration) {
81 match &mut *self.lock() {
82 Slot::Pending { volume, .. } => *volume = Some((Decibels(db), duration)),
84 Slot::Playing(h) => h.set_volume(Decibels(db), tween(duration)),
85 Slot::Cancelled => {}
86 }
87 }
88
89 pub fn pause(&self) {
91 match &mut *self.lock() {
92 Slot::Pending { paused, .. } => *paused = true,
93 Slot::Playing(h) => h.pause(Tween::default()),
94 Slot::Cancelled => {}
95 }
96 }
97
98 pub fn resume(&self) {
100 match &mut *self.lock() {
101 Slot::Pending { paused, .. } => *paused = false,
102 Slot::Playing(h) => h.resume(Tween::default()),
103 Slot::Cancelled => {}
104 }
105 }
106
107 pub fn stop(&self) {
109 let mut slot = self.lock();
110 match &mut *slot {
111 Slot::Pending { .. } => *slot = Slot::Cancelled,
112 Slot::Playing(h) => h.stop(Tween::default()),
113 Slot::Cancelled => {}
114 }
115 }
116
117 pub fn is_playing(&self) -> bool {
120 match &*self.lock() {
121 Slot::Playing(h) => h.state().is_advancing(),
122 Slot::Pending { .. } | Slot::Cancelled => false,
123 }
124 }
125
126 pub fn is_paused(&self) -> bool {
128 match &*self.lock() {
129 Slot::Playing(h) => h.state() == PlaybackState::Paused,
130 Slot::Pending { paused, .. } => *paused,
131 Slot::Cancelled => false,
132 }
133 }
134
135 pub fn is_pending(&self) -> bool {
137 matches!(&*self.lock(), Slot::Pending { .. })
138 }
139
140 pub fn is_finished(&self) -> bool {
142 match &*self.lock() {
143 Slot::Playing(h) => h.state() == PlaybackState::Stopped,
144 Slot::Pending { .. } => false,
145 Slot::Cancelled => true,
146 }
147 }
148
149 pub fn with_kira<R>(&self, f: impl FnOnce(&mut StaticSoundHandle) -> R) -> Option<R> {
152 match &mut *self.lock() {
153 Slot::Playing(h) => Some(f(h)),
154 Slot::Pending { .. } | Slot::Cancelled => None,
155 }
156 }
157}
158
159pub(crate) fn tween(duration: Duration) -> Tween {
160 if duration.is_zero() {
161 Tween::default()
162 } else {
163 Tween {
164 duration,
165 ..Tween::default()
166 }
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn pending_handle_remembers_state() {
176 let h = SoundHandle::pending();
177 assert!(h.is_pending() && !h.is_playing() && !h.is_finished());
178 h.set_volume_db(-12.0);
179 h.pause();
180 assert!(h.is_paused());
181 match &*h.lock() {
182 Slot::Pending { volume, paused } => {
183 assert_eq!(*volume, Some((Decibels(-12.0), Duration::ZERO)));
184 assert!(*paused);
185 }
186 other => panic!("expected pending, got {other:?}"),
187 }
188 h.resume();
189 assert!(!h.is_paused());
190 h.fade_volume_db(-3.0, Duration::from_secs(2));
191 assert!(matches!(
192 &*h.lock(),
193 Slot::Pending { volume: Some((Decibels(db), d)), .. }
194 if *db == -3.0 && *d == Duration::from_secs(2)
195 ));
196 }
197
198 #[test]
199 fn stopping_a_pending_handle_cancels_it() {
200 let h = SoundHandle::pending();
201 let clone = h.clone();
202 h.stop();
203 assert!(clone.is_finished() && !clone.is_pending());
204 assert!(clone.with_kira(|_| ()).is_none());
205 }
206}