1use std::sync::{Arc, Mutex};
15use std::f32::consts::{TAU, FRAC_PI_2};
16use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
17
18#[derive(Clone, Debug)]
22pub struct ToneParams {
23 pub x: f32, pub y: f32, pub z: f32,
25 pub w: f32,
28 pub freq: f32,
30 pub amp: f32,
32 pub lfo_rate: f32,
34 pub lfo_depth: f32,
36}
37
38impl Default for ToneParams {
39 fn default() -> Self {
40 Self {
41 x: 0.0, y: 0.0, z: 0.0, w: 1.0,
42 freq: 220.0, amp: 0.15,
43 lfo_rate: 0.5, lfo_depth: 0.02,
44 }
45 }
46}
47
48struct Tone {
51 params: ToneParams,
52 phase: f32, lfo_phase: f32, w_phase: f32, cur_amp: f32, cur_freq: f32, }
58
59impl Tone {
60 fn new(params: ToneParams) -> Self {
61 let (a, f) = (params.amp, params.freq);
62 Self { params, phase: 0.0, lfo_phase: 0.0, w_phase: 0.0, cur_amp: a, cur_freq: f }
63 }
64}
65
66#[derive(Clone, Copy, Debug)]
68pub enum Wave { Sine, Square, Saw, Triangle, Noise }
69
70impl Wave {
71 pub fn from_name(s: &str) -> Wave {
72 match s.to_ascii_lowercase().as_str() {
73 "square" | "sq" => Wave::Square,
74 "saw" | "sawtooth" => Wave::Saw,
75 "tri" | "triangle" => Wave::Triangle,
76 "noise" | "wn" | "ns" => Wave::Noise,
77 _ => Wave::Sine,
78 }
79 }
80 #[inline]
81 fn sample(self, phase: f32) -> f32 {
82 match self {
83 Wave::Sine => (phase * TAU).sin(),
84 Wave::Square => if phase < 0.5 { 1.0 } else { -1.0 },
85 Wave::Saw => phase * 2.0 - 1.0,
86 Wave::Triangle => 1.0 - 4.0 * (phase - 0.5).abs(),
87 Wave::Noise => 0.0, }
89 }
90}
91
92struct Blip {
94 freq: f32,
95 amp: f32,
96 wave: Wave,
97 dur: f32, age: f32, phase: f32,
100 seed: u32, }
102
103impl Blip {
104 #[inline]
106 fn next(&mut self, dt: f32) -> f32 {
107 let atk = 0.005;
109 let env = if self.age < atk {
110 self.age / atk
111 } else {
112 (-(self.age - atk) / (self.dur * 0.4 + 1e-4)).exp()
113 };
114 let raw = if let Wave::Noise = self.wave {
115 self.seed = self.seed.wrapping_mul(1664525).wrapping_add(1013904223);
116 ((self.seed >> 8) as f32 / 8_388_608.0) - 1.0
117 } else { self.wave.sample(self.phase) };
118 let s = raw * self.amp * env;
119 self.phase = (self.phase + self.freq * dt).fract();
120 self.age += dt;
121 s
122 }
123 fn done(&self) -> bool { self.age >= self.dur }
124}
125
126struct BgmTrack {
127 samples: Vec<f32>,
129 src_rate: u32,
130 pos: f64,
132 volume: f32,
133}
134
135#[inline]
138fn spatial_gains(cry: f32, sry: f32, crx: f32, srx: f32, lx: f32, ly: f32, lz: f32, room_w: f32, x: f32, y: f32, z: f32) -> (f32, f32) {
139 let (x, y, z) = (x - lx, y - ly, z - lz); let rz1 = x * sry + z * cry;
141 let cam_x = x * cry - z * sry;
142 let cam_y = y * crx - rz1 * srx;
143 let cam_z = y * srx + rz1 * crx;
144 let dist = (cam_x * cam_x + cam_y * cam_y + cam_z * cam_z).sqrt().max(0.5);
145 let atten = (1.0 / (1.0 + dist * 0.18)).clamp(0.0, 1.0);
146 let pan = (cam_x / room_w.max(1.0)).clamp(-1.0, 1.0);
147 let angle = (pan + 1.0) * 0.5 * FRAC_PI_2;
148 (angle.cos() * atten, angle.sin() * atten)
149}
150
151struct SfxVoice {
154 x: f32, y: f32, z: f32, w: f32,
155 freq: f32, amp: f32, wave: Wave, dur: f32, age: f32, phase: f32, w_phase: f32, seed: u32,
156}
157impl SfxVoice {
158 #[inline]
159 fn next(&mut self, dt: f32) -> f32 {
160 let atk = 0.005;
161 let env = if self.age < atk { self.age / atk }
162 else { (-(self.age - atk) / (self.dur * 0.4 + 1e-4)).exp() };
163 let w_mod = (self.w_phase * TAU).sin() * 0.25;
164 self.w_phase = (self.w_phase + self.freq * self.w.abs() * 0.007 * dt).fract();
165 let f = self.freq * (1.0 + w_mod * 0.06);
166 let raw = if let Wave::Noise = self.wave {
167 self.seed = self.seed.wrapping_mul(1664525).wrapping_add(1013904223);
168 ((self.seed >> 8) as f32 / 8_388_608.0) - 1.0
169 } else { self.wave.sample(self.phase) };
170 let s = raw * self.amp * env;
171 self.phase = (self.phase + f * dt).fract();
172 self.age += dt;
173 s
174 }
175 fn done(&self) -> bool { self.age >= self.dur }
176}
177
178#[allow(dead_code)] struct SampleVoice {
181 id: u32,
182 sample: usize,
183 pos: f64,
184 x: f32, y: f32, z: f32, w: f32,
185 vol: f32, looping: bool, active: bool,
186}
187
188struct Delay {
190 bl: Vec<f32>, br: Vec<f32>, idx: usize, len: usize, fb: f32, mix: f32,
191}
192impl Delay {
193 fn new(rate: u32) -> Self {
194 let cap = (rate as usize * 2).max(1); Self { bl: vec![0.0; cap], br: vec![0.0; cap], idx: 0, len: 0, fb: 0.0, mix: 0.0 }
196 }
197 #[inline]
198 fn process(&mut self, l: f32, r: f32) -> (f32, f32) {
199 if self.mix <= 0.0 || self.len == 0 { return (l, r); }
200 let read = (self.idx + self.bl.len() - self.len) % self.bl.len();
201 let dl = self.bl[read]; let dr = self.br[read];
202 self.bl[self.idx] = l + dl * self.fb;
203 self.br[self.idx] = r + dr * self.fb;
204 self.idx = (self.idx + 1) % self.bl.len();
205 (l + dl * self.mix, r + dr * self.mix)
206 }
207}
208
209struct Comb { buf: Vec<f32>, idx: usize, fb: f32, store: f32, damp: f32 }
211impl Comb {
212 fn new(n: usize, fb: f32) -> Self { Self { buf: vec![0.0; n.max(1)], idx: 0, fb, store: 0.0, damp: 0.2 } }
213 #[inline]
214 fn process(&mut self, x: f32) -> f32 {
215 let y = self.buf[self.idx];
216 self.store = y * (1.0 - self.damp) + self.store * self.damp;
217 self.buf[self.idx] = x + self.store * self.fb;
218 self.idx = (self.idx + 1) % self.buf.len();
219 y
220 }
221}
222struct Allpass { buf: Vec<f32>, idx: usize }
224impl Allpass {
225 fn new(n: usize) -> Self { Self { buf: vec![0.0; n.max(1)], idx: 0 } }
226 #[inline]
227 fn process(&mut self, x: f32) -> f32 {
228 let buf = self.buf[self.idx];
229 let y = -x + buf;
230 self.buf[self.idx] = x + buf * 0.5;
231 self.idx = (self.idx + 1) % self.buf.len();
232 y
233 }
234}
235struct Reverb { combs: Vec<Comb>, allpass: Vec<Allpass>, mix: f32 }
237impl Reverb {
238 fn new(rate: u32) -> Self {
239 let s = rate as f32 / 44100.0;
240 let comb = |n: usize, fb: f32| Comb::new((n as f32 * s) as usize, fb);
241 let ap = |n: usize| Allpass::new((n as f32 * s) as usize);
242 Self {
243 combs: vec![comb(1116, 0.84), comb(1188, 0.83), comb(1277, 0.82), comb(1356, 0.81)],
244 allpass: vec![ap(225), ap(556)],
245 mix: 0.0,
246 }
247 }
248 #[inline]
249 fn process(&mut self, l: f32, r: f32) -> (f32, f32) {
250 if self.mix <= 0.0 { return (l, r); }
251 let x = (l + r) * 0.5;
252 let mut y = 0.0;
253 for c in &mut self.combs { y += c.process(x); }
254 y *= 0.25;
255 for a in &mut self.allpass { y = a.process(y); }
256 (l + y * self.mix, r + y * self.mix)
257 }
258}
259
260struct LowPass { yl: [f32; 2], yr: [f32; 2], cutoff: f32, target: f32 }
263impl LowPass {
264 fn new() -> Self { Self { yl: [0.0; 2], yr: [0.0; 2], cutoff: 1.0, target: 1.0 } }
265 #[inline]
266 fn process(&mut self, l: f32, r: f32) -> (f32, f32) {
267 self.cutoff += (self.target - self.cutoff) * 0.001;
269 if self.cutoff >= 0.999 { return (l, r); }
270 let a = (self.cutoff * self.cutoff).clamp(0.0008, 1.0);
272 self.yl[0] += a * (l - self.yl[0]); self.yl[1] += a * (self.yl[0] - self.yl[1]);
273 self.yr[0] += a * (r - self.yr[0]); self.yr[1] += a * (self.yr[0] - self.yr[1]);
274 (self.yl[1], self.yr[1])
275 }
276}
277
278struct AudioState {
279 tones: Vec<Option<Tone>>,
280 blips: Vec<Blip>,
281 sfx: Vec<SfxVoice>,
282 samples: Vec<(std::sync::Arc<Vec<f32>>, u32)>, sample_voices: Vec<SampleVoice>,
284 next_voice_id: u32,
285 delay: Delay,
286 reverb: Reverb,
287 lowpass: LowPass,
288 bgm: Option<BgmTrack>,
289 master_volume: f32,
290 cry: f32, sry: f32,
292 crx: f32, srx: f32,
293 room_w: f32,
295 lx: f32, ly: f32, lz: f32,
297 sample_rate: u32,
298}
299
300impl AudioState {
301 fn new(sample_rate: u32) -> Self {
302 Self {
303 tones: (0..16).map(|_| None).collect(),
304 blips: Vec::new(),
305 sfx: Vec::new(),
306 samples: Vec::new(),
307 sample_voices: Vec::new(),
308 next_voice_id: 1,
309 delay: Delay::new(sample_rate),
310 reverb: Reverb::new(sample_rate),
311 lowpass: LowPass::new(),
312 bgm: None,
313 master_volume: 0.5,
314 cry: 1.0, sry: 0.0,
315 crx: 1.0, srx: 0.0,
316 room_w: 9.0,
317 lx: 0.0, ly: 0.0, lz: 0.0,
318 sample_rate,
319 }
320 }
321
322 #[inline]
324 fn next_sample(&mut self) -> (f32, f32) {
325 let cry = self.cry;
327 let sry = self.sry;
328 let crx = self.crx;
329 let srx = self.srx;
330 let room_w = self.room_w;
331 let lx = self.lx;
332 let ly = self.ly;
333 let lz = self.lz;
334 let dt = 1.0 / self.sample_rate as f32;
335
336 let mut l = 0.0f32;
337 let mut r = 0.0f32;
338
339 for slot in &mut self.tones {
340 let tone = match slot.as_mut() { Some(t) => t, None => continue };
341 let (px, py, pz, pfreq, pamp, plfo_depth, plfo_rate, pw) = {
343 let p = &tone.params;
344 (p.x, p.y, p.z, p.freq, p.amp, p.lfo_depth, p.lfo_rate, p.w)
345 };
346 tone.cur_amp += (pamp - tone.cur_amp) * 0.004;
350 tone.cur_freq += (pfreq - tone.cur_freq) * 0.012;
351
352 let rz1 = px * sry + pz * cry;
355 let cam_x = px * cry - pz * sry;
356 let cam_y = py * crx - rz1 * srx;
357 let cam_z = py * srx + rz1 * crx;
358
359 let dist = (cam_x * cam_x + cam_y * cam_y + cam_z * cam_z).sqrt().max(0.5);
361 let atten = (1.0 / (1.0 + dist * 0.18)).clamp(0.0, 1.0);
362
363 let pan = (cam_x / room_w.max(1.0)).clamp(-1.0, 1.0);
365 let angle = (pan + 1.0) * 0.5 * FRAC_PI_2;
366 let l_gain = angle.cos() * atten;
367 let r_gain = angle.sin() * atten;
368
369 let lfo_mod = (tone.lfo_phase * TAU).sin() * plfo_depth;
371 tone.lfo_phase = (tone.lfo_phase + plfo_rate * dt).fract();
372
373 let w_mod = (tone.w_phase * TAU).sin() * 0.25;
377 let w_freq = tone.cur_freq * pw.abs() * 0.007;
378 tone.w_phase = (tone.w_phase + w_freq * dt).fract();
379
380 let inst_freq = tone.cur_freq * (1.0 + lfo_mod) * (1.0 + w_mod * 0.08);
382 let sample = (tone.phase * TAU).sin() * tone.cur_amp;
383 tone.phase = (tone.phase + inst_freq * dt).fract();
384
385 l += sample * l_gain;
386 r += sample * r_gain;
387 }
388
389 if !self.blips.is_empty() {
391 let mut mono = 0.0f32;
392 for b in &mut self.blips { mono += b.next(dt); }
393 self.blips.retain(|b| !b.done());
394 l += mono;
395 r += mono;
396 }
397
398 if !self.sfx.is_empty() {
400 for v in &mut self.sfx {
401 let (lg, rg) = spatial_gains(cry, sry, crx, srx, lx, ly, lz, room_w, v.x, v.y, v.z);
402 let s = v.next(dt);
403 l += s * lg;
404 r += s * rg;
405 }
406 self.sfx.retain(|v| !v.done());
407 }
408
409 if !self.sample_voices.is_empty() {
411 let out_rate = self.sample_rate as f64;
412 for v in &mut self.sample_voices {
413 if !v.active { continue; }
414 let (buf, src_rate) = match self.samples.get(v.sample) { Some(s) => s, None => { v.active = false; continue; } };
415 let n = buf.len();
416 if n < 2 { v.active = false; continue; }
417 let idx = v.pos as usize;
418 let frac = (v.pos - idx as f64) as f32;
419 let s = if idx + 1 < n { buf[idx] + (buf[idx + 1] - buf[idx]) * frac } else { buf[idx.min(n - 1)] };
420 let (lg, rg) = spatial_gains(cry, sry, crx, srx, lx, ly, lz, room_w, v.x, v.y, v.z);
421 l += s * v.vol * lg;
422 r += s * v.vol * rg;
423 v.pos += *src_rate as f64 / out_rate;
424 if v.pos as usize >= n - 1 {
425 if v.looping { v.pos = 0.0; } else { v.active = false; }
426 }
427 }
428 self.sample_voices.retain(|v| v.active);
429 }
430
431 if let Some(bgm) = &mut self.bgm {
433 let n_pairs = bgm.samples.len() / 2;
434 if n_pairs >= 2 {
435 let ratio = bgm.src_rate as f64 / self.sample_rate as f64;
436 let idx = bgm.pos as usize;
437 let frac = (bgm.pos - idx as f64) as f32;
438 let nxt = (idx + 1) % n_pairs;
439
440 let bl = bgm.samples[idx * 2 ] + (bgm.samples[nxt * 2 ] - bgm.samples[idx * 2 ]) * frac;
441 let br = bgm.samples[idx * 2 + 1] + (bgm.samples[nxt * 2 + 1] - bgm.samples[idx * 2 + 1]) * frac;
442
443 l += bl * bgm.volume;
444 r += br * bgm.volume;
445
446 bgm.pos += ratio;
447 if bgm.pos as usize >= n_pairs.saturating_sub(1) {
448 bgm.pos = 0.0; }
450 }
451 }
452
453 let (l, r) = self.delay.process(l, r);
455 let (l, r) = self.reverb.process(l, r);
456 let (l, r) = self.lowpass.process(l, r);
457 let mv = self.master_volume;
458 ((l * mv).tanh(), (r * mv).tanh())
459 }
460}
461
462pub struct AudioEngine {
467 state: Arc<Mutex<AudioState>>,
468 _stream: cpal::Stream,
470 pub out_rate: u32,
472}
473
474impl AudioEngine {
475 pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
478 let host = cpal::default_host();
479 let device = host.default_output_device()
480 .ok_or("no default audio output device")?;
481 let supported = device.default_output_config()?;
482
483 let channels = supported.channels() as usize;
484 let out_rate = supported.sample_rate().0;
485 let fmt = supported.sample_format();
486 let config = supported.config();
487
488 let state = Arc::new(Mutex::new(AudioState::new(out_rate)));
489 let stream = build_stream(&device, &config, channels, Arc::clone(&state), fmt)?;
490 stream.play()?;
491
492 Ok(Self { state, _stream: stream, out_rate })
493 }
494
495 pub fn set_tone(&self, idx: usize, params: ToneParams) {
499 if let Ok(mut s) = self.state.lock() {
500 while s.tones.len() <= idx { s.tones.push(None); }
501 match &mut s.tones[idx] {
502 Some(t) => t.params = params,
503 slot => *slot = Some(Tone::new(params)),
504 }
505 }
506 }
507
508 pub fn blip(&self, freq: f32, amp: f32, dur: f32, wave: Wave) {
511 if let Ok(mut s) = self.state.lock() {
512 if s.blips.len() >= 32 { s.blips.remove(0); }
513 s.blips.push(Blip { freq, amp, wave, dur: dur.max(0.01), age: 0.0, phase: 0.0, seed: freq.to_bits().wrapping_mul(2654435761).wrapping_add(1) });
514 }
515 }
516
517 pub fn sfx(&self, x: f32, y: f32, z: f32, w: f32, freq: f32, amp: f32, dur: f32, wave: Wave) {
519 if let Ok(mut s) = self.state.lock() {
520 if s.sfx.len() >= 64 { s.sfx.remove(0); }
521 s.sfx.push(SfxVoice { x, y, z, w, freq, amp, wave, dur: dur.max(0.01), age: 0.0, phase: 0.0, w_phase: 0.0, seed: freq.to_bits().wrapping_mul(2654435761).wrapping_add(1) });
522 }
523 }
524
525 pub fn add_sample(&self, mono: Vec<f32>, src_rate: u32) -> usize {
527 if let Ok(mut s) = self.state.lock() {
528 s.samples.push((std::sync::Arc::new(mono), src_rate.max(1)));
529 s.samples.len() - 1
530 } else { 0 }
531 }
532
533 pub fn play_sample(&self, id: usize, x: f32, y: f32, z: f32, w: f32, vol: f32, looping: bool) -> u32 {
535 if let Ok(mut s) = self.state.lock() {
536 if id >= s.samples.len() { return 0; }
537 let vid = s.next_voice_id; s.next_voice_id += 1;
538 if s.sample_voices.len() >= 64 { s.sample_voices.remove(0); }
539 s.sample_voices.push(SampleVoice { id: vid, sample: id, pos: 0.0, x, y, z, w, vol, looping, active: true });
540 vid
541 } else { 0 }
542 }
543
544 pub fn stop_sample(&self, voice: u32) {
546 if let Ok(mut s) = self.state.lock() {
547 if let Some(v) = s.sample_voices.iter_mut().find(|v| v.id == voice) { v.active = false; }
548 }
549 }
550
551 pub fn fx_delay(&self, time_s: f32, feedback: f32, mix: f32) {
553 if let Ok(mut s) = self.state.lock() {
554 let cap = s.delay.bl.len();
555 s.delay.len = ((time_s.max(0.0) * s.sample_rate as f32) as usize).min(cap.saturating_sub(1));
556 s.delay.fb = feedback.clamp(0.0, 0.95);
557 s.delay.mix = mix.clamp(0.0, 1.0);
558 }
559 }
560 pub fn fx_reverb(&self, mix: f32) {
561 if let Ok(mut s) = self.state.lock() { s.reverb.mix = mix.clamp(0.0, 1.0); }
562 }
563 pub fn fx_lowpass(&self, cutoff01: f32) {
565 if let Ok(mut s) = self.state.lock() { s.lowpass.target = cutoff01.clamp(0.0, 1.0); }
566 }
567
568 pub fn clear_tone(&self, idx: usize) {
570 if let Ok(mut s) = self.state.lock() {
571 if let Some(slot) = s.tones.get_mut(idx) { *slot = None; }
572 }
573 }
574
575 pub fn set_listener(&self, cry: f32, sry: f32, crx: f32, srx: f32) {
579 if let Ok(mut s) = self.state.lock() {
580 s.cry = cry; s.sry = sry;
581 s.crx = crx; s.srx = srx;
582 }
583 }
584
585 pub fn set_listener_pos(&self, x: f32, y: f32, z: f32) {
588 if let Ok(mut s) = self.state.lock() {
589 s.lx = x; s.ly = y; s.lz = z;
590 }
591 }
592
593 pub fn load_bgm(&self, path: &str, vol: f32) {
598 match load_wav(path) {
599 Ok((samples, src_rate)) => {
600 if let Ok(mut s) = self.state.lock() {
601 s.bgm = Some(BgmTrack { samples, src_rate, pos: 0.0, volume: vol });
602 }
603 }
604 Err(e) => eprintln!("audio: bgm load failed ({path}): {e}"),
605 }
606 }
607
608 pub fn set_bgm_volume(&self, vol: f32) {
610 if let Ok(mut s) = self.state.lock() {
611 if let Some(bgm) = &mut s.bgm { bgm.volume = vol; }
612 }
613 }
614
615 pub fn set_master_volume(&self, vol: f32) {
618 if let Ok(mut s) = self.state.lock() { s.master_volume = vol; }
619 }
620}
621
622fn load_wav(path: &str) -> Result<(Vec<f32>, u32), Box<dyn std::error::Error>> {
625 let mut reader = hound::WavReader::open(path)?;
626 let spec = reader.spec();
627 let channels = spec.channels as usize;
628 let src_rate = spec.sample_rate;
629
630 let raw: Vec<f32> = match spec.sample_format {
631 hound::SampleFormat::Float => {
632 reader.samples::<f32>().filter_map(|s| s.ok()).collect()
633 }
634 hound::SampleFormat::Int => {
635 let max = (1i32 << spec.bits_per_sample.saturating_sub(1)) as f32;
637 reader.samples::<i32>().filter_map(|s| s.ok())
638 .map(|s| s as f32 / max)
639 .collect()
640 }
641 };
642
643 let stereo: Vec<f32> = match channels {
645 1 => raw.iter().flat_map(|&s| [s, s]).collect(),
646 2 => raw,
647 n => raw.chunks(n)
648 .flat_map(|c| [c[0], if c.len() > 1 { c[1] } else { c[0] }])
649 .collect(),
650 };
651
652 Ok((stereo, src_rate))
653}
654
655fn build_stream(
658 device: &cpal::Device,
659 config: &cpal::StreamConfig,
660 channels: usize,
661 state: Arc<Mutex<AudioState>>,
662 fmt: cpal::SampleFormat,
663) -> Result<cpal::Stream, Box<dyn std::error::Error>> {
664 let err_fn = |e: cpal::StreamError| eprintln!("cpal stream error: {e}");
665
666 Ok(match fmt {
667 cpal::SampleFormat::F32 => {
668 let st = Arc::clone(&state);
669 device.build_output_stream(
670 config,
671 move |data: &mut [f32], _| fill_f32(data, channels, &st),
672 err_fn,
673 None,
674 )?
675 }
676 cpal::SampleFormat::I16 => {
677 let st = Arc::clone(&state);
678 device.build_output_stream(
679 config,
680 move |data: &mut [i16], _| fill_i16(data, channels, &st),
681 err_fn,
682 None,
683 )?
684 }
685 _ => {
686 let st = Arc::clone(&state);
688 device.build_output_stream::<i16, _, _>(
689 config,
690 move |data: &mut [i16], _| fill_i16(data, channels, &st),
691 err_fn,
692 None,
693 )?
694 }
695 })
696}
697
698fn fill_f32(data: &mut [f32], channels: usize, state: &Arc<Mutex<AudioState>>) {
700 let ch = channels.max(1);
701 if let Ok(mut s) = state.try_lock() {
702 for frame in data.chunks_mut(ch) {
703 let (l, r) = s.next_sample();
704 frame[0] = l;
705 if ch > 1 { frame[1] = r; }
706 for extra in frame.iter_mut().skip(2) { *extra = 0.0; }
707 }
708 } else {
709 for s in data.iter_mut() { *s = 0.0; }
710 }
711}
712
713fn fill_i16(data: &mut [i16], channels: usize, state: &Arc<Mutex<AudioState>>) {
715 let ch = channels.max(1);
716 if let Ok(mut s) = state.try_lock() {
717 for frame in data.chunks_mut(ch) {
718 let (l, r) = s.next_sample();
719 frame[0] = (l * 32_767.0) as i16;
720 if ch > 1 { frame[1] = (r * 32_767.0) as i16; }
721 for extra in frame.iter_mut().skip(2) { *extra = 0; }
722 }
723 } else {
724 for s in data.iter_mut() { *s = 0; }
725 }
726}
727
728#[cfg(test)]
729mod tests {
730 use super::*;
731
732 #[test]
733 fn sfx_voice_envelopes_and_ends() {
734 let mut v = SfxVoice { x: 0.0, y: 0.0, z: 0.0, w: 1.0, freq: 440.0, amp: 0.5,
735 wave: Wave::Sine, dur: 0.02, age: 0.0, phase: 0.0, w_phase: 0.0, seed: 1 };
736 let dt = 1.0 / 44100.0;
737 let mut peak = 0.0f32;
738 let mut steps = 0;
739 while !v.done() && steps < 44100 { peak = peak.max(v.next(dt).abs()); steps += 1; }
740 assert!(peak > 0.01, "sfx should produce sound");
741 assert!(v.done(), "sfx should finish after its duration");
742 }
743
744 #[test]
745 fn sample_voice_loops_and_oneshot_stops() {
746 let mut st = AudioState::new(44100);
747 st.samples.push((std::sync::Arc::new(vec![0.5f32; 100]), 44100));
748 st.sample_voices.push(SampleVoice { id: 1, sample: 0, pos: 0.0, x: 0.0, y: 0.0, z: 0.0, w: 1.0, vol: 1.0, looping: true, active: true });
750 for _ in 0..500 { let _ = st.next_sample(); }
751 assert_eq!(st.sample_voices.len(), 1, "looping voice should still be alive");
752 st.sample_voices.push(SampleVoice { id: 2, sample: 0, pos: 0.0, x: 0.0, y: 0.0, z: 0.0, w: 1.0, vol: 1.0, looping: false, active: true });
754 for _ in 0..500 { let _ = st.next_sample(); }
755 assert!(st.sample_voices.iter().all(|v| v.id != 2), "one-shot should have stopped");
756 }
757
758 #[test]
759 fn master_fx_stay_finite() {
760 let mut st = AudioState::new(44100);
761 st.delay.len = 2000; st.delay.fb = 0.6; st.delay.mix = 0.4;
762 st.reverb.mix = 0.5;
763 st.lowpass.target = 0.2; st.lowpass.cutoff = 0.2;
764 st.tones[0] = Some(Tone::new(ToneParams { freq: 220.0, amp: 0.8, ..Default::default() }));
766 for _ in 0..44100 {
767 let (l, r) = st.next_sample();
768 assert!(l.is_finite() && r.is_finite() && l.abs() <= 1.0 && r.abs() <= 1.0);
769 }
770 }
771}