Initial commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shweetandClaude Opus 5.5 committed 2026-11-08 01:30:33 -05:00
commit 2007fb7edc
200 files changed
+27710

No files matched your search

+214
View File
@@ -0,0 +1,214 @@
# Candidates for the sounds the review is still choosing, and the chip-style synth recipes some of them use.
# Each candidate is a pick in tools/sfx_build.py's form (a layer, or a list of layers), so it's auditioned crunched
# exactly as the game's sound would be, and choosing it in the review means copying it into PICKS. A layer is either
# cut from a library track ({"f": ..., "s": ...}) or synthesized here ({"synth": recipe, "a": {arguments}}).
import math
import numpy as np
RATE = 44100
def time_axis(seconds):
return np.arange(int(RATE * seconds)) / RATE
def envelope(t, attack, power):
rise = np.clip(t / max(attack, 1e-4), 0, 1)
return rise * (1 - t / max(t[-1], 1e-9)) ** power
def glide(t, start, end):
# Exponential, so it sweeps evenly by ear.
return start * (end / start) ** (t / max(t[-1], 1e-9))
def phase(frequency):
return 2 * math.pi * np.cumsum(frequency) / RATE
def pulse(angle, duty=0.5):
return np.where((angle / (2 * math.pi)) % 1 < duty, 1.0, -1.0)
def noise(count, seed):
return np.random.default_rng(seed).uniform(-1, 1, count)
def lowpass(signal, amount):
amounts = np.broadcast_to(amount, signal.shape)
out = np.zeros_like(signal)
value = 0.0
for i, sample in enumerate(signal):
value += (sample - value) * amounts[i]
out[i] = value
return out
def at(signal, part, seconds):
start = int(seconds * RATE)
end = min(len(signal), start + len(part))
signal[start:end] += part[:end - start]
return signal
# A pulse-wave laser: a fast falling sweep, optionally with an octave above and a wobble.
def pew(start=1600, end=200, seconds=0.14, duty=0.5, octave=0.0, vibrato=0.0):
t = time_axis(seconds)
frequency = glide(t, start, end) * (1 + 0.08 * np.sin(2 * math.pi * vibrato * t))
tone = pulse(phase(frequency), duty) + octave * pulse(phase(frequency * 2), duty)
return 0.5 * tone * envelope(t, 0.002, 1.3)
# Notes one after another (Hz), on a pulse wave.
def arp(notes=(523, 659, 784, 1047), seconds=0.3, duty=0.5):
t = time_axis(seconds)
step = len(t) / len(notes)
frequency = np.array(notes, dtype=float)[np.minimum((np.arange(len(t)) / step).astype(int), len(notes) - 1)]
return 0.45 * pulse(phase(frequency), duty) * envelope(t, 0.002, 0.8)
def slide(start=400, end=1200, seconds=0.3, duty=0.5, vibrato=0.0):
t = time_axis(seconds)
frequency = glide(t, start, end) * (1 + 0.06 * np.sin(2 * math.pi * vibrato * t))
return 0.45 * pulse(phase(frequency), duty) * envelope(t, 0.004, 1)
def beeps(frequency=1800, count=2, length=0.045, gap=0.07, duty=0.5):
t = time_axis(length)
beep = 0.5 * pulse(2 * math.pi * frequency * t, duty) * envelope(t, 0.001, 0.4)
signal = np.zeros(int(RATE * (gap * (count - 1) + length)) + 1)
for i in range(count):
at(signal, beep, i * gap)
return signal
# Short blips repeating and fading, like an echo.
def echo_blips(frequency=1400, repeats=3, gap=0.07, duty=0.25):
t = time_axis(0.05)
blip = pulse(phase(glide(t, frequency, frequency * 0.7)), duty) * envelope(t, 0.001, 1.5)
signal = np.zeros(int(RATE * (gap * repeats + 0.05)) + 1)
for i in range(repeats):
at(signal, 0.5 * blip * 0.55 ** i, i * gap)
return signal
def shimmer(frequency=1400, rate=22, seconds=0.28, duty=0.5):
t = time_axis(seconds)
tone = pulse(2 * math.pi * frequency * t, duty) * (0.6 + 0.4 * np.sin(2 * math.pi * rate * t))
return 0.4 * tone * envelope(t, 0.005, 2)
# A ring-modulated chirp: metallic, bell-ish and odd.
def ring_chirp(start=1800, end=900, seconds=0.22, ring=330):
t = time_axis(seconds)
return 0.6 * np.sin(phase(glide(t, start, end))) * np.sin(2 * math.pi * ring * t) * envelope(t, 0.002, 1.5)
# Rising, wobbling tone: a warp gathering or a charge building.
def warp(start=300, end=1500, seconds=0.38, wobble=24, duty=0.5, depth=0.5):
t = time_axis(seconds)
tone = pulse(phase(glide(t, start, end)), duty) * (1 + depth * np.sin(2 * math.pi * wobble * t))
return 0.3 * tone * np.sin(math.pi * t / t[-1]) ** 0.5
def charge(start=180, end=520, seconds=0.8, duty=0.5, shimmer=12, rumble=0.0):
t = time_axis(seconds)
tone = pulse(phase(glide(t, start, end)), duty) * (0.75 + 0.25 * np.sin(2 * math.pi * shimmer * t))
low = rumble * lowpass(noise(len(t), 3), 0.03) * 4
return 0.3 * (tone + low) * np.clip(t / 0.12, 0, 1)
# Noise swept from dark to bright and back, with a rising roar under it: a big whoosh.
def whoosh(seconds=0.45, low=0.03, high=0.5, seed=62, roar=0.6, roarStart=90, roarEnd=260, drive=1.8):
t = time_axis(seconds)
sweep = lowpass(noise(len(t), seed), np.linspace(low, high, len(t))) * np.sin(math.pi * t / t[-1]) ** 0.6
tone = np.sin(phase(glide(t, roarStart, roarEnd))) * envelope(t, 0.01, 2.5)
return 0.95 * np.tanh(drive * (1.4 * sweep + roar * tone))
# Embers: a soft hiss swelling up with crackling pops scattered through it.
def ember(seconds=0.4, seed=7, density=60, hiss=0.5, swell=0.3):
t = time_axis(seconds)
rng = np.random.default_rng(seed)
body = hiss * lowpass(noise(len(t), seed), 0.12) * np.clip(t / max(swell * seconds, 1e-3), 0, 1)
pops = np.zeros(len(t))
for start in rng.uniform(0, seconds * 0.9, int(density * seconds)):
size = int(rng.uniform(0.001, 0.004) * RATE)
at(pops, rng.uniform(0.3, 1) * noise(size, int(start * 1e6)) * np.linspace(1, 0, size), start)
return 0.9 * (body + pops) * envelope(t, 0.02, 0.8)
# Sparkles: quick high pings at random pitches over a crackle, with an optional thump for weight.
def sparkle(seconds=0.15, seed=11, density=90, low=2000, high=5000, crackle=0.5, thump=0.0):
t = time_axis(seconds)
rng = np.random.default_rng(seed)
signal = np.zeros(len(t))
for start in rng.uniform(0, seconds * 0.8, max(3, int(density * seconds))):
ping = time_axis(rng.uniform(0.015, 0.04))
at(signal, 0.5 * np.sin(2 * math.pi * rng.uniform(low, high) * ping) * envelope(ping, 0.0005, 2), start)
signal += crackle * np.where(rng.random(len(t)) < 0.02, rng.uniform(-1, 1, len(t)), 0)
signal += thump * np.sin(phase(glide(t, 180, 60))) * envelope(t, 0.001, 3)
return signal * envelope(t, 0.001, 1.2)
# A dull, low thud: a falling sine with a muffled noise body.
def thud(start=140, end=45, seconds=0.35, body=0.6, seed=9):
t = time_axis(seconds)
boom = np.sin(phase(glide(t, start, end))) * envelope(t, 0.002, 2)
return 0.8 * np.tanh(1.5 * (boom + body * lowpass(noise(len(t), seed), 0.04) * 4 * envelope(t, 0.002, 3)))
# An arrival pop: a falling thump and a burst, with a sparkle over it.
def pop(start=500, end=90, seconds=0.3, glitter=0.4):
t = time_axis(seconds)
body = np.sin(phase(glide(t, start, end))) * envelope(t, 0.001, 4) + 0.7 * lowpass(noise(len(t), 91), 0.25) * envelope(t, 0.0005, 6)
shine = glitter * (np.sin(2 * math.pi * 2200 * t) + 0.5 * np.sin(2 * math.pi * 3300 * t)) * envelope(t, 0.001, 7)
return 0.8 * np.tanh(body + shine)
def bell(frequency=1320, seconds=0.45):
t = time_axis(seconds)
tone = sum(np.sin(2 * math.pi * frequency * ratio * t) * weight for ratio, weight in [(1, 1), (1.5, 0.6), (2, 0.25), (2.76, 0.15)])
return 0.35 * tone * envelope(t, 0.002, 3)
# A plucked string (Karplus-Strong): a burst of noise looping through a delay the length of one period, softened each
# pass, so it rings at the string's pitch and dulls as it dies.
def pluck(frequency=180, seconds=0.3, damping=0.996):
count = int(RATE * seconds)
period = max(2, int(RATE / frequency))
buffer = list(noise(period, 21))
out = np.zeros(count)
for i in range(count):
first = buffer.pop(0)
out[i] = first
buffer.append(damping * 0.5 * (first + buffer[0]))
return 0.9 * out
# The first synthesized mine: a clunk with a short beep after it.
def mine_clunk():
t = time_axis(0.12)
clunk = np.sin(phase(glide(t, 220, 120))) * envelope(t, 0.001, 8) + lowpass(noise(len(t), 32), 0.5) * envelope(t, 0.001, 14)
return 0.6 * clunk
SYNTHS = {function.__name__: function for function in
[pew, arp, slide, beeps, echo_blips, shimmer, ring_chirp, warp, charge, whoosh, ember, sparkle, thud, pop,
bell, pluck, mine_clunk]}
def L(f, s, n, **options):
return {"f": f, "s": s, "n": n, **options}
def S(recipe, **arguments):
options = {key: arguments.pop(key) for key in ["g", "at", "r"] if key in arguments}
return {"synth": recipe, "a": arguments, **options}
# For each sound still being chosen: candidates as (label, pick), the first being what the game uses meanwhile. All
# chosen for now; a new review round puts the open sounds' candidates here.
SUITES = {}