Files
gods-eye-view/intro-sequence/audio/sfx.py
Claude 02e535cb55 feat(intro): v2 — cities, accelerating sweep, detections, thriller sound design
- City engine: Esri ground, OSM buildings that rise with the scan, road
  ribbons carrying modelled traffic coloured by congestion, NYC DOT cameras,
  real ADS-B, MBTA GTFS lines + realtime vehicles, LAX runways with approach
  strobes, Fort Bragg restricted volumes, Everest terrain with contours.
- Indexing keeps accelerating into a 260-capture global sweep while the
  planet whips a full turn.
- The eye opens: aperture blades, sensor array waking, optics calibration,
  radar sweep, orbiting coordinate ring.
- Camera wall with YOLOv8n detections and a city-pulse trace.
- Ends on GOD'S EYE VIEW / OMNISCIENCE.
- audio/sfx.py: sound design only, built to sit under a music track.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01KqP857pippaUs6konSDfe8
2026-10-19 01:10:39 +00:00

528 lines
21 KiB
Python

#!/usr/bin/env python3
"""God's Eye View — First Light (v2): sound design, built to sit under a music track.
No melody, no beeps. Sub, air, metal, impacts, swells, data texture — a tech
thriller palette that leaves the mids for the score. Everything is synthesised
and placed against audio/cues.json (exported from src/timeline.js).
python3 audio/sfx.py -> audio/sfx.wav (48 kHz, 24-bit stereo)
"""
import json
import os
import numpy as np
from scipy import signal
from scipy.io import wavfile
SR = 48000
HERE = os.path.dirname(os.path.abspath(__file__))
C = json.load(open(os.path.join(HERE, 'cues.json')))
T = C['T']
DUR = C['DURATION']
N = int(SR * DUR)
rng = np.random.default_rng(20261019)
B = {k: np.zeros((2, N)) for k in ('bed', 'hits', 'fx', 'ui', 'verb')}
def place(dst, sig, t, gain=1.0, pan=0.0, verb=0.0):
if sig.ndim == 1:
p = (pan + 1) * np.pi / 4
sig = np.vstack([sig * np.cos(p), sig * np.sin(p)])
i0 = int(round(t * SR))
if i0 >= N:
return
s0 = max(0, -i0)
i0 = max(0, i0)
n = min(sig.shape[1] - s0, N - i0)
if n <= 0:
return
seg = sig[:, s0:s0 + n] * gain
B[dst][:, i0:i0 + n] += seg
if verb > 0:
B['verb'][:, i0:i0 + n] += seg * verb
def tt(d):
return np.arange(int(d * SR)) / SR
def lp(fc, o=2):
return signal.butter(o, min(fc, SR * 0.45), 'low', fs=SR, output='sos')
def hp(fc, o=2):
return signal.butter(o, max(fc, 10), 'high', fs=SR, output='sos')
def bp(lo, hi, o=2):
lo = max(lo, 10)
hi = min(max(hi, lo * 1.05), SR * 0.45)
return signal.butter(o, [lo, hi], 'band', fs=SR, output='sos')
def F(x, sos):
return signal.sosfilt(sos, x, axis=-1)
def sweep(x, fn, kind='low', block=256):
y = np.zeros_like(x)
zi = None
for b in range(0, len(x), block):
fc = float(fn(b / SR))
sos = lp(fc) if kind == 'low' else hp(fc) if kind == 'high' else bp(fc * 0.7, fc * 1.35)
if zi is None:
zi = signal.sosfilt_zi(sos) * 0
y[b:b + block], zi = signal.sosfilt(sos, x[b:b + block], zi=zi)
return y
def noise(d):
return rng.standard_normal(int(d * SR))
def env_exp(d, k, a=0.002):
t = tt(d)
return np.minimum(1, t / max(a, 1e-4)) * np.exp(-t * k)
def env_ar(d, a, r, c=2.0):
t = tt(d)
return np.minimum(np.clip(t / max(a, 1e-4), 0, 1) ** c, np.clip((d - t) / max(r, 1e-4), 0, 1) ** c)
def sine(f, d, ph=0.0):
t = tt(d)
if np.isscalar(f):
return np.sin(2 * np.pi * f * t + ph)
return np.sin(2 * np.pi * np.cumsum(f) / SR + ph)
def saw(f, d):
t = tt(d)
ph = f * t if np.isscalar(f) else np.cumsum(f) / SR
return 2 * (ph - np.floor(ph + 0.5))
# ---------------------------------------------------------------- designed sounds
def tick(d=0.006, f=5000, q=0.6):
"""A dry, expensive-sounding UI tick: filtered noise, no pitch."""
return F(noise(d) * env_exp(d, 1200, 0.0002), bp(f * (1 - q / 2), f * (1 + q)))
def granule(f=6000):
d = 0.012 + rng.random() * 0.02
return F(noise(d) * env_ar(d, 0.002, d * 0.8), bp(f * 0.8, f * 1.25))
def sub_boom(d=3.0, f0=55, f1=24, k=1.4):
t = tt(d)
f = f1 + (f0 - f1) * np.exp(-t * 7)
return sine(f, d) * env_exp(d, k, 0.003)
def metal(d=3.0, base=180.0, k=1.2):
"""Inharmonic struck-metal resonance — the cinematic 'tail'."""
t = tt(d)
out = np.zeros(len(t))
for r, a in ((1.0, 1.0), (2.76, 0.6), (5.4, 0.45), (8.93, 0.3), (13.3, 0.2), (1.51, 0.5)):
out += a * np.sin(2 * np.pi * base * r * (1 + 0.002 * rng.standard_normal()) * t + rng.random() * 6) * np.exp(-t * k * (0.6 + r * 0.25))
return out / 3.0
def crack(d=0.4):
return F(noise(d) * env_exp(d, 18, 0.0005), bp(400, 7000))
def impact(size=1.0, dark=0.0, tail=3.5, base=150):
"""Trailer hit: sub drop + body thud + noise crack + metallic tail."""
d = tail + 0.5
s = sub_boom(d, 62, 22, 1.1) * 1.0
body = F(noise(0.5) * env_exp(0.5, 9, 0.001), lp(420)) * 0.9
out = s
out[: len(body)] += body
c = crack(0.45) * (0.5 - dark * 0.35)
out[: len(c)] += c
m = metal(d, base * (1 - dark * 0.3), 1.0) * 0.28
out += F(m, lp(4000 - dark * 2500))
return np.tanh(out * 1.4 * size) * 0.8
def braam(d=4.0, root=36.71, bright=1400, gain=1.0):
"""Low, distorted brass-like swell — filtered and dirty, not synthy."""
acc = np.zeros(int(d * SR))
for mult in (1, 1.5, 2, 3):
for det in (-0.01, 0, 0.012):
acc += saw(root * mult * (1 + det), d)
acc /= 10
y = sweep(acc, lambda tc: 120 + bright * min(1, tc / 0.09) * np.exp(-max(0, tc - 0.09) * 1.3))
y = np.tanh(y * 3.0) * env_ar(d, 0.015, d * 0.85, 1.2)
return y * gain
def whoosh(d=1.0, f0=300, f1=4000, peak=0.55, width=0.06):
x = noise(d)
y = sweep(x, lambda tc: f0 * (f1 / f0) ** min(1, tc / d), 'band')
t0 = tt(d)
return y * np.exp(-((t0 / d - peak) ** 2) / width)
def swell(d=1.5, f0=200, f1=6000):
"""Reverse-swell into a hit."""
x = noise(d)
y = sweep(x, lambda tc: f0 * (f1 / f0) ** ((tc / d) ** 1.8), 'band')
t0 = tt(d)
m = metal(d, 220, 0.2)[::-1] * 0.25
return (y + m) * (t0 / d) ** 2.6
def servo(d=0.6, f0=180, f1=420):
t0 = tt(d)
f = f0 + (f1 - f0) * np.sin(np.pi * t0 / d / 2)
x = saw(f, d) + 0.4 * saw(f * 2.01, d)
return F(x, bp(200, 2200)) * env_ar(d, 0.05, 0.12) * 0.5
def glitch(d=0.25):
"""Digital stutter: gated, bit-crushed noise and a buzzing carrier."""
x = noise(d)
gate = (np.sin(2 * np.pi * (35 + rng.random() * 40) * tt(d)) > 0).astype(float)
crushed = np.round(x * 4) / 4
buzz = np.sign(np.sin(2 * np.pi * (90 + rng.random() * 80) * tt(d))) * 0.3
return F((crushed * 0.6 + buzz) * gate, bp(300, 9000)) * env_ar(d, 0.002, 0.03)
def drone_bed(d, notes=(36.71, 55.0, 73.42), lpf=180):
t0 = tt(d)
x = sum(np.sin(2 * np.pi * f * (1 + 0.0008 * k) * t0 + k) * (0.6 / (k + 1)) for k, f in enumerate(notes))
x += F(noise(d), lp(lpf)) * 0.25
return x
def tension(d, base=55.0):
"""Dissonant cluster, heavily low-passed: dread, not tune."""
t0 = tt(d)
x = np.zeros(len(t0))
for r in (1.0, 1.059, 1.5, 2.12, 2.0):
x += saw(base * r * (1 + 0.003 * np.sin(2 * np.pi * 0.07 * t0 * r)), d)
x = F(x / 5, lp(380))
return x * (0.7 + 0.3 * np.sin(2 * np.pi * 0.11 * t0))
def rotor(d, rate=5.6):
t0 = tt(d)
thump = (0.5 + 0.5 * np.sin(2 * np.pi * rate * t0)) ** 6
x = F(noise(d), lp(220)) * thump * 1.6 + F(noise(d), bp(900, 3000)) * thump * 0.08
return x
def jet(d):
t0 = tt(d)
whine = sine(2400 + 120 * np.sin(2 * np.pi * 0.2 * t0), d) * 0.04 + sine(3600, d) * 0.015
roar = F(noise(d), bp(120, 900)) * 0.6
return roar + whine
def wind(d):
a = sweep(noise(d), lambda tc: 260 + 500 * (0.5 + 0.5 * np.sin(tc * 0.9)), 'band')
b = sweep(noise(d), lambda tc: 300 + 600 * (0.5 + 0.5 * np.sin(tc * 0.7 + 1.3)), 'band')
return np.vstack([a, b])
def squelch(d=0.35):
x = F(noise(d), bp(800, 4500)) * env_ar(d, 0.005, 0.06)
return x
def heartbeat(t, g=1.0):
for dt, gg, f0 in ((0.0, 1.0, 64), (0.19, 0.55, 54)):
d = 0.7
t0 = tt(d)
f = 30 + (f0 - 30) * np.exp(-t0 * 28)
place('hits', sine(f, d) * env_exp(d, 7.5, 0.002), t + dt, g * gg, verb=0.2)
# =================================================================== the film
t_all = tt(DUR)
# bed: sub + air, riding an intensity curve, silent through the cut
inten = np.interp(t_all, [0, 4, 9, 16, 30, 43, 44, 72, 76.6, T['cut'] - 0.01, T['cut'], T['title'], T['title'] + 3, DUR - 2.5, DUR],
[0, 0.25, 0.4, 0.55, 0.65, 0.7, 0.55, 0.65, 0.75, 1.0, 0, 0, 0.6, 0.5, 0])
bed = drone_bed(DUR) * inten
B['bed'] += np.vstack([bed, bed]) * 0.32
airbed = np.vstack([F(noise(DUR), bp(2500, 11000)), F(noise(DUR), bp(2500, 11000))]) * 0.012 * inten
B['bed'] += airbed
# tension cluster under the indexing and the cities
tz = tension(DUR) * np.interp(t_all, [16, 20, 30, 31, 43, 44, 70, 76, 84.4, 84.41], [0, 0.5, 0.6, 0.35, 0.45, 0.6, 0.7, 0.8, 1.0, 0], left=0, right=0)
B['bed'] += np.vstack([tz, tz * 0.95]) * 0.22
# ACT 0 — console
hum = (sine(100, DUR) * 0.25 + F(noise(DUR), bp(80, 300)) * 0.05) * np.interp(t_all, [0.4, 1.2, T['collapse'], T['seed']], [0, 1, 1, 0], left=0, right=0)
place('fx', hum, 0, 0.12)
for k in C['boot']:
if k['ch'] != ' ':
place('ui', tick(0.006, 4200 + rng.random() * 2500), k['t'], 0.16, pan=(rng.random() - 0.5) * 0.3)
place('fx', swell(T['seed'] - T['collapse'] + 0.05, 300, 9000), T['collapse'], 0.35)
place('fx', glitch(0.18), T['collapse'], 0.25)
place('hits', impact(0.7, dark=0.6, tail=3.0, base=110), T['seed'], 0.8, verb=0.35)
for bt in (T['beat1'], T['beat2']):
heartbeat(bt, 1.1)
# aperture: servos as the blades retract
place('fx', servo(1.2, 140, 360), T['aperture'], 0.22, -0.3, verb=0.2)
place('fx', servo(1.0, 160, 300), T['aperture'] + 0.25, 0.18, 0.3, verb=0.2)
for i in range(120):
place('ui', tick(0.004, 7000 if i % 10 else 4200), T['dishDraw'] + (i / 120) * (T['dishDone'] - T['dishDraw']), 0.05 if i % 10 else 0.1, pan=np.sin(i / 120 * 6.28) * 0.8)
# ACT 1 — growth: organic granular crackle densifying
gs, ge = T['growStart'], T['growEnd']
for _ in range(2400):
x = rng.random() ** 0.45
tc = gs + x * (ge - gs + 0.3)
k = (np.exp(3 * (tc - gs) / (ge - gs)) - 1) / (np.exp(3) - 1)
if rng.random() > 0.15 + 0.85 * k:
continue
place('fx', granule(2000 + rng.random() * 7000), tc, 0.05 + 0.08 * rng.random() * (0.4 + k), pan=rng.uniform(-0.9, 0.9))
# radar sweep: a slow low whoosh per revolution
sweep_period = 2 * np.pi / 2.4
for i in range(int((T['substrate'] - gs) / sweep_period) + 1):
place('fx', whoosh(sweep_period, 120, 900, 0.5, 0.12), gs + i * sweep_period, 0.08, pan=0.0)
place('fx', swell(1.6, 120, 5000), T['substrate'] - 1.6, 0.3)
place('hits', impact(0.9, 0.2, 4.0, 150), T['substrate'], 0.9, verb=0.35)
place('hits', braam(3.5, 36.71, 1100), T['substrate'], 0.35, verb=0.3)
# ACT 2 — indexing: tiles as data grains, captures as shutter clicks
for tl in C['tiles']:
if not tl['vis'] or tl['t'] > 23.5:
continue
if tl['z'] == 5 and rng.random() > 0.3:
continue
place('ui', granule(3000 + rng.random() * 5000), tl['t'], {3: 0.07, 4: 0.04, 5: 0.025}[tl['z']], pan=tl['pan'] * 0.8)
def shutter(t, g=1.0, pan=0.0):
place('fx', tick(0.008, 3800, 0.6), t, 0.45 * g, pan)
place('fx', tick(0.014, 2100, 0.6), t + 0.045, 0.35 * g, pan)
place('fx', F(noise(0.05) * env_exp(0.05, 60), lp(500)), t, 0.25 * g, pan)
for cp in C['caps']:
if cp['slow']:
shutter(cp['t'], 1.0, -0.3)
place('fx', servo(0.9, 220, 900), cp['t'] + 0.3, 0.12, -0.3, verb=0.2)
place('fx', whoosh(0.45, 900, 5000, 0.5), cp['t'] + 1.15, 0.16, 0.4)
else:
shutter(cp['t'], 0.5, 0.5)
# the flurry: shutters dissolving into a roar of capture
fl0, fl1 = T['flurry'], T['flurryEnd']
for i in range(420):
u = rng.random() ** 0.6
tc = fl0 + u * (fl1 - fl0)
shutter(tc, 0.18 + 0.25 * u, rng.uniform(-0.9, 0.9))
sw_d = fl1 - fl0
spin = whoosh(sw_d, 150, 3500, 0.62, 0.05)
place('fx', np.vstack([spin, np.roll(spin, 900)]), fl0, 0.35)
place('fx', swell(1.2, 200, 8000), T['indexed'] - 1.2, 0.35)
place('hits', impact(1.1, 0.0, 4.5, 170), T['indexed'], 1.0, verb=0.4)
place('hits', braam(4.0, 36.71, 1800), T['indexed'], 0.4, verb=0.3)
# ACT 3 — feeds: each a designed arrival, not a beep
def feed(key, t):
place('ui', tick(0.01, 3000), t, 0.25, -0.5)
place('fx', whoosh(0.5, 2500, 400, 0.3), t - 0.05, 0.12, -0.4)
if key == 'air':
for _ in range(380):
place('ui', tick(0.003, 6000 + rng.random() * 3000), t + rng.random() ** 0.7 * 1.6, 0.04 + rng.random() * 0.04, rng.uniform(-0.9, 0.9))
place('fx', jet(1.6) * env_ar(1.6, 0.3, 1.0), t, 0.25, 0.3)
elif key == 'sea':
p = sine(820 * np.exp(-tt(2.5) * 0.06), 2.5) * env_exp(2.5, 2.0)
place('fx', F(p, lp(2000)), t + 0.05, 0.12, 0.2, verb=0.9)
place('fx', F(noise(2.0), lp(300)) * env_ar(2.0, 0.6, 1.2), t, 0.25)
elif key == 'mil':
place('fx', squelch(0.3), t + 0.05, 0.25, 0.4)
place('fx', F(saw(48, 1.2), bp(60, 600)) * env_ar(1.2, 0.1, 0.8), t, 0.18)
elif key == 'orbit':
place('fx', whoosh(2.6, 2600, 160, 0.4, 0.1), t + 0.2, 0.3)
place('hits', sub_boom(2.0, 48, 24, 2.0), t + 0.1, 0.5, verb=0.3)
for _ in range(40):
place('ui', granule(7000 + rng.random() * 4000), t + rng.random() * 2.0, 0.03, rng.uniform(-1, 1), verb=0.6)
elif key == 'quake':
for j, dt in enumerate((0.05, 0.45, 0.95)):
place('hits', sub_boom(1.4, 50, 22, 3.0), t + dt, 0.6 - j * 0.12, verb=0.25)
place('fx', F(noise(2.4), lp(90)) * env_ar(2.4, 0.2, 1.6), t, 1.0)
elif key == 'fire':
for _ in range(700):
x = rng.random() ** 1.3 * 2.0
place('fx', granule(700 + rng.random() * 4000), t + x, (1 - x / 2) * 0.1 * rng.random(), rng.uniform(-0.8, 0.8))
place('fx', F(noise(2.0), bp(150, 1200)) * env_ar(2.0, 0.05, 1.5), t, 0.08)
elif key == 'wind':
place('fx', wind(2.6) * env_ar(2.6, 0.5, 1.5), t, 0.45)
elif key == 'cable':
buzz = F(saw(60, 1.5) + 0.5 * saw(120.4, 1.5), bp(300, 2500)) * env_ar(1.5, 0.02, 0.9)
place('fx', buzz, t, 0.05, 0.2)
place('fx', glitch(0.2), t, 0.12, -0.2)
for fd in C['feeds']:
feed(fd['key'], fd['t'])
for t0, d in ((31.6, 1.8), (33.6, 2.2), (35.0, 2.2)):
place('fx', whoosh(d, 200, 2400, 0.5), t0, 0.16, rng.uniform(-0.5, 0.5))
# ACT 4 — descent and the cities
place('fx', swell(1.4, 120, 9000), T['terrain'] - 1.4, 0.45)
place('fx', whoosh(1.2, 4000, 120, 0.7, 0.08), T['terrain'] - 1.0, 0.4)
place('hits', impact(1.0, 0.4, 3.5, 120), T['terrain'], 0.85, verb=0.4)
for c in C['cities']:
t0, t1 = c['t0'], c['t1']
d = t1 - t0
if c['slug'] != 'everest':
place('fx', glitch(0.22), t0 - 0.05, 0.35)
place('hits', impact(0.6, 0.5, 2.5, 140), t0, 0.5, verb=0.3)
place('fx', whoosh(0.6, 3000, 300, 0.25), t0 - 0.1, 0.25)
amb = F(noise(d), bp(60, 400)) * env_ar(d, 0.4, 0.4) * 0.5
place('fx', np.vstack([amb, np.roll(amb, 2000)]), t0, 0.25)
if c['slug'] == 'everest':
place('fx', wind(d) * env_ar(d, 0.6, 0.6), t0, 0.7)
# LiDAR sweep: a soft rising tone per pass
for i in range(int(d / (1 / 0.35 * 1.2 / 1.2)) + 1):
place('fx', F(sine(np.linspace(300, 1200, int(1.2 * SR)), 1.2), bp(200, 1600)) * env_ar(1.2, 0.3, 0.6), t0 + i * 2.86, 0.03, verb=0.4)
elif c['slug'] == 'nyc':
traffic = F(noise(d), bp(80, 700)) * env_ar(d, 0.8, 0.5)
place('fx', np.vstack([traffic, np.roll(traffic, 5000)]), t0, 0.22)
place('fx', rotor(d) * env_ar(d, 1.0, 1.0), t0 + 0.2, 0.3, 0.4)
for j in range(8):
place('fx', tick(0.01, 2400), t0 + 2.2 + j * 0.35, 0.18, 0.7)
place('fx', F(noise(0.1), bp(1500, 7000)) * env_exp(0.1, 25), t0 + 2.2 + j * 0.35, 0.05, 0.7)
elif c['slug'] == 'boston':
for j in range(4):
place('fx', F(noise(1.4), bp(100, 900)) * env_ar(1.4, 0.4, 0.8) * (1 + 0.3 * np.sin(2 * np.pi * 9 * tt(1.4))), t0 + 0.6 + j * 1.0, 0.1, rng.uniform(-0.7, 0.7))
elif c['slug'] == 'lax':
place('fx', jet(d) * env_ar(d, 0.8, 0.8), t0, 0.35, -0.3)
place('fx', whoosh(2.4, 200, 1400, 0.5, 0.1), t0 + 1.4, 0.3, 0.5)
elif c['slug'] == 'bragg':
place('fx', rotor(d, 4.6) * env_ar(d, 0.8, 0.8), t0 + 0.4, 0.25, -0.4)
for j in range(3):
place('fx', squelch(0.3), t0 + 0.8 + j * 1.3, 0.16, 0.5)
# the camera wall
wt = T['cctvWall']
place('hits', impact(0.9, 0.3, 3.0, 160), wt, 0.8, verb=0.3)
place('hits', braam(3.0, 36.71, 1400), wt, 0.3, verb=0.3)
for _ in range(100):
tc = wt + rng.random() ** 0.8 * 0.85 + rng.random() * 0.12
place('fx', tick(0.012, 2200 + rng.random() * 2000), tc, 0.14, rng.uniform(-0.95, 0.95))
place('fx', F(noise(0.08), bp(1500, 8000)) * env_exp(0.08, 30), tc, 0.03, rng.uniform(-0.95, 0.95))
wd = T['cctvOut'] - wt + 0.3
place('fx', np.vstack([F(noise(wd), bp(800, 6000)), F(noise(wd), bp(800, 6000))]) * env_ar(wd, 0.4, 0.25), wt, 0.035)
for j in range(int((T['cctvOut'] - wt - 0.8) * 7)):
place('ui', tick(0.006, 5200), wt + 0.8 + j / 7, 0.06, rng.uniform(-0.8, 0.8))
place('fx', whoosh(0.6, 5000, 150, 0.7), T['cctvOut'] - 0.1, 0.4)
place('hits', impact(0.9, 0.5, 3.0, 120), T['cctvOut'] + 0.4, 0.7, verb=0.35)
# ACT 5 — saturation: rising pressure, the breach, the state
sat0, cut = T['saturate'], T['cut']
sd = cut - sat0
t0s = tt(sd)
pos = 0.45 * t0s + 0.06 * t0s ** 2
sh = np.zeros(len(t0s))
for k in range(9):
o = (k + pos) % 9
a = np.exp(-((o - 4.5) ** 2) / (2 * 1.8 ** 2))
sh += np.sin(2 * np.pi * np.cumsum(30 * 2 ** o) / SR) * a
sh = F(sh / 9 * 2, lp(2200)) * env_ar(sd, 2.0, 0.01, 1.3)
place('fx', sh, sat0, 0.3, verb=0.2)
place('fx', swell(sd, 120, 9000), sat0, 0.3)
# pulse of low hits accelerating toward the breach
tk = sat0 + 0.5
gap = 0.9
while tk < T['dishBreak'] - 0.2:
place('hits', sub_boom(0.9, 58, 30, 4.0), tk, 0.35 + 0.35 * (tk - sat0) / sd)
place('fx', F(noise(0.12), bp(500, 4000)) * env_exp(0.12, 30), tk, 0.08)
tk += gap
gap = max(0.22, gap * 0.86)
for st in C['status']:
for j in range(16):
place('ui', tick(0.005, 4500 + rng.random() * 1500), st + j * 0.026, 0.1, 0.4)
for _ in range(300):
tc = T['twin'] + rng.random() ** 0.7 * (T['dishBreak'] - T['twin'])
place('ui', granule(5000 + rng.random() * 5000), tc, 0.025, rng.uniform(-1, 1), verb=0.3)
db = T['dishBreak']
place('hits', impact(1.25, 0.0, 5.0, 190), db, 1.1, verb=0.45)
place('hits', braam(3.0, 36.71, 2600), db, 0.45, verb=0.4)
for _ in range(200):
tc = db + rng.random() ** 2.2 * 1.1
f = 2500 + rng.random() * 7000
g = F(noise(0.25), bp(f * 0.97, f * 1.03)) * env_exp(0.25, 10 + rng.random() * 25)
place('fx', g, tc, 0.16 * (1 - (tc - db) / 1.2), rng.uniform(-1, 1), verb=0.3)
place('hits', impact(1.0, 0.3, 4.0, 110), T['omni'], 0.8, verb=0.45)
# ACT 6 — title: low bloom, the eye, the word
ti = T['title']
d = DUR - ti
warm = drone_bed(d, (36.71, 55.0, 73.42, 110.0), 140) * np.interp(tt(d), [0, 0.8, 2.0, d - 3.0, d], [0, 0.4, 0.7, 0.6, 0])
place('bed', warm, ti, 0.3)
place('fx', swell(0.9, 400, 7000), T['eyeOpen'] - 0.9, 0.2)
place('hits', impact(0.8, 0.6, 5.0, 90), T['eyeOpen'], 0.6, verb=0.5)
place('fx', metal(6.0, 220, 0.5) * 0.4, T['eyeOpen'], 0.12, verb=0.7)
for j in range(36):
place('ui', tick(0.006, 3500 + rng.random() * 4000), T['wordmark'] + j * 0.024, 0.07, rng.uniform(-0.4, 0.4))
place('hits', impact(1.0, 0.4, 6.5, 100), T['wordmark'] + 0.85, 0.8, verb=0.55)
place('hits', braam(6.0, 36.71, 900), T['wordmark'] + 0.85, 0.35, verb=0.5)
for j in range(11):
place('ui', tick(0.008, 4000), T['tagline'] + j * 0.08, 0.1)
place('fx', metal(6.0, 330, 0.45) * 0.35, T['tagline'] + 0.9, 0.1, verb=0.8)
# =================================================================== reverb, mix, master
def ir(rt=3.6, pre=0.03):
n = int((rt + pre) * SR)
t0 = np.arange(n) / SR
out = np.zeros((2, n))
for c in range(2):
x = rng.standard_normal(n) * np.exp(-6.9 * t0 / rt)
l2 = F(x, lp(2200))
m = np.clip(t0 / 1.2, 0, 1)
out[c] = x * (1 - m) + l2 * m
out[c, : int(pre * SR)] = 0
return out / np.sqrt(np.sum(out ** 2) / 2)
R = ir()
wet = np.vstack([signal.fftconvolve(B['verb'][c], R[c])[:N] for c in range(2)])
dip = np.ones(N)
for ht, depth in ((T['substrate'], 0.3), (T['indexed'], 0.25), (T['terrain'], 0.3), (T['cctvWall'], 0.4), (T['dishBreak'], 0.2), (T['wordmark'] + 0.85, 0.4)):
a, b, c = int((ht - 0.28) * SR), int((ht - 0.03) * SR), int((ht + 0.5) * SR)
dip[a:b] = np.minimum(dip[a:b], np.linspace(1, depth, b - a))
dip[b:c] = np.minimum(dip[b:c], np.linspace(depth, 1, c - b) ** 0.7)
mix = (B['bed'] + B['fx'] * 0.9 + B['ui'] * 0.8) * dip + B['hits'] * 1.2 + wet * 0.3
mix = F(mix, hp(22))
def stutter(x, a, b, sl=0.04):
ia, ib, s = int(a * SR), int(b * SR), int(sl * SR)
src = x[:, ia:ia + s].copy()
for i in range(ia, ib, s):
n = min(s, ib - i)
x[:, i:i + n] = src[:, :n] * (0.9 if ((i - ia) // s) % 2 == 0 else 0.55)
return x
mix = stutter(mix, cut - 0.22, cut)
ic, it = int(cut * SR), int(T['title'] * SR)
f = int(0.004 * SR)
mix[:, ic - f:ic] *= np.linspace(1, 0, f)
mix[:, ic:it] = 0
# gentle glue + ceiling
p = np.sqrt(np.convolve(np.mean(mix ** 2, axis=0), np.ones(4000) / 4000, mode='same') + 1e-12)
thr = 10 ** (-16 / 20)
g = np.where(p > thr, (thr * (p / thr) ** (1 / 1.8)) / p, 1.0)
g = signal.sosfiltfilt(lp(8), g)
mix *= g
mix = np.tanh(mix * 1.5) / 1.5
look = int(0.004 * SR)
pk = np.lib.stride_tricks.sliding_window_view(np.pad(np.max(np.abs(mix), axis=0), (0, look)), look + 1).max(axis=1)[:N]
ceil = 10 ** (-1.2 / 20)
lg = signal.sosfiltfilt(lp(60), np.minimum(1.0, ceil / np.maximum(pk, 1e-9)))
mix *= np.minimum(lg, 1.0)
mix *= ceil / max(1e-9, np.max(np.abs(mix)))
out = os.path.join(HERE, 'sfx.wav')
wavfile.write(out, SR, (mix.T * (2 ** 31 - 1)).astype(np.int32))
print('wrote', out, DUR, 's')