1- use rodio:: { OutputStream , Sink } ;
21use std:: sync:: atomic:: { AtomicBool , Ordering } ;
32
43// Global sound enabled state
@@ -9,11 +8,20 @@ static AUDIO_AVAILABLE: AtomicBool = AtomicBool::new(true);
98/// Check if audio is available and update the availability state
109/// This should be called at startup to detect if we're in an environment without audio (e.g., Docker)
1110pub fn check_audio_availability ( ) -> bool {
12- // Try to create an output stream
13- // Note: ALSA may print errors to stderr, but we handle the failure gracefully
14- let available = OutputStream :: try_default ( ) . is_ok ( ) ;
15- AUDIO_AVAILABLE . store ( available, Ordering :: Relaxed ) ;
16- available
11+ #[ cfg( feature = "sound" ) ]
12+ {
13+ use rodio:: OutputStream ;
14+ // Try to create an output stream
15+ // Note: ALSA may print errors to stderr, but we handle the failure gracefully
16+ let available = OutputStream :: try_default ( ) . is_ok ( ) ;
17+ AUDIO_AVAILABLE . store ( available, Ordering :: Relaxed ) ;
18+ available
19+ }
20+ #[ cfg( not( feature = "sound" ) ) ]
21+ {
22+ AUDIO_AVAILABLE . store ( false , Ordering :: Relaxed ) ;
23+ false
24+ }
1725}
1826
1927/// Set whether sounds are enabled
@@ -29,112 +37,124 @@ pub fn is_sound_enabled() -> bool {
2937/// Plays a move sound when a chess piece is moved.
3038/// This generates a pleasant, wood-like "click" sound using multiple harmonics.
3139pub fn play_move_sound ( ) {
32- if !is_sound_enabled ( ) {
33- return ;
34- }
35- // Spawn in a separate thread to avoid blocking the main game loop
36- std:: thread:: spawn ( || {
37- // Try to get an output stream, but don't fail if audio isn't available
38- let Ok ( ( _stream, stream_handle) ) = OutputStream :: try_default ( ) else {
39- return ;
40- } ;
41-
42- // Create a sink to play the sound
43- let Ok ( sink) = Sink :: try_new ( & stream_handle) else {
40+ #[ cfg( feature = "sound" ) ]
41+ {
42+ if !is_sound_enabled ( ) {
4443 return ;
45- } ;
46-
47- // Generate a pleasant wood-like click sound
48- // Using a lower fundamental frequency with harmonics for a richer sound
49- let sample_rate = 44100 ;
50- let duration = 0.08 ; // 80 milliseconds - slightly longer for better perception
51- let fundamental = 200.0 ; // Lower frequency for a more pleasant, less harsh sound
52-
53- let num_samples = ( sample_rate as f64 * duration) as usize ;
54- let mut samples = Vec :: with_capacity ( num_samples) ;
55-
56- for i in 0 ..num_samples {
57- let t = i as f64 / sample_rate as f64 ;
58-
59- // Create a more sophisticated envelope with exponential decay
60- // Quick attack, smooth decay - like a wood piece being placed
61- let envelope = if t < duration * 0.1 {
62- // Quick attack (10% of duration)
63- ( t / ( duration * 0.1 ) ) . powf ( 0.5 )
64- } else {
65- // Exponential decay
66- let decay_start = duration * 0.1 ;
67- let decay_time = t - decay_start;
68- let decay_duration = duration - decay_start;
69- ( -decay_time * 8.0 / decay_duration) . exp ( )
44+ }
45+ // Spawn in a separate thread to avoid blocking the main game loop
46+ std:: thread:: spawn ( || {
47+ use rodio:: { OutputStream , Sink } ;
48+ // Try to get an output stream, but don't fail if audio isn't available
49+ let Ok ( ( _stream, stream_handle) ) = OutputStream :: try_default ( ) else {
50+ return ;
7051 } ;
7152
72- // Generate a richer sound with harmonics
73- // Fundamental + 2nd harmonic (octave) + 3rd harmonic (fifth)
74- let fundamental_wave = ( t * fundamental * 2.0 * std:: f64:: consts:: PI ) . sin ( ) ;
75- let harmonic2 = ( t * fundamental * 2.0 * 2.0 * std:: f64:: consts:: PI ) . sin ( ) * 0.3 ;
76- let harmonic3 = ( t * fundamental * 2.0 * 3.0 * std:: f64:: consts:: PI ) . sin ( ) * 0.15 ;
77-
78- // Combine harmonics with envelope
79- let sample = ( fundamental_wave + harmonic2 + harmonic3) * envelope * 0.25 ;
80-
81- // Convert to i16 sample
82- samples. push ( ( sample * i16:: MAX as f64 ) . clamp ( i16:: MIN as f64 , i16:: MAX as f64 ) as i16 ) ;
83- }
53+ // Create a sink to play the sound
54+ let Ok ( sink) = Sink :: try_new ( & stream_handle) else {
55+ return ;
56+ } ;
8457
85- // Convert to a source that rodio can play
86- let source = rodio:: buffer:: SamplesBuffer :: new ( 1 , sample_rate, samples) ;
87- sink. append ( source) ;
88- sink. sleep_until_end ( ) ;
89- } ) ;
58+ // Generate a pleasant wood-like click sound
59+ // Using a lower fundamental frequency with harmonics for a richer sound
60+ let sample_rate = 44100 ;
61+ let duration = 0.08 ; // 80 milliseconds - slightly longer for better perception
62+ let fundamental = 200.0 ; // Lower frequency for a more pleasant, less harsh sound
63+
64+ let num_samples = ( sample_rate as f64 * duration) as usize ;
65+ let mut samples = Vec :: with_capacity ( num_samples) ;
66+
67+ for i in 0 ..num_samples {
68+ let t = i as f64 / sample_rate as f64 ;
69+
70+ // Create a more sophisticated envelope with exponential decay
71+ // Quick attack, smooth decay - like a wood piece being placed
72+ let envelope = if t < duration * 0.1 {
73+ // Quick attack (10% of duration)
74+ ( t / ( duration * 0.1 ) ) . powf ( 0.5 )
75+ } else {
76+ // Exponential decay
77+ let decay_start = duration * 0.1 ;
78+ let decay_time = t - decay_start;
79+ let decay_duration = duration - decay_start;
80+ ( -decay_time * 8.0 / decay_duration) . exp ( )
81+ } ;
82+
83+ // Generate a richer sound with harmonics
84+ // Fundamental + 2nd harmonic (octave) + 3rd harmonic (fifth)
85+ let fundamental_wave = ( t * fundamental * 2.0 * std:: f64:: consts:: PI ) . sin ( ) ;
86+ let harmonic2 = ( t * fundamental * 2.0 * 2.0 * std:: f64:: consts:: PI ) . sin ( ) * 0.3 ;
87+ let harmonic3 = ( t * fundamental * 2.0 * 3.0 * std:: f64:: consts:: PI ) . sin ( ) * 0.15 ;
88+
89+ // Combine harmonics with envelope
90+ let sample = ( fundamental_wave + harmonic2 + harmonic3) * envelope * 0.25 ;
91+
92+ // Convert to i16 sample
93+ samples. push (
94+ ( sample * i16:: MAX as f64 ) . clamp ( i16:: MIN as f64 , i16:: MAX as f64 ) as i16 ,
95+ ) ;
96+ }
97+
98+ // Convert to a source that rodio can play
99+ let source = rodio:: buffer:: SamplesBuffer :: new ( 1 , sample_rate, samples) ;
100+ sink. append ( source) ;
101+ sink. sleep_until_end ( ) ;
102+ } ) ;
103+ }
90104}
91105
92106/// Plays a light navigation sound when moving through menu items.
93107/// This generates a subtle, high-pitched "tick" sound for menu navigation.
94108pub fn play_menu_nav_sound ( ) {
95- if !is_sound_enabled ( ) {
96- return ;
97- }
98- // Spawn in a separate thread to avoid blocking the main game loop
99- std:: thread:: spawn ( || {
100- // Try to get an output stream, but don't fail if audio isn't available
101- let Ok ( ( _stream, stream_handle) ) = OutputStream :: try_default ( ) else {
109+ #[ cfg( feature = "sound" ) ]
110+ {
111+ if !is_sound_enabled ( ) {
102112 return ;
103- } ;
104-
105- // Create a sink to play the sound
106- let Ok ( sink) = Sink :: try_new ( & stream_handle) else {
107- return ;
108- } ;
109-
110- // Generate a light, high-pitched tick sound for menu navigation
111- let sample_rate = 44100 ;
112- let duration = 0.04 ;
113- let frequency = 600.0 ;
114-
115- let num_samples = ( sample_rate as f64 * duration) as usize ;
116- let mut samples = Vec :: with_capacity ( num_samples) ;
117-
118- for i in 0 ..num_samples {
119- let t = i as f64 / sample_rate as f64 ;
120-
121- let envelope = if t < duration * 0.2 {
122- ( t / ( duration * 0.2 ) ) . powf ( 0.3 )
123- } else {
124- let decay_start = duration * 0.2 ;
125- let decay_time = t - decay_start;
126- let decay_duration = duration - decay_start;
127- ( -decay_time * 12.0 / decay_duration) . exp ( )
113+ }
114+ // Spawn in a separate thread to avoid blocking the main game loop
115+ std:: thread:: spawn ( || {
116+ use rodio:: { OutputStream , Sink } ;
117+ // Try to get an output stream, but don't fail if audio isn't available
118+ let Ok ( ( _stream, stream_handle) ) = OutputStream :: try_default ( ) else {
119+ return ;
128120 } ;
129121
130- let sample = ( t * frequency * 2.0 * std :: f64 :: consts :: PI ) . sin ( ) * envelope * 0.3 ;
131-
132- samples . push ( ( sample * i16 :: MAX as f64 ) . clamp ( i16 :: MIN as f64 , i16 :: MAX as f64 ) as i16 ) ;
133- }
122+ // Create a sink to play the sound
123+ let Ok ( sink ) = Sink :: try_new ( & stream_handle ) else {
124+ return ;
125+ } ;
134126
135- // Convert to a source that rodio can play
136- let source = rodio:: buffer:: SamplesBuffer :: new ( 1 , sample_rate, samples) ;
137- sink. append ( source) ;
138- sink. sleep_until_end ( ) ;
139- } ) ;
127+ // Generate a light, high-pitched tick sound for menu navigation
128+ let sample_rate = 44100 ;
129+ let duration = 0.04 ;
130+ let frequency = 600.0 ;
131+
132+ let num_samples = ( sample_rate as f64 * duration) as usize ;
133+ let mut samples = Vec :: with_capacity ( num_samples) ;
134+
135+ for i in 0 ..num_samples {
136+ let t = i as f64 / sample_rate as f64 ;
137+
138+ let envelope = if t < duration * 0.2 {
139+ ( t / ( duration * 0.2 ) ) . powf ( 0.3 )
140+ } else {
141+ let decay_start = duration * 0.2 ;
142+ let decay_time = t - decay_start;
143+ let decay_duration = duration - decay_start;
144+ ( -decay_time * 12.0 / decay_duration) . exp ( )
145+ } ;
146+
147+ let sample = ( t * frequency * 2.0 * std:: f64:: consts:: PI ) . sin ( ) * envelope * 0.3 ;
148+
149+ samples. push (
150+ ( sample * i16:: MAX as f64 ) . clamp ( i16:: MIN as f64 , i16:: MAX as f64 ) as i16 ,
151+ ) ;
152+ }
153+
154+ // Convert to a source that rodio can play
155+ let source = rodio:: buffer:: SamplesBuffer :: new ( 1 , sample_rate, samples) ;
156+ sink. append ( source) ;
157+ sink. sleep_until_end ( ) ;
158+ } ) ;
159+ }
140160}
0 commit comments