- 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>
49 lines
1.5 KiB
Rust
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);
|
|
}
|
|
}
|