<?phpnamespace Tools\Art;/** The sound effects, synthesized (22050 Hz, 16-bit mono WAV). */final class Sounds{ private const RATE = 22050; /** @return array<string, string> name => WAV bytes */ public static function all(): array { Noise::seed(99); return [ 'pistol' => self::mix(0.28, fn ($t) => self::noise() * self::env($t, 0.002, 0.25, 3) * 0.9 + self::sq(140 - $t * 200, $t) * self::env($t, 0.001, 0.12, 4) * 0.5, 0.35), 'shotgun' => self::mix(0.6, fn ($t) => self::lowNoise(0.35) * self::env($t, 0.002, 0.55, 2.5) * 1.0 + self::sq(70 - $t * 40, $t) * self::env($t, 0.001, 0.3, 3) * 0.6), 'pump' => self::mix(0.35, fn ($t) => ($t < 0.08 || ($t > 0.18 && $t < 0.26)) ? self::noise() * 0.5 * self::env(fmod($t, 0.18), 0.001, 0.08, 6) : 0.0), 'rivet' => self::mix(0.16, fn ($t) => self::noise() * self::env($t, 0.001, 0.15, 4) * 0.8 + self::sq(220 - $t * 600, $t) * self::env($t, 0.001, 0.08, 4) * 0.4), 'ion' => self::mix(0.35, fn ($t) => self::sine(300 + $t * 2400, $t) * self::env($t, 0.005, 0.33, 2) * 0.6 + self::sq(150 + $t * 900, $t) * 0.2 * self::env($t, 0.005, 0.3, 2)), 'swing' => self::mix(0.2, fn ($t) => self::lowNoise(0.6) * sin(min(1, $t / 0.2) * M_PI) * 0.5), 'punch' => self::mix(0.15, fn ($t) => self::lowNoise(0.25) * self::env($t, 0.001, 0.14, 5) * 0.9), 'dooropen' => self::mix(0.9, fn ($t) => (self::sq(55 + $t * 20, $t) * 0.3 + self::lowNoise(0.2) * 0.5) * self::env($t, 0.05, 0.85, 1)), 'doorclose' => self::mix(0.9, fn ($t) => (self::sq(75 - $t * 25, $t) * 0.3 + self::lowNoise(0.2) * 0.5) * self::env($t, 0.05, 0.85, 1) + ($t > 0.75 ? self::lowNoise(0.4) * self::env($t - 0.75, 0.001, 0.15, 3) : 0)), 'lift' => self::mix(1.0, fn ($t) => (self::sq(48, $t) * 0.25 + self::lowNoise(0.1) * 0.4) * self::env($t, 0.1, 0.9, 1)), 'liftstop' => self::mix(0.3, fn ($t) => self::lowNoise(0.3) * self::env($t, 0.001, 0.28, 3) * 0.8), 'switch' => self::mix(0.18, fn ($t) => self::sq($t < 0.06 ? 1200 : 700, $t) * self::env($t, 0.001, 0.17, 3) * 0.4 + self::noise() * 0.3 * self::env($t, 0.001, 0.04, 5)), 'item' => self::mix(0.16, fn ($t) => self::sq($t < 0.06 ? 900 : 1350, $t) * self::env($t, 0.002, 0.15, 1) * 0.35), 'weapon' => self::mix(0.45, fn ($t) => self::sq([523, 659, 784, 1047][min(3, (int)($t / 0.1))], $t) * self::env($t, 0.002, 0.44, 1) * 0.35), 'powerup' => self::mix(0.8, fn ($t) => self::sine(400 + 500 * sin($t * 30) + $t * 600, $t) * self::env($t, 0.01, 0.78, 1) * 0.5), 'oof' => self::mix(0.2, fn ($t) => self::sq(110 - $t * 150, $t) * self::env($t, 0.005, 0.19, 2) * 0.5), 'noway' => self::mix(0.25, fn ($t) => self::sq(90, $t) * self::env($t, 0.005, 0.24, 2) * 0.45), 'plpain' => self::mix(0.35, fn ($t) => (self::saw(260 - $t * 300, $t) * 0.5 + self::noise() * 0.2) * self::env($t, 0.005, 0.33, 2)), 'pldeath' => self::mix(1.1, fn ($t) => (self::saw(300 - $t * 220, $t) * 0.5 + self::noise() * 0.2) * self::env($t, 0.01, 1.05, 1.5)), 'telept' => self::mix(0.6, fn ($t) => self::sine(200 + $t * 1500 + 200 * sin($t * 60), $t) * self::env($t, 0.01, 0.58, 1) * 0.5), 'barrel' => self::mix(1.2, fn ($t) => self::lowNoise(0.5 - $t * 0.3) * self::env($t, 0.002, 1.15, 2.5) * 1.0 + self::sine(45, $t) * self::env($t, 0.002, 0.6, 3) * 0.6), 'firesht' => self::mix(0.45, fn ($t) => (self::lowNoise(0.5) * 0.6 + self::sine(500 - $t * 600, $t) * 0.3) * self::env($t, 0.01, 0.43, 1.5)), 'firexpl' => self::mix(0.6, fn ($t) => (self::lowNoise(0.4) * 0.8 + self::sq(90 - $t * 80, $t) * 0.3) * self::env($t, 0.002, 0.58, 2)), 'plasma' => self::mix(0.5, fn ($t) => (self::sq(900 - $t * 1200, $t) * 0.3 + self::sine(1800 - $t * 2000, $t) * 0.3) * self::env($t, 0.002, 0.48, 1.5)), // monsters: sight, active, pain, death per kind 'husksit' => self::mix(0.6, fn ($t) => self::sq(180 + 60 * floor(fmod($t * 12, 3)), $t) * self::env($t, 0.01, 0.58, 1) * 0.35 + self::noise() * 0.08), 'huskact' => self::mix(0.4, fn ($t) => self::sq(120 + 40 * sin($t * 40), $t) * self::env($t, 0.01, 0.38, 1) * 0.25), 'huskpain' => self::mix(0.3, fn ($t) => self::sq(400 - $t * 600, $t) * self::env($t, 0.005, 0.28, 2) * 0.4 + self::noise() * 0.15 * self::env($t, 0.001, 0.1, 3)), 'huskdie' => self::mix(0.9, fn ($t) => (self::sq(300 - $t * 280, $t) * 0.35 + self::noise() * 0.35) * self::env($t, 0.005, 0.88, 1.5)), 'gnshsit' => self::mix(0.7, fn ($t) => (self::saw(90 + 30 * sin($t * 25), $t) * 0.5 + self::lowNoise(0.3) * 0.3) * self::env($t, 0.03, 0.66, 1)), 'gnshact' => self::mix(0.4, fn ($t) => self::lowNoise(0.25) * self::env($t, 0.02, 0.38, 1) * 0.5), 'gnshpain' => self::mix(0.35, fn ($t) => (self::saw(160 - $t * 100, $t) * 0.5 + self::noise() * 0.2) * self::env($t, 0.005, 0.33, 2)), 'gnshdie' => self::mix(1.0, fn ($t) => (self::saw(130 - $t * 90, $t) * 0.5 + self::lowNoise(0.3) * 0.3) * self::env($t, 0.01, 0.98, 1.2)), 'gnshbite' => self::mix(0.2, fn ($t) => self::lowNoise(0.5) * self::env($t, 0.001, 0.19, 4) * 0.9), 'spitsit' => self::mix(0.7, fn ($t) => (self::saw(240 + 80 * sin($t * 14), $t) * 0.4 + self::noise() * 0.25) * self::env($t, 0.02, 0.68, 1)), 'spitact' => self::mix(0.4, fn ($t) => self::noise() * 0.35 * self::env($t, 0.02, 0.38, 1) + self::saw(200, $t) * 0.15 * self::env($t, 0.02, 0.38, 1)), 'spitpain' => self::mix(0.3, fn ($t) => (self::saw(330 - $t * 300, $t) * 0.45 + self::noise() * 0.2) * self::env($t, 0.005, 0.28, 2)), 'spitdie' => self::mix(0.9, fn ($t) => (self::saw(280 - $t * 240, $t) * 0.45 + self::noise() * 0.3) * self::env($t, 0.01, 0.88, 1.3)), 'claw' => self::mix(0.2, fn ($t) => self::noise() * self::env($t, 0.001, 0.19, 3) * 0.6), 'wardsit' => self::mix(1.2, fn ($t) => (self::saw(60 + 20 * sin($t * 8), $t) * 0.5 + self::lowNoise(0.2) * 0.35) * self::env($t, 0.05, 1.15, 1)), 'wardact' => self::mix(0.6, fn ($t) => self::saw(55, $t) * self::env($t, 0.05, 0.58, 1) * 0.35), 'wardpain' => self::mix(0.45, fn ($t) => (self::saw(110 - $t * 60, $t) * 0.55 + self::lowNoise(0.3) * 0.25) * self::env($t, 0.005, 0.43, 2)), 'warddie' => self::mix(1.8, fn ($t) => (self::saw(90 - $t * 40, $t) * 0.5 + self::lowNoise(0.4) * 0.4) * self::env($t, 0.01, 1.78, 1)), 'step' => self::mix(0.12, fn ($t) => self::lowNoise(0.3) * self::env($t, 0.001, 0.11, 4) * 0.5), 'secret' => self::mix(0.6, fn ($t) => self::sine([659, 831, 988, 1319][min(3, (int)($t / 0.12))], $t) * self::env($t, 0.002, 0.58, 1) * 0.45), 'menu' => self::mix(0.06, fn ($t) => self::sq(660, $t) * self::env($t, 0.001, 0.055, 2) * 0.3), 'menuok' => self::mix(0.2, fn ($t) => self::sq($t < 0.07 ? 660 : 990, $t) * self::env($t, 0.001, 0.19, 1) * 0.3), ]; } private static float $lp = 0.0; private static function mix(float $seconds, callable $f, float $gain = 1.0): string { $n = (int)($seconds * self::RATE); $samples = []; self::$lp = 0.0; for ($i = 0; $i < $n; $i++) { $v = $f($i / self::RATE) * $gain; $samples[] = (int)max(-32767, min(32767, round(tanh($v * 1.2) * 30000))); } $data = pack('v*', ...array_map(fn (int $s) => $s & 0xFFFF, $samples)); return 'RIFF' . pack('V', 36 + strlen($data)) . 'WAVE' . 'fmt ' . pack('VvvVVvv', 16, 1, 1, self::RATE, self::RATE * 2, 2, 16) . 'data' . pack('V', strlen($data)) . $data; } /** Attack-decay envelope: 0..1. */ private static function env(float $t, float $attack, float $length, float $curve): float { if ($t < 0 || $t > $length) { return 0.0; } if ($t < $attack) { return $t / $attack; } return (1 - ($t - $attack) / max(0.0001, $length - $attack)) ** $curve; } private static function noise(): float { return Noise::rand() * 2 - 1; } /** Low-passed noise: rumble ($k 0..1 = how much of each new sample). */ private static function lowNoise(float $k): float { self::$lp += (self::noise() - self::$lp) * max(0.02, $k); return self::$lp * 1.6; } private static function sq(float $hz, float $t): float { return fmod(max(1.0, $hz) * $t, 1.0) < 0.5 ? 1.0 : -1.0; } private static function saw(float $hz, float $t): float { return fmod(max(1.0, $hz) * $t, 1.0) * 2 - 1; } private static function sine(float $hz, float $t): float { return sin(2 * M_PI * $hz * $t); }}