1use parking_lot::Mutex;
2use rodio::{OutputStream, OutputStreamBuilder, Sink, Source};
3use std::sync::Arc;
4use std::time::Duration;
5
6pub struct AudioBell {
8 stream: Option<OutputStream>,
10 sink: Option<Arc<Mutex<Sink>>>,
12}
13
14impl Drop for AudioBell {
15 fn drop(&mut self) {
16 if let Some(stream) = self.stream.take() {
18 std::mem::forget(stream);
19 }
20 }
21}
22
23impl AudioBell {
24 pub fn new() -> Result<Self, String> {
26 let stream = OutputStreamBuilder::open_default_stream()
27 .map_err(|e| format!("Failed to open audio stream: {}", e))?;
28
29 let sink = Sink::connect_new(stream.mixer());
30
31 Ok(Self {
32 stream: Some(stream),
33 sink: Some(Arc::new(Mutex::new(sink))),
34 })
35 }
36
37 pub fn disabled() -> Self {
39 Self {
40 stream: None,
41 sink: None,
42 }
43 }
44
45 pub fn play(&self, volume: u8) {
50 if volume == 0 {
51 return;
52 }
53
54 let sink_arc = match &self.sink {
55 Some(s) => s,
56 None => return, };
58
59 let volume_f32 = (volume.min(100) as f32) / 100.0;
61
62 let source = rodio::source::SineWave::new(800.0)
64 .take_duration(Duration::from_millis(100))
65 .amplify(volume_f32 * 0.3); let sink = sink_arc.lock();
68 sink.append(source);
69 }
70}
71
72impl Default for AudioBell {
73 fn default() -> Self {
74 Self::new().unwrap_or_else(|e| {
75 log::warn!("Failed to initialize audio bell: {}", e);
76 Self::disabled()
77 })
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn test_audio_bell_creation() {
87 let bell = AudioBell::new();
89 assert!(bell.is_ok() || bell.is_err());
90 }
91
92 #[test]
93 fn test_audio_bell_default() {
94 let _bell = AudioBell::default();
96 }
97
98 #[test]
99 fn test_audio_bell_play_zero_volume() {
100 if let Ok(bell) = AudioBell::new() {
101 bell.play(0);
103 }
104 }
105
106 #[test]
107 fn test_audio_bell_play_max_volume() {
108 if let Ok(bell) = AudioBell::new() {
109 bell.play(100);
111 }
112 }
113
114 #[test]
115 fn test_audio_bell_play_over_max_volume() {
116 if let Ok(bell) = AudioBell::new() {
117 bell.play(150);
119 }
120 }
121
122 #[test]
123 fn test_disabled_bell() {
124 let bell = AudioBell::disabled();
125 bell.play(50);
127 }
128}