"""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) # 1. the shriek: inharmonic partials sweeping down with deepening vibrato 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) # swell in shriek *= np.exp(-0.55 * t) # decay # 2. sub rumble: slow 42-55 Hz wobble underneath everything 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 # 3. noise stabs at the start and mid-convulsion 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) # gentle distortion, glues layers fade = int(0.5 * _SR) mix[-fade:] *= np.linspace(1, 0, fade) # fade out mix = mix / np.max(np.abs(mix)) * 0.6 # moderate volume, no blasting 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() # Hollow's reply chime: a single detuned music-box note, one per turn, # rotating through a small minor-feel set so it never becomes a loop. _CHIME_FREQS = (659.3, 493.9, 784.0) # E5, B4, G5 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): # struck-tine partials, slightly inharmonic, fast decay 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)) # two oscillators a breath apart — the music box is out of tune note = tine(f) + tine(f * 1.006) note *= np.exp(-5.5 * t) # tine decay note *= np.clip(t / 0.004, 0, 1) # 4 ms attack, no click note = note / np.max(np.abs(note)) * 0.22 # quiet — plays every turn 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 # ~70 bpm 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) # lub sig += _thud(t, b0 + 0.16, 46, 0.7, 0.045) # dub 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) # breath: smoothed noise (a soft 'hhh') with a quick-in long-out envelope 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 # warm resolving tone: G3 + major third + fifth, swelling then fading 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)): # widening gaps 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) # slow breathing, exactly 2 cycles over the loop (continuous at the seam) breath = 0.8 + 0.2 * np.sin(2 * np.pi * (2.0 / dur) * t) # 1. sub (felt): 41 + 27.5 Hz (41*16, 27.5*16 are integers) sub = (np.sin(2 * np.pi * 41.0 * t) + 0.6 * np.sin(2 * np.pi * 27.5 * t)) * 0.16 * breath # 2. audible body: a hollow fifth in the low-mids (65.5/98/131 Hz + harmonics) 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 # 3. slow dissonant swell: two close tones beating (minor-second tension) swell_env = np.sin(np.pi * np.clip(t / dur, 0, 1)) ** 1.5 # 0 at both ends swell = (np.sin(2 * np.pi * 110.0 * t) + np.sin(2 * np.pi * 116.5 * t)) * 0.05 * swell_env # 4. distant toll ~every 5.3 s: struck tone, long decay, far and quiet 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) # room tail 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 # audible; no end-fade -> seamless 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) # faint detune + 0.2 * np.sin(2 * np.pi * 360 * t)) click = rng.standard_normal(len(t)) * 0.25 # a little grit at the onset env = np.exp(-t * 90) # fast percussive decay 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")