登录
ZH
games/doom-jumphp / tools / art / Sounds.php · commit 8e1cb9f
<?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);    }}