142 lines
4.3 KiB
Python
142 lines
4.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate crisp, pleasant retro/modern sound effects as 16-bit 44.1kHz mono WAVs."""
|
|
import math
|
|
import struct
|
|
import wave
|
|
from pathlib import Path
|
|
|
|
SAMPLE_RATE = 44100
|
|
OUT_DIR = Path(__file__).resolve().parent.parent / "assets" / "audio"
|
|
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
def write_wav(filename: str, samples: list[float]):
|
|
path = OUT_DIR / filename
|
|
with wave.open(str(path), "w") as wav:
|
|
wav.setnchannels(1)
|
|
wav.setsampwidth(2)
|
|
wav.setframerate(SAMPLE_RATE)
|
|
raw = bytearray()
|
|
for s in samples:
|
|
val = max(-1.0, min(1.0, s))
|
|
ival = int(val * 32767)
|
|
raw.extend(struct.pack("<h", ival))
|
|
wav.writeframes(raw)
|
|
print(f"Generated {path} ({len(samples)} samples, {len(samples)/SAMPLE_RATE:.2f}s)")
|
|
|
|
|
|
def gen_bell():
|
|
"""Resonant brass bell chime with multiple harmonics and long decay."""
|
|
duration = 1.6
|
|
n = int(SAMPLE_RATE * duration)
|
|
samples = []
|
|
f0 = 880.0 # A5
|
|
for i in range(n):
|
|
t = i / SAMPLE_RATE
|
|
env = math.exp(-3.5 * t)
|
|
# Bell acoustics: partials at 1.0, 2.0, 3.0, 4.2 with differing decay rates
|
|
s = (
|
|
math.sin(2 * math.pi * f0 * t) * 0.6
|
|
+ math.sin(2 * math.pi * f0 * 2.0 * t) * 0.3 * math.exp(-2.0 * t)
|
|
+ math.sin(2 * math.pi * f0 * 3.0 * t) * 0.15 * math.exp(-4.0 * t)
|
|
+ math.sin(2 * math.pi * f0 * 4.2 * t) * 0.1 * math.exp(-6.0 * t)
|
|
) * env
|
|
samples.append(s * 0.9)
|
|
write_wav("bell.wav", samples)
|
|
|
|
|
|
def gen_coin():
|
|
"""Bright two-tone pickup chime (B5 -> E6)."""
|
|
duration = 0.35
|
|
n = int(SAMPLE_RATE * duration)
|
|
samples = []
|
|
t_split = 0.08
|
|
for i in range(n):
|
|
t = i / SAMPLE_RATE
|
|
if t < t_split:
|
|
freq = 987.77 # B5
|
|
env = min(1.0, t / 0.005)
|
|
else:
|
|
freq = 1318.51 # E6
|
|
t_sub = t - t_split
|
|
env = math.exp(-9.0 * t_sub)
|
|
# Add a light sparkle harmonic
|
|
s = (math.sin(2 * math.pi * freq * t) + 0.25 * math.sin(4 * math.pi * freq * t)) * env
|
|
samples.append(s * 0.8)
|
|
write_wav("coin.wav", samples)
|
|
|
|
|
|
def gen_click():
|
|
"""Short crisp mechanical button click."""
|
|
duration = 0.05
|
|
n = int(SAMPLE_RATE * duration)
|
|
samples = []
|
|
for i in range(n):
|
|
t = i / SAMPLE_RATE
|
|
env = math.exp(-80.0 * t)
|
|
freq = 1200.0 - 800.0 * (t / duration)
|
|
s = math.sin(2 * math.pi * freq * t) * env
|
|
samples.append(s * 0.7)
|
|
write_wav("click.wav", samples)
|
|
|
|
|
|
def gen_chime():
|
|
"""Pleasant 3-note ascending arpeggio (C6, E6, G6)."""
|
|
duration = 0.8
|
|
n = int(SAMPLE_RATE * duration)
|
|
notes = [1046.5, 1318.5, 1567.98] # C6, E6, G6
|
|
step = 0.12
|
|
samples = []
|
|
for i in range(n):
|
|
t = i / SAMPLE_RATE
|
|
s_total = 0.0
|
|
for idx, freq in enumerate(notes):
|
|
t_start = idx * step
|
|
if t >= t_start:
|
|
t_note = t - t_start
|
|
env = math.exp(-6.0 * t_note)
|
|
s_total += math.sin(2 * math.pi * freq * t_note) * env * 0.35
|
|
samples.append(s_total)
|
|
write_wav("chime.wav", samples)
|
|
|
|
|
|
def gen_fanfare():
|
|
"""Triumphant short fanfare jingle (C5, G5, C6)."""
|
|
duration = 1.0
|
|
n = int(SAMPLE_RATE * duration)
|
|
notes = [(0.0, 0.15, 523.25), (0.15, 0.30, 783.99), (0.30, 0.95, 1046.5)]
|
|
samples = []
|
|
for i in range(n):
|
|
t = i / SAMPLE_RATE
|
|
s_total = 0.0
|
|
for t_start, t_end, freq in notes:
|
|
if t >= t_start:
|
|
t_rel = t - t_start
|
|
env = math.exp(-4.0 * t_rel) if t >= t_end - 0.05 else min(1.0, t_rel / 0.01)
|
|
s_total += (math.sin(2 * math.pi * freq * t_rel) + 0.2 * math.sin(4 * math.pi * freq * t_rel)) * env * 0.4
|
|
samples.append(s_total)
|
|
write_wav("fanfare.wav", samples)
|
|
|
|
|
|
def gen_door_open():
|
|
"""Low wooden mechanical slide/creak."""
|
|
duration = 0.6
|
|
n = int(SAMPLE_RATE * duration)
|
|
samples = []
|
|
for i in range(n):
|
|
t = i / SAMPLE_RATE
|
|
env = math.sin(math.pi * (t / duration)) ** 0.5
|
|
freq = 160.0 + 80.0 * math.sin(30.0 * t)
|
|
s = math.sin(2 * math.pi * freq * t) * env * 0.6
|
|
samples.append(s)
|
|
write_wav("door_open.wav", samples)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
gen_bell()
|
|
gen_coin()
|
|
gen_click()
|
|
gen_chime()
|
|
gen_fanfare()
|
|
gen_door_open()
|