Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
065fe80218 | ||
|
|
bc1f89b4a7 |
No files matched your search
+10
-1
@@ -62,17 +62,26 @@ pub enum Sfx {
|
||||
Voice4,
|
||||
Voice5,
|
||||
Voice6,
|
||||
// weather, rogues and groves
|
||||
Thunder,
|
||||
Steal,
|
||||
Snare,
|
||||
Sermon,
|
||||
Rain,
|
||||
Wind,
|
||||
Spores,
|
||||
}
|
||||
|
||||
pub const VOICE_SYLLABLES: [Sfx; 6] = [Sfx::Voice1, Sfx::Voice2, Sfx::Voice3, Sfx::Voice4, Sfx::Voice5, Sfx::Voice6];
|
||||
|
||||
pub const SFX_ALL: [Sfx; 42] = [
|
||||
pub const SFX_ALL: [Sfx; 49] = [
|
||||
Sfx::Dig, Sfx::GoldDig, Sfx::DigDone, Sfx::Claim, Sfx::Deposit, Sfx::Build, Sfx::Sell, Sfx::MeleeHit,
|
||||
Sfx::Shoot, Sfx::Death, Sfx::HeroDeath, Sfx::Lightning, Sfx::Heal, Sfx::Summon, Sfx::PortalArrive,
|
||||
Sfx::WaveAlarm, Sfx::WaveStart, Sfx::CoreHit, Sfx::GateHit, Sfx::GateDefenders, Sfx::LevelUp,
|
||||
Sfx::Pickup, Sfx::Drop, Sfx::Payday, Sfx::FlagSet, Sfx::Victory, Sfx::Defeat, Sfx::Click, Sfx::Error,
|
||||
Sfx::Reinforce, Sfx::Cannon, Sfx::Magic, Sfx::Capture, Sfx::Convert, Sfx::Slap, Sfx::Punch,
|
||||
Sfx::Voice1, Sfx::Voice2, Sfx::Voice3, Sfx::Voice4, Sfx::Voice5, Sfx::Voice6,
|
||||
Sfx::Thunder, Sfx::Steal, Sfx::Snare, Sfx::Sermon, Sfx::Rain, Sfx::Wind, Sfx::Spores,
|
||||
];
|
||||
|
||||
impl Sfx {
|
||||
|
||||
@@ -1992,6 +1992,7 @@ app.pending_briefing = briefing_of(&campaign, i);
|
||||
for ev in app.game.events.drain(..) {
|
||||
sounds.play(ev, vec2(app.cam.rx, app.cam.rz), right, hear, now);
|
||||
}
|
||||
sounds.ambience(&app.game, (app.cam.rx, app.cam.rz), now);
|
||||
// a won mission goes into the campaign progress (with the carry-over); a co-op mission of an
|
||||
// editor campaign keeps no progress (its index is not a built-in mission)
|
||||
if app.coop_editor {
|
||||
|
||||
+3
-2
@@ -94,6 +94,7 @@ impl Game {
|
||||
}
|
||||
e.carry += got;
|
||||
self.fx(5, e.x, e.y, 0.0, 0.0, 0.5, 0xff30d0ff);
|
||||
self.sfx(Sfx::Steal, e.x, e.y);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -195,7 +196,7 @@ impl Game {
|
||||
e.flash = 0.2;
|
||||
let (x, y, side, kind, dead) = (e.x, e.y, e.side, e.kind, e.hp <= 0.0);
|
||||
self.fx(2, x, y, 0.0, 0.0, 0.4, 0xff4040ff);
|
||||
self.sfx(Sfx::MeleeHit, x, y);
|
||||
self.sfx(Sfx::Snare, x, y);
|
||||
if side == self.local_side {
|
||||
self.tell_local("msg.snare_caught", vec![format!("@{}", st(kind).name)], None);
|
||||
}
|
||||
@@ -244,7 +245,7 @@ impl Game {
|
||||
self.shot(e.x, e.y, 0.6, x, y, 0.45, 0.35, 0xff40d0ff);
|
||||
self.style_last_fx(crate::particles::ST_HOLY);
|
||||
self.fx(4, x, y, 0.0, 0.0, 0.9, 0xffffeecc);
|
||||
self.sfx(Sfx::Capture, x, y);
|
||||
self.sfx(Sfx::Sermon, x, y);
|
||||
if side == self.local_side {
|
||||
self.tell_local("msg.converted", vec![format!("@{}", st(kind).name)], Some(Sfx::WaveAlarm));
|
||||
}
|
||||
|
||||
+43
@@ -164,6 +164,31 @@ fn def(s: Sfx, r: f32) -> Vec<f32> {
|
||||
Sfx::Voice4 => l(&[seg(Square(0.15), 900.0, 980.0, 0.05, 0.13, 10.0), vib(seg(Square(0.2), 700.0, 560.0, 0.06, 0.14, 8.0), 0.04)], 65),
|
||||
Sfx::Voice5 => mix(vec![l(&[vib(seg(Square(0.25), 470.0, 560.0, 0.1, 0.15, 5.0), 0.06)], 66), l(&[seg(Noise, 3000.0, 2000.0, 0.025, 0.06, 30.0)], 67)]),
|
||||
Sfx::Voice6 => l(&[vib(seg(Square(0.2), 680.0, 820.0, 0.06, 0.15, 9.0), 0.05), seg(Square(0.2), 820.0, 700.0, 0.04, 0.12, 12.0)], 68),
|
||||
// a long rumble rolling away after the crack
|
||||
Sfx::Thunder => mix(vec![
|
||||
l(&[seg(Noise, 420.0, 70.0, 2.4, 0.55, 1.1)], 70),
|
||||
l(&[vib(seg(Tri, 55.0, 32.0, 2.2, 0.5, 1.2), 0.08)], 71),
|
||||
l(&[seg(Noise, 160.0, 160.0, 0.35, 0.0, 0.0), seg(Noise, 160.0, 90.0, 1.4, 0.3, 1.6)], 72),
|
||||
]),
|
||||
// the thief's fingers in the coins: a quick, soft jingle
|
||||
Sfx::Steal => mix(vec![l(&arp(Square(0.25), &[2093.0, 1760.0, 2349.0], 0.04, 0.1), 73), l(&[seg(Noise, 7000.0, 7000.0, 0.03, 0.08, 60.0)], 74)]),
|
||||
// iron jaws snap shut
|
||||
Sfx::Snare => mix(vec![l(&[seg(Noise, 8000.0, 3000.0, 0.04, 0.5, 50.0)], 75), l(&[seg(Square(0.5), 1400.0, 300.0, 0.09, 0.22, 25.0)], 76)]),
|
||||
// the inquisitor's sermon: a short, hollow chant
|
||||
Sfx::Sermon => mix(vec![
|
||||
l(&[vib(seg(Sine, 330.0, 330.0, 0.7, 0.28, 2.0), 0.02)], 77),
|
||||
l(&[vib(seg(Sine, 392.0, 415.0, 0.7, 0.2, 2.0), 0.02)], 78),
|
||||
l(&[vib(seg(Sine, 494.0, 494.0, 0.7, 0.16, 2.0), 0.03)], 79),
|
||||
]),
|
||||
// rain on the land: fades in and out, played over and over while it rains
|
||||
Sfx::Rain => mix(vec![
|
||||
l(&[seg(Noise, 7000.0, 7000.0, 0.3, 0.04, -6.0), seg(Noise, 7000.0, 7000.0, 0.9, 0.24, 0.0), seg(Noise, 7000.0, 7000.0, 0.3, 0.24, 6.0)], 80),
|
||||
l(&[seg(Noise, 2500.0, 2500.0, 0.3, 0.02, -6.0), seg(Noise, 2500.0, 2500.0, 0.9, 0.12, 0.0), seg(Noise, 2500.0, 2500.0, 0.3, 0.12, 6.0)], 81),
|
||||
]),
|
||||
// wind over the land: a slow, howling sweep
|
||||
Sfx::Wind => l(&[seg(Noise, 300.0, 700.0, 0.6, 0.03, -3.0), seg(Noise, 700.0, 500.0, 1.4, 0.18, 0.2), seg(Noise, 500.0, 250.0, 0.6, 0.15, 3.0)], 82),
|
||||
// the spores of a grove: a faint chime
|
||||
Sfx::Spores => l(&arp(Sine, &[2093.0, 2637.0, 3136.0, 2637.0], 0.09, 0.07), 83),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,3 +196,21 @@ fn def(s: Sfx, r: f32) -> Vec<f32> {
|
||||
pub fn generate_all(rate: f32) -> Vec<Vec<f32>> {
|
||||
SFX_ALL.iter().map(|&s| def(s, rate)).collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn every_effect_is_audible_and_does_not_clip() {
|
||||
let all = generate_all(22050.0);
|
||||
for (s, buf) in SFX_ALL.iter().zip(all.iter()) {
|
||||
let peak = buf.iter().fold(0.0f32, |m, v| m.max(v.abs()));
|
||||
assert!(!buf.is_empty() && peak > 0.02, "{:?} is silent", s);
|
||||
assert!(peak < 1.5, "{:?} is too loud: {}", s, peak);
|
||||
}
|
||||
// the rain loops: it is long enough to overlap when played every 1.25 s
|
||||
let rain = &all[Sfx::Rain.index()];
|
||||
assert!(rain.len() as f32 / 22050.0 > 1.3);
|
||||
}
|
||||
}
|
||||
+42
-1
@@ -8,11 +8,52 @@ use macroquad::prelude::*;
|
||||
|
||||
pub struct SoundBank {
|
||||
last: Vec<f64>,
|
||||
next_rain: f64,
|
||||
next_wind: f64,
|
||||
next_spores: f64,
|
||||
}
|
||||
|
||||
impl SoundBank {
|
||||
pub fn new() -> SoundBank {
|
||||
SoundBank { last: vec![-1.0; SFX_ALL.len()] }
|
||||
SoundBank { last: vec![-1.0; SFX_ALL.len()], next_rain: 0.0, next_wind: 0.0, next_spores: 0.0 }
|
||||
}
|
||||
|
||||
// background sounds where the camera looks: rain and wind over the land above ground (the
|
||||
// samples fade in and out and overlap a little), the chime of a mushroom grove now and then
|
||||
pub fn ambience(&mut self, g: &crate::game::Game, (cx, cz): (f32, f32), now: f64) {
|
||||
use crate::map::{idx, inb, Kind};
|
||||
let near = |r: i32, f: &dyn Fn(usize) -> bool| {
|
||||
let (x, z) = (cx as i32, cz as i32);
|
||||
let mut n = 0;
|
||||
for dz in (-r..=r).step_by(2) {
|
||||
for dx in (-r..=r).step_by(2) {
|
||||
if inb(x + dx, z + dz) && f(idx(x + dx, z + dz)) {
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
n
|
||||
};
|
||||
if g.state != 0 {
|
||||
return;
|
||||
}
|
||||
let outside = !g.map.sky.is_empty() && near(6, &|i| g.map.sky(i)) >= 6;
|
||||
let w = &g.weather;
|
||||
if outside && w.rain() > 0.05 && now >= self.next_rain {
|
||||
self.next_rain = now + 1.25;
|
||||
audio::play_sfx(Sfx::Rain.index(), 0.55 * w.rain(), 0.0);
|
||||
}
|
||||
let windy = if w.shown == crate::weather::STORM { w.level } else { w.fog() * 0.5 };
|
||||
if outside && windy > 0.05 && now >= self.next_wind {
|
||||
self.next_wind = now + 2.2;
|
||||
audio::play_sfx(Sfx::Wind.index(), 0.45 * windy, (now.sin() * 0.4) as f32);
|
||||
}
|
||||
if now >= self.next_spores {
|
||||
self.next_spores = now + 3.0 + (now * 7.3).sin().abs() * 3.0;
|
||||
if near(5, &|i| matches!(g.map.t[i].kind, Kind::Grove)) >= 3 {
|
||||
audio::play_sfx(Sfx::Spores.index(), 0.35, ((now * 1.7).sin() * 0.5) as f32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// minimum gap between two identical sounds (avoids a wall of noise)
|
||||
|
||||
@@ -175,6 +175,10 @@ impl Game {
|
||||
self.weather.flash = self.time;
|
||||
self.fx(1, x, y, 0.0, 0.0, 0.5, 0xffffeeaa);
|
||||
self.sfx(Sfx::Lightning, x, y);
|
||||
// the thunder rolls over the whole land
|
||||
if self.events.len() < 64 {
|
||||
self.events.push(SfxEvent { sfx: Sfx::Thunder, pos: None });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user