Files
dungeoncraft/src/sound.rs
T
Richard ZinkandClaude Opus 5.5 925be17e82 Own XM/MOD replayer, audio mixer with crossfades, synthesised chip sound effects, music director
- src/xm.rs: FastTracker 2 XM and ProTracker MOD replayer (verified against libopenmpt)
- src/audio.rs: music players with crossfade + effect voices, rendered on demand
- src/sfx.rs: 29 synthesised chip sound effects
- www/dc_plugin.js: AudioContext started on first input, streams samples from WASM
- music follows the game: title, rotating build tracks, battle, gate assault, jingles
- macroquad audio removed; all tests run natively with cargo test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 06:10:28 +01:00

49 lines
1.5 KiB
Rust

// Sound effect dispatch: turns game sound events into positioned effect voices
// (volume by distance to the camera, stereo panning along the screen's x axis).
// The audio itself is rendered by audio.rs.
use crate::audio;
use crate::game::{Sfx, SfxEvent, SFX_ALL};
use macroquad::prelude::*;
pub struct SoundBank {
last: Vec<f64>,
}
impl SoundBank {
pub fn new() -> SoundBank {
SoundBank { last: vec![-1.0; SFX_ALL.len()] }
}
// minimum gap between two identical sounds (avoids a wall of noise)
fn min_gap(sfx: Sfx) -> f64 {
match sfx {
Sfx::Dig | Sfx::GoldDig | Sfx::MeleeHit | Sfx::Shoot | Sfx::GateHit | Sfx::CoreHit => 0.08,
Sfx::Claim | Sfx::Deposit | Sfx::DigDone => 0.12,
_ => 0.03,
}
}
// listener: camera target on the ground; right: screen-right direction on the ground;
// hear: audible radius in tiles
pub fn play(&mut self, ev: SfxEvent, listener: Vec2, right: Vec2, hear: f32, now: f64) {
let i = ev.sfx.index();
if now - self.last[i] < Self::min_gap(ev.sfx) {
return;
}
let mut vol = 1.0;
let mut pan = 0.0;
if let Some((wx, wy)) = ev.pos {
let d = vec2(wx, wy) - listener;
let f = 1.0 - ((d.length() - hear * 0.5) / hear).clamp(0.0, 1.0);
if f <= 0.05 {
return;
}
vol *= f;
pan = (d.dot(right) / hear).clamp(-1.0, 1.0) * 0.7;
}
self.last[i] = now;
audio::play_sfx(i, vol, pan);
}
}