Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149"""nervox — make a regulated voice sound nervous again.
@jeffrey, 2026-08-19, on the loner lane: "i think wavering notes too /flanging and wiggling the pitches · would be nice · so the voice soundsmore nervous · lets call this 'nervox' technique · and canonize it in our/pop tooling setup".
THE PROBLEM IT SOLVES. Every WORLD lane here ends up snapping the singeronto a grid — cult's sing.py, loner's halo3, factory's chart.py. The snapis what makes her notes read as NOTES against a strict beat, and it isalso what makes her sound like a machine: a held tone whose f0 is astraight line does not occur in a human throat. Turning the snap downgets the humanity back by giving up the regulation, which is the wrongtrade. nervox keeps the regulation and puts the tremor back on top.
TWO PARTS, and they are different instruments:
waver() f0 modulation. NOT vibrato — vibrato is periodic and confident. This is three incommensurate rates beating against each other plus a smoothed random walk, so the pitch never repeats its own wobble. That irregularity is the whole effect; a single clean LFO reads as an opera singer, and these read as someone whose voice is not quite steady.
flange() a modulated short delay through the voice. Sweeping comb notches make the timbre itself unstable, which is the part you hear as nerves rather than as tuning.
TWO RULES, both learned the hard way on loner:
1. Waver HELD notes only. A slide is already moving; wobbling it makes mush. Pass `rate` (semitones/second, smoothed) and frames above GLIDE_ST_S are left alone — the same test that decides where the pitch snap lets go, so the two agree by construction. 2. Ramp in. A tremor present at the attack sounds like a broken sample; one that grows over ~120 ms sounds like a held note going unsteady, which is what a nervous singer actually does.
Deterministic: everything is seeded, so a render is reproducible and tworuns of the same score are byte-comparable.
from nervox import waver, flange f0 = waver(f0, frame_s, rate=rate) # cents of tremor on f0 x = flange(x, fs) # comb sweep on the audio"""
import numpy as np
# the same threshold the pitch snap uses to decide she is slidingGLIDE_ST_S = 18.0
WAVER_CENTS = 22.0 # depth of the tremor, cents peakWAVER_RATES = (4.3, 5.9, 7.1) # incommensurate, so the wobble never repeatsWAVER_DRIFT_HZ = 0.7 # how fast the random walk under it movesWAVER_RAMP_S = 0.120 # a tremor at the attack reads as a broken sample
FLANGE_MS = (0.6, 4.5) # sweep range of the delayFLANGE_HZ = 0.23 # the sweepFLANGE_DRIFT_HZ = 0.07 # …and a slower drift under it, so it never cyclesFLANGE_FB = 0.35FLANGE_MIX = 0.38
def _walk(n, rng, hz, frame_s): """A smoothed random walk in [-1, 1] — the part that will not repeat.""" if n <= 0: return np.zeros(0) step = max(1, int(round(1.0 / max(hz, 1e-6) / frame_s))) knots = rng.uniform(-1.0, 1.0, size=n // step + 2) w = np.interp(np.arange(n), np.arange(len(knots)) * step, knots) k = max(1, step // 2) return np.convolve(w, np.ones(k) / k, mode="same")
def waver(f0, frame_s, rate=None, cents=WAVER_CENTS, seed=0x10AE, voiced=None, ramp_s=WAVER_RAMP_S): """Put an unsteady tremor on a corrected f0 contour.
f0 Hz per frame, 0 where unvoiced rate |df0/dt| in semitones/second per frame; frames above GLIDE_ST_S are left alone (she is sliding, not holding) """ f0 = np.asarray(f0, dtype=float) n = len(f0) if n == 0: return f0 v = (f0 > 0) if voiced is None else np.asarray(voiced, dtype=bool)[:n] rng = np.random.default_rng(seed) t = np.arange(n) * frame_s
# three rates beating, each with its own slowly wandering depth trem = np.zeros(n) for i, hz in enumerate(WAVER_RATES): depth = 0.55 + 0.45 * _walk(n, rng, WAVER_DRIFT_HZ, frame_s) trem += depth * np.sin(2 * np.pi * hz * t + rng.uniform(0, 2 * np.pi)) trem /= len(WAVER_RATES) trem = 0.75 * trem + 0.25 * _walk(n, rng, WAVER_DRIFT_HZ * 1.6, frame_s)
# held notes only — a slide is already moving hold = np.ones(n) if rate is not None: r = np.asarray(rate, dtype=float)[:n] hold = np.clip(1.0 - (r - GLIDE_ST_S) / GLIDE_ST_S, 0.0, 1.0)
# …and ramp in from each voiced onset ramp = np.zeros(n) k = max(1, int(round(ramp_s / frame_s))) run = 0 for i in range(n): run = run + 1 if v[i] else 0 ramp[i] = min(1.0, run / k)
out = f0.copy() out[v] = f0[v] * 2.0 ** ((cents * trem * hold * ramp)[v] / 1200.0) return out
def flange(x, fs, depth_ms=FLANGE_MS, hz=FLANGE_HZ, fb=FLANGE_FB, mix=FLANGE_MIX, seed=0x11AE): """A modulated short delay — the timbre goes unstable, not the tuning.""" x = np.asarray(x, dtype=float) n = len(x) if n == 0: return x t = np.arange(n) / float(fs) lo, hi = depth_ms[0] / 1000.0, depth_ms[1] / 1000.0 rng = np.random.default_rng(seed) ph = rng.uniform(0, 2 * np.pi) sweep = 0.5 - 0.5 * np.cos(2 * np.pi * hz * t + ph) sweep = 0.78 * sweep + 0.22 * (0.5 - 0.5 * np.cos(2 * np.pi * FLANGE_DRIFT_HZ * t)) d = (lo + (hi - lo) * sweep) * fs
# linear-interpolated delay line with feedback, written in place so the # feedback path hears its own output the way a real flanger does y = np.zeros(n) buf = np.zeros(n) for i in range(n): di = d[i] j = i - di if j < 1: v = 0.0 else: j0 = int(j) fr = j - j0 v = buf[j0] * (1 - fr) + buf[j0 + 1] * fr if j0 + 1 < n else buf[j0] buf[i] = x[i] + fb * v y[i] = v return (1.0 - mix) * x + mix * y