| """The bad-finale horror sting, synthesized at import time (~50 ms): a |
| dissonant descending shriek over a sub rumble with noise stabs. ~3.2 s mono |
| WAV. Generated in code so no binary lives in the repo (HF rejects raw .wav). |
| Tune and listen: `python sting.py` writes sting_preview.wav.""" |
|
|
| import io |
| import wave |
|
|
| import numpy as np |
|
|
| _SR = 22050 |
| _DUR = 3.2 |
|
|
|
|
| def sting_wav_bytes() -> bytes: |
| t = np.linspace(0, _DUR, int(_SR * _DUR), endpoint=False) |
| rng = np.random.default_rng(13) |
|
|
| |
| sweep = 2200 * np.exp(-1.1 * t) + 380 |
| vibrato = 1 + 0.025 * np.sin(2 * np.pi * (5 + 3 * t) * t) |
| phase = 2 * np.pi * np.cumsum(sweep * vibrato) / _SR |
| shriek = (np.sin(phase) + 0.6 * np.sin(1.93 * phase) |
| + 0.4 * np.sin(2.71 * phase)) |
| shriek *= np.clip(t / 0.35, 0, 1) |
| shriek *= np.exp(-0.55 * t) |
|
|
| |
| wob = 48 + 7 * np.sin(2 * np.pi * 0.7 * t) |
| rumble = np.sin(2 * np.pi * np.cumsum(wob) / _SR) |
| rumble *= np.clip(t / 0.8, 0, 1) * 0.9 |
|
|
| |
| noise = rng.standard_normal(len(t)) |
| stab = np.zeros_like(t) |
| for start, decay in ((0.0, 18.0), (1.5, 22.0)): |
| env = np.where(t >= start, np.exp(-decay * (t - start)), 0) |
| stab += env |
|
|
| mix = 0.9 * shriek + 0.8 * rumble + 0.5 * noise * stab |
| mix = np.tanh(1.6 * mix) |
| fade = int(0.5 * _SR) |
| mix[-fade:] *= np.linspace(1, 0, fade) |
| mix = mix / np.max(np.abs(mix)) * 0.6 |
|
|
| buf = io.BytesIO() |
| with wave.open(buf, "wb") as w: |
| w.setnchannels(1) |
| w.setsampwidth(2) |
| w.setframerate(_SR) |
| w.writeframes((mix * 32767).astype(np.int16).tobytes()) |
| return buf.getvalue() |
|
|
|
|
| def _wav(mix: np.ndarray, sr: int) -> bytes: |
| buf = io.BytesIO() |
| with wave.open(buf, "wb") as w: |
| w.setnchannels(1) |
| w.setsampwidth(2) |
| w.setframerate(sr) |
| w.writeframes((mix * 32767).astype(np.int16).tobytes()) |
| return buf.getvalue() |
|
|
|
|
| |
| |
| _CHIME_FREQS = (659.3, 493.9, 784.0) |
|
|
|
|
| def chime_wav_bytes(idx: int = 0) -> bytes: |
| sr = 22050 |
| dur = 0.8 |
| t = np.linspace(0, dur, int(sr * dur), endpoint=False) |
| f = _CHIME_FREQS[idx % len(_CHIME_FREQS)] |
|
|
| def tine(freq): |
| |
| return (np.sin(2 * np.pi * freq * t) |
| + 0.35 * np.sin(2 * np.pi * 2.76 * freq * t) |
| + 0.12 * np.sin(2 * np.pi * 5.4 * freq * t)) |
|
|
| |
| note = tine(f) + tine(f * 1.006) |
| note *= np.exp(-5.5 * t) |
| note *= np.clip(t / 0.004, 0, 1) |
| note = note / np.max(np.abs(note)) * 0.22 |
| return _wav(note, sr) |
|
|
|
|
| def _thud(t, center, freq, amp, width): |
| """A single soft low drum hit centered at `center` seconds.""" |
| env = np.exp(-((t - center) ** 2) / (2 * width ** 2)) |
| return amp * np.sin(2 * np.pi * freq * t) * env |
|
|
|
|
| def heartbeat_wav_bytes() -> bytes: |
| """A loopable lub-dub heartbeat bed for the finale build. Exactly two |
| beats (1.7 s) so the HTML `loop` attribute repeats seamlessly.""" |
| sr = 22050 |
| beat = 0.85 |
| beats = 2 |
| dur = beat * beats |
| t = np.linspace(0, dur, int(sr * dur), endpoint=False) |
| sig = np.zeros_like(t) |
| for b in range(beats): |
| b0 = b * beat |
| sig += _thud(t, b0 + 0.00, 60, 1.0, 0.035) |
| sig += _thud(t, b0 + 0.16, 46, 0.7, 0.045) |
| sig = np.tanh(1.5 * sig) |
| sig = sig / np.max(np.abs(sig)) * 0.45 |
| return _wav(sig, sr) |
|
|
|
|
| def sigh_wav_bytes() -> bytes: |
| """Relief for the redemption climax: a soft filtered exhale over a warm |
| major drone that swells and resolves. ~2.6 s.""" |
| sr = 22050 |
| dur = 2.6 |
| t = np.linspace(0, dur, int(sr * dur), endpoint=False) |
| rng = np.random.default_rng(7) |
|
|
| |
| noise = rng.standard_normal(len(t)) |
| kernel = np.hanning(220) |
| kernel /= kernel.sum() |
| breath = np.convolve(noise, kernel, mode="same") |
| exhale = np.clip(t / 0.15, 0, 1) * np.exp(-1.6 * t) |
| breath *= exhale * 0.5 |
|
|
| |
| tone = (np.sin(2 * np.pi * 196.00 * t) |
| + 0.5 * np.sin(2 * np.pi * 246.94 * t) |
| + 0.4 * np.sin(2 * np.pi * 293.66 * t)) |
| swell = np.sin(np.pi * np.clip(t / dur, 0, 1)) |
| tone *= swell * 0.25 |
|
|
| mix = breath + tone |
| fade = int(0.3 * sr) |
| mix[-fade:] *= np.linspace(1, 0, fade) |
| mix = mix / np.max(np.abs(mix)) * 0.5 |
| return _wav(mix, sr) |
|
|
|
|
| def flatline_wav_bytes() -> bytes: |
| """The visitor-loop climax: three decelerating beats, then the line goes |
| flat — a sustained low tone that fades. Your heart stops being yours. ~4 s.""" |
| sr = 22050 |
| dur = 4.0 |
| t = np.linspace(0, dur, int(sr * dur), endpoint=False) |
| sig = np.zeros_like(t) |
| for i, c in enumerate((0.0, 0.95, 2.1)): |
| amp = 1.0 - 0.25 * i |
| sig += _thud(t, c, 58, amp, 0.035) |
| sig += _thud(t, c + 0.16, 46, 0.7 * amp, 0.045) |
|
|
| flat_start = 2.5 |
| flat = np.where(t >= flat_start, 0.18 * np.sin(2 * np.pi * 55 * t), 0.0) |
| flat *= np.clip((t - flat_start) / 0.2, 0, 1) |
| sig += flat |
|
|
| fade = int(0.6 * sr) |
| sig[-fade:] *= np.linspace(1, 0, fade) |
| sig = sig / np.max(np.abs(sig)) * 0.5 |
| return _wav(sig, sr) |
|
|
|
|
| def menu_loop_wav_bytes() -> bytes: |
| """Deep dread menu bed, made audible: a dark hollow-fifth body in the |
| low-mids (heard on any speaker) over a felt sub, with a slow dissonant |
| swell and a distant toll. Seamless loop — every sustained component is |
| integer-cycle over the 16 s loop, so no fade-dip is needed at the seam.""" |
| sr = 22050 |
| dur = 16.0 |
| t = np.linspace(0, dur, int(sr * dur), endpoint=False) |
|
|
| |
| breath = 0.8 + 0.2 * np.sin(2 * np.pi * (2.0 / dur) * t) |
|
|
| |
| sub = (np.sin(2 * np.pi * 41.0 * t) |
| + 0.6 * np.sin(2 * np.pi * 27.5 * t)) * 0.16 * breath |
|
|
| |
| def tone(f): |
| return (np.sin(2 * np.pi * f * t) |
| + 0.4 * np.sin(2 * np.pi * 2 * f * t) |
| + 0.18 * np.sin(2 * np.pi * 3 * f * t)) |
| body = (tone(65.5) + 0.8 * tone(98.0) + 0.5 * tone(131.0)) * 0.07 * breath |
|
|
| |
| swell_env = np.sin(np.pi * np.clip(t / dur, 0, 1)) ** 1.5 |
| swell = (np.sin(2 * np.pi * 110.0 * t) |
| + np.sin(2 * np.pi * 116.5 * t)) * 0.05 * swell_env |
|
|
| |
| toll = np.zeros_like(t) |
| for c in (1.0, 6.3, 11.6): |
| env = np.where(t >= c, np.exp(-1.8 * (t - c)), 0.0) |
| strike = (np.sin(2 * np.pi * 98.0 * t) |
| + 0.5 * np.sin(2 * np.pi * 147.0 * t) |
| + 0.25 * np.sin(2 * np.pi * 196.0 * t)) |
| toll += strike * env |
| echo = np.zeros_like(t) |
| for delay, gain in ((0.28, 0.30), (0.56, 0.15)): |
| ds = int(delay * sr) |
| echo[ds:] += toll[:-ds] * gain |
| toll = (toll + echo) * 0.14 |
|
|
| mix = sub + body + swell + toll |
| mix = np.tanh(1.5 * mix) |
| mix = mix / np.max(np.abs(mix)) * 0.5 |
| return _wav(mix, sr) |
|
|
|
|
| def type_tick_wav_bytes() -> bytes: |
| """A short dry tick for the intro typewriter: a low ~180 Hz burst with a |
| fast decay and a touch of noise, faintly detuned. Synthesized like the |
| other stings (HF rejects committed audio binaries).""" |
| sr = 22050 |
| dur = 0.035 |
| t = np.linspace(0, dur, int(sr * dur), endpoint=False) |
| rng = np.random.default_rng(7) |
|
|
| body = (np.sin(2 * np.pi * 180 * t) |
| + 0.4 * np.sin(2 * np.pi * 181.2 * t) |
| + 0.2 * np.sin(2 * np.pi * 360 * t)) |
| click = rng.standard_normal(len(t)) * 0.25 |
| env = np.exp(-t * 90) |
| mix = (body + click) * env |
|
|
| mix = mix / np.max(np.abs(mix)) * 0.5 |
| return _wav(mix, sr) |
|
|
|
|
| if __name__ == "__main__": |
| from pathlib import Path |
| here = Path(__file__).parent |
| (here / "sting_preview.wav").write_bytes(sting_wav_bytes()) |
| for i in range(3): |
| (here / f"chime_preview_{i}.wav").write_bytes(chime_wav_bytes(i)) |
| (here / "heartbeat_preview.wav").write_bytes(heartbeat_wav_bytes()) |
| (here / "sigh_preview.wav").write_bytes(sigh_wav_bytes()) |
| (here / "flatline_preview.wav").write_bytes(flatline_wav_bytes()) |
| (here / "menu_loop_preview.wav").write_bytes(menu_loop_wav_bytes()) |
| (here / "type_tick_preview.wav").write_bytes(type_tick_wav_bytes()) |
| print("written sting_preview.wav + chime_preview_0/1/2.wav + menu_loop_preview.wav + type_tick_preview.wav") |
|
|