1use std::sync::mpsc::Receiver;
8
9use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
10use cpal::{SampleFormat, Stream, StreamConfig};
11use glam::Vec3;
12
13use crate::audio::{AudioEvent, MusicVibe};
14use crate::audio::math_source::{MathAudioSource, Waveform as MsWaveform};
15use crate::audio::mixer::{spatial_weight, stereo_pan};
16use crate::audio::synth::{oscillator, Adsr, Waveform as SynthWaveform};
17
18fn ms_to_synth_waveform(w: MsWaveform) -> SynthWaveform {
19 match w {
20 MsWaveform::Sine => SynthWaveform::Sine,
21 MsWaveform::Triangle => SynthWaveform::Triangle,
22 MsWaveform::Square => SynthWaveform::Square,
23 MsWaveform::Sawtooth => SynthWaveform::Sawtooth,
24 MsWaveform::ReverseSaw => SynthWaveform::ReverseSaw,
25 MsWaveform::Pulse(d) => SynthWaveform::Pulse(d),
26 MsWaveform::Noise => SynthWaveform::Noise,
27 }
28}
29
30struct ActiveSource {
32 src: MathAudioSource,
33 phase: f32, age: f32, note_off: Option<f32>,
36 adsr: Adsr,
37}
38
39#[allow(dead_code)]
41struct AudioState {
42 sources: Vec<ActiveSource>,
43 rx: Receiver<AudioEvent>,
44 master_volume: f32,
45 music_volume: f32,
46 music_vibe: MusicVibe,
47 sample_rate: f32,
48 channels: usize,
49 listener: Vec3,
51 time: f32,
53}
54
55impl AudioState {
56 fn process_events(&mut self) {
57 while let Ok(event) = self.rx.try_recv() {
58 match event {
59 AudioEvent::SpawnSource { source, position } => {
60 let mut src = source;
61 src.position = position;
62 self.sources.push(ActiveSource {
63 src,
64 phase: 0.0,
65 age: 0.0,
66 note_off: None,
67 adsr: Adsr {
68 attack: 0.02,
69 decay: 0.1,
70 sustain: 0.8,
71 release: 0.3,
72 },
73 });
74 }
75 AudioEvent::StopTag(tag) => {
76 let now = self.time;
77 for s in &mut self.sources {
78 if s.src.tag.as_deref() == Some(&tag) {
79 s.note_off = Some(now);
80 }
81 }
82 }
83 AudioEvent::SetMasterVolume(v) => {
84 self.master_volume = v.clamp(0.0, 1.0);
85 }
86 AudioEvent::SetMusicVolume(v) => {
87 self.music_volume = v.clamp(0.0, 1.0);
88 }
89 AudioEvent::PlaySfx { name: _, position, volume } => {
90 use crate::math::MathFunction;
92 self.sources.push(ActiveSource {
93 src: MathAudioSource {
94 function: MathFunction::Sine { amplitude: 1.0, frequency: 1.0, phase: 0.0 },
95 frequency_range: (440.0, 880.0),
96 amplitude: volume,
97 waveform: MsWaveform::Sine,
98 filter: None,
99 position,
100 tag: Some("sfx".to_string()),
101 lifetime: 0.15,
102 ..Default::default()
103 },
104 phase: 0.0,
105 age: 0.0,
106 note_off: None,
107 adsr: Adsr { attack: 0.01, decay: 0.05, sustain: 0.0, release: 0.05 },
108 });
109 }
110 AudioEvent::SetMusicVibe(vibe) => {
111 self.music_vibe = vibe;
112 }
113 }
114 }
115 }
116
117 fn next_sample(&mut self) -> (f32, f32) {
119 let dt = 1.0 / self.sample_rate;
120 self.time += dt;
121
122 let mut left = 0.0f32;
123 let mut right = 0.0f32;
124 let mut to_remove = Vec::new();
125
126 for (i, active) in self.sources.iter_mut().enumerate() {
127 if active.src.lifetime >= 0.0 && active.age >= active.src.lifetime {
129 to_remove.push(i);
130 continue;
131 }
132 if let Some(off) = active.note_off {
134 if active.age - off > active.adsr.release + 0.05 {
135 to_remove.push(i);
136 continue;
137 }
138 }
139
140 let fn_out = active.src.function.evaluate(active.age, 0.0);
142
143 let (f_min, f_max) = active.src.frequency_range;
145 let t_freq = (fn_out * 0.5 + 0.5).clamp(0.0, 1.0); let freq = f_min + t_freq * (f_max - f_min);
147
148 active.phase = (active.phase + freq * dt).fract();
150
151 let raw = oscillator(ms_to_synth_waveform(active.src.waveform), active.phase);
153 let env = active.adsr.level(active.age, active.note_off);
154 let vol = active.src.amplitude * env;
155
156 let weight = spatial_weight(self.listener, active.src.position, 30.0);
158 let (pan_l, pan_r) = stereo_pan(self.listener, active.src.position);
159 let sample = raw * vol * weight;
160
161 left += sample * pan_l;
162 right += sample * pan_r;
163
164 active.age += dt;
165 }
166
167 for &i in to_remove.iter().rev() {
169 self.sources.swap_remove(i);
170 }
171
172 let mv = self.master_volume;
173 (left * mv, right * mv)
174 }
175}
176
177pub struct AudioOutput {
181 pub sample_rate: u32,
182 pub channels: u16,
183 _stream: Stream,
184}
185
186impl AudioOutput {
187 pub fn try_new(rx: Receiver<AudioEvent>) -> Option<Self> {
190 let host = cpal::default_host();
191 let device = host.default_output_device()?;
192
193 let supported = device.default_output_config().ok()?;
194 let channels = supported.channels();
195 let rate = supported.sample_rate().0;
196
197 let config = StreamConfig {
198 channels,
199 sample_rate: supported.sample_rate(),
200 buffer_size: cpal::BufferSize::Default,
201 };
202
203 let state = AudioState {
204 sources: Vec::new(),
205 rx,
206 master_volume: 1.0,
207 music_volume: 0.6,
208 music_vibe: MusicVibe::Silence,
209 sample_rate: rate as f32,
210 channels: channels as usize,
211 listener: Vec3::ZERO,
212 time: 0.0,
213 };
214
215 let stream = match supported.sample_format() {
216 SampleFormat::F32 => build_stream_f32(&device, &config, state),
217 fmt => {
218 log::warn!("AudioOutput: unsupported sample format {:?}, defaulting to f32", fmt);
219 build_stream_f32(&device, &config, state)
221 }
222 }?;
223
224 stream.play().ok()?;
225
226 log::info!("AudioOutput: {} Hz, {} ch", rate, channels);
227 Some(Self { sample_rate: rate, channels, _stream: stream })
228 }
229}
230
231fn build_stream_f32(
232 device: &cpal::Device,
233 config: &StreamConfig,
234 mut state: AudioState,
235) -> Option<Stream> {
236 let ch = config.channels as usize;
237 let stream = device
238 .build_output_stream(
239 config,
240 move |data: &mut [f32], _info: &cpal::OutputCallbackInfo| {
241 state.process_events();
242 for frame in data.chunks_mut(ch) {
243 let (l, r) = state.next_sample();
244 frame[0] = l.clamp(-1.0, 1.0);
245 if ch > 1 {
246 frame[1] = r.clamp(-1.0, 1.0);
247 }
248 }
249 },
250 |err| log::error!("AudioOutput stream error: {err}"),
251 None,
252 )
253 .ok()?;
254 Some(stream)
255}