Monorepo for Aesthetic.Computer aesthetic.computer
core pop loner
11 folders · 4 files

analysis
vox
vox3
harvest.json
survey.json
README.md

pop / loner — "whistlegraph loner --- remix" #

A remix of lonr — "Loner", composed by Camille Klein (@cksuperstore), the emo whistlegraph. posts.json tags nineteen posts with the work — 13.8M views on the biggest — and the whole song is one sung sentence:

sitting curled up in myself, i think of a stone, just waiting very patiently for time to pass

Where cult got a club record, loner gets a bedroom ballad: the voice close and forward, tape-warm pads, soft-attack bass, brushed-adjacent percussion that mostly isn't there, and a dotted-eighth delay doing the work a reverb would. v2 is the current cut: out/loner-remix-v2.mp3.

cut file what it is
v1 out/loner-remix-v1.mp3 2:51. Bedroom ballad, raw takes placed. Kept for reference.
v2 out/loner-remix-v2.mp3 v1 through the aesthetivox — every vocal a WORLD render.
v3 out/loner-remix-v3.mp3 2:39. Angelic drum & bass: solo Camille haloed, harped and chopped over a synthesized two-step. out/loner-remix.mp3 is this cut.

The takes #

Four of the nineteen feed v1 (fetched from the assets mirror by bin/slice.mjs; whisper.cpp + per-word librosa.pyin receipts in harvest.json):

prefix post what it is
f- 7108062006980201771 Ten Whistlegraphs / Feral File — the spine. The whole lyric, one clean unaccompanied voice, ~80 BPM
n- 7021262898479549702 the 13.8M "not again!" take — spoken "Camille, are you doing emo whistlegraphs again?", the sung answer, and "i knew it" at the end
o- 6988619239657622790 the origin take — spoken "Here's a whistlegraph by Camille called loner. Ready?"
s- 6988954628167585030 a lower-register solo take — sliced, unused in v1 (it sits ~a third below the others and would need repitching)

No whistling anywhere: a pyin scan of the 500–3000 Hz band over all seven downloaded takes found nothing. Like cult, loner is sung.

What the source gave us #

The Feral File take sits in A# minor about +30 cents sharp of A440 — tonic hits at ~237 Hz against A#3's 233 — so the whole accompaniment tunes to TONIC = 237 Hz and never consults concert pitch. The melody, per-word (receipt in harvest.json):

words note degree
sitting F4 5
curled up in C#4 → A#3 ♭3 → 1
myself D#4 4
i think G#3 ♭7, below
of a A#4 the octave — the one loud feeling in the lyric
stone ~E4 a blue note drifting between 4 and #4
just waiting C4 → C#4 2 → ♭3
very patiently A#3 → D#4 1 → 4
for time D#4 → F4 4 → 5
to pass C#4 ♭3

The chords (i / VI / III / VII / iv — Bbm, Gb, Db, Ab, Ebm) were chosen so every measured word pitch lands as chord colour: "just waiting"'s C4 is the maj7 of Db, "i think"'s G#3 is the 9th of Gb, and "stone"'s drift always happens over VI, where it reads as 6th/♭7 and never fights the tonic.

v1 — the form #

56 bars at 80 BPM (the take's own tempo), 2:48 + tail:

act at bars what happens
I · TAPE 0:00 0–4 hiss, a low drone, and the spoken introduction
II · VERSE 0:12 4–14 the Feral File take at its own rubato — bass and pad only; a hat tick doesn't dare enter until bar 9
III · DRIFT 0:42 14–18 instrumental breath; "stone" echoes dark from across the room; a music box answers
IV · VERSE AGAIN 0:54 18–28 the "not again!" take sings the same sentence — its spoken question opens the act, tucked low — over a soft thump
V · OF A STONE 1:24 28–36 the lifted middle: both takes' octave leap answer each other over the warmest pads, a shimmer an octave up
VI · THINNING 1:48 36–44 the back half of the sentence returns while the room empties, one element per bar
VII · BREATHE 2:12 44–53 unaccompanied. The whole original take, alone with the hiss. Then, spoken: "i knew it."
VIII · OUT 2:39 53–56 the delay finishes the sentence

Sound design, in one paragraph: the pads are detuned sine pairs under a one-pole lid with a shared wow LFO (±4 cents at 0.38 Hz — cassette transport, and it's one LFO so the whole bed leans together); the bass is cult's sine-bump with the attack opened to 45 ms; the thump is cult's kick with everything punchy removed (no transient blips, drive 1.3, a felt-mallet 190 Hz touch); the brushes are band-passed noise that swells into downbeats without a snare anywhere; the hiss is a mix element that breathes with the sections, loudest when the music is gone. Sidechain depth is 0.18 — a breath, nothing pumps. Every cult mixing rule that still applies survives: raised-cosine tails everywhere, no master tanh, one linear trim, mono-safe pans with a band-limited antisymmetric side return.

Run it #

node pop/loner/bin/slice.mjs            # fetch takes + cut the bank (20 slices)
node pop/loner/bin/render.mjs           # score → out/loner-remix-v1-full.wav
bash pop/loner/bin/cut-v1.sh            # measure → one static dB → limit → mp3
node pop/loner/bin/render.mjs --stems   # per-bus stems, for balance checks

Analysis (needs pop/.venv — see pop/cult/README.md for the recipe):

pop/.venv/bin/python pop/loner/bin/survey.py    # first look at all takes
pop/.venv/bin/python pop/loner/bin/analyze.py   # per-word pyin → harvest.json

cut-v1.sh follows the cult mastering law — MEASURE → one static dB → true-peak limiter, never a second loudnorm — with one deliberate difference: the target is −16 LUFS, not −14. A ballad keeps its dynamics; the render measured −15.1 raw, so mastering is a −0.77 dB trim and a limiter that has almost nothing to do.

v2 — the aesthetivox #

The rule, established on the cult lane the same day (memory feedback_vocals_always_aesthetivox): vocals never skip the aesthetivox — an exposed unprocessed take is too raw to ship. v1 deliberately featured raw takes; v2 keeps v1's entire arrangement and fixes exactly that. bin/aesthetivox.py puts all sixteen slices the score plays through the WORLD chain (harvest → stonemask → cheaptrick → d4c → resynthesize), and bin/render2.mjs is v1's score reading vox/ instead of samples/.

Because loner's charm is the rubato, the sung takes are not re-scored to a melody the way cult/bin/sing.py does it — they get the pop_world_autotune move instead: the f0 contour is pulled toward the nearest scale tone at strength 0.7, and the correction (never the pitch) is smoothed over 45 ms, so slides stay slides and vibrato keeps its shape while the centre of every held tone comes home. Plus longdots.sh's ballad-gentle +1.6 dB singer's formant (not sing.py's 3.2 default). Consonants stay real: f0 runs continuous through unvoiced gaps into the synth, and the original take's unvoiced audio is composited back over 5 ms seams — pitchsnap_world's two survival tricks.

The tuning decision (the brief's option b): the band keeps TONIC = 237 Hz and the +30¢ offset lives in the aesthetivox targets — the correction grid is A# natural minor in the take's own frame, never A440's. Rationale: the band already tunes to Camille; correcting her toward her own centre changes her the least.

The spoken asides survive only as treated material: range compressed toward the median (^0.55), dropped a semitone, envelope darkened above ~3.5 kHz — still them, no longer raw speech. BREATHE keeps its nakedness: the naked thing is now the WORLD render of the whole take.

Receipt: vox/.manifest.json — per slice, mode and pyin median |cents-to-grid| before → after. The Feral phrases read ~10¢ off-grid raw → on-grid after; the "not again!" phrases ~20–30¢ → ~10¢ (strength 0.7 leaves a residual on purpose; pyin's 10¢ resolution quantizes the readout).

pop/.venv/bin/python pop/loner/bin/aesthetivox.py   # samples/ → vox/ (16 WORLD renders)
node pop/loner/bin/render2.mjs                      # → out/loner-remix-v2-full.wav
bash pop/loner/bin/cut-v2.sh                        # → out/loner-remix-v2.mp3

v3 — angelic drum & bass #

@jeffrey, in order: "for camilles we need to like make her vocals more angelic" · "more arpeggiating within them" · "no alex voice — this track is all solo camille" · "i want a kick in it — sitting kick ick waiting kick cik" · "lets start splitting it up — making it more almost like a drum and bass". All five, in one cut, with v2's rules intact: every vocal object is a WORLD render (bin/halo.py, receipt vox3/.manifest.json), tuning stays TONIC = 237 Hz.

  • The halo. Behind every lead, the same slice doubled an octave up — f0 × 2 with the cheaptrick envelope untouched, so it is her head voice, not a chipmunk — two copies at +6/−7 ¢, 28/41 ms late, darker (5.5 kHz tilt), 1.5× breathier, vowels-only (consonants muted so the stack can't smear), panned ±0.55, reverb send 0.55–0.75. Diatonic 3rd/5th renders swell in on the held phrase-ends ("myself…", "stone…", "pass…") with 0.5–0.8 s attacks — the interval computed per frame from her corrected contour, so the harmony carries her rubato. Leads get AIR (+2.5 dB above 8 kHz in the envelope) and BREATH (aperiodicity +0.14 in the same band, ramped in 150 ms into each voiced run — held vowels only, never consonants).
  • The harp. Vowel excerpts ("oh" from stone, "ah" from pass) re-sung flat at grid tones st +12…+27 (12 ¢ / 5.2 Hz vibrato), run 4–7 notes at 0.12–0.30 s in chord-tone shapes — dotArp()'s move from cult render10, built from WORLD notes of Camille.
  • Solo Camille. The origin/ensemble takes and all three spoken asides are gone.
  • The kick between her syllables. In the verse the kicks sit at the harvest's word boundaries — sitting·KICK·ick, waiting·KICK·cik — and the "ick"s are real: 8 tiny chops of her WORLD leads (forward and reversed) bounce off the kicks as percussion.
  • The two-step. Drums think 160 over the 80 BPM floor: kick steps 0·10 (+6 every 4th bar), snare 4·12 with ghosts and a roll into every 4th bar of drop 2, velocity-shaped 16th ticks, a 12 ms-attack sub on steps 0/6/10 with a fifth pickup. All synthesized — no sample packs.
  • The space. The vox bus gets a decorrelated Schroeder pair: 4 combs per side (44.6–54.2 ms, right offsets +23…+37 samples), RT60 ≈ 3.2 s with a 3.4 kHz one-pole damp inside the loop, two series allpasses per side (g 0.7), 40 ms pre-delay, 180 Hz high-passed return at 0.34 — under the lead, hotter under the halos. Worst-window mono fold-down is −1.05 dB, and it lands on the intro, where the halo is deliberately all side.

Form: INTRO (halo with no lead inside it) → VERSE (syllable kicks) → BUILD → DROP 1 → BREATHE (the whole take, naked but haloed) → REBUILD → DROP 2 → OUT. 52 bars, 2:39.

pop/.venv/bin/python pop/loner/bin/halo.py   # samples/ → vox3/ (66 WORLD renders)
node pop/loner/bin/render3.mjs               # → out/loner-remix-v3-full.wav
bash pop/loner/bin/cut-v3.sh                 # → out/loner-remix-v3.mp3

v4 — regulated, immediate (WIP) #

@jeffrey, in order (2026-08-17): "i want the lyrics starting right away" · "and more musical notes" · "some words directly seem to just cut off" · "we need a smoother time stretching so we dont cut off her words" · "i wish our musical notes were the same as her voice" · "we could give her a backup vocal to match the words" · "i guess i want that world snapping for camille / that regulation". Backup voice decision: Camille herself — the solo rule holds.

Six moves, one cut:

  1. NOW. No 12-second overture. Her first word lands inside bar 0 — at most a half-bar of hiss/drone as a pickup breath. Everything v3 put in INTRO (the empty halo, the forming arp) happens under the first phrase instead of before it.
  2. THE REGULATION. The aesthetivox snap comes up from 0.7 to ~0.92 on the sung leads: her notes become NOTES — dead on the A#-minor grid in her own 237 Hz frame — while the 45 ms correction smoothing still lets slides be slides. Her rubato survives in time; the drift goes away in pitch.
  3. HER MELODY IS THE MELODY. A plucked lead (music-box register) doubles the harvest's per-word pitches — F4 sitting, C#4→A#3 curled up in, D#4 myself, G#3 i think, A#4 of a, the E4 stone-drift, C4→C#4 just waiting, A#3→D#4 very patiently, D#4→F4 for time, C#4 to pass — in sync under her words, and answers with the same line in the instrumental gaps. The arps re-voice toward melody tones. More notes everywhere, and every one of them is hers.
  4. NO WORD LEFT CUT. The halo2 bank finally gets played: the held words that v3's slices clipped resolve into the *-long family (sing.py's frame-axis warp — 1:1 through the onset, the vowel absorbing the hold for seconds, shimmer + late vibrato, 1:1 release) instead of a 10 ms fade mid-phoneme. Any slice whose last word still rings past its boundary gets re-cut with a real tail.
  5. THE BACKUP SINGER IS ALSO CAMILLE. Full-word WORLD renders (not vowels-only) at the diatonic 3rd/5th below, singing the whole lyric with her, tucked −10 dB under the lead, darker, wetter — a backing line that matches the words, where v3's swells only matched the vowels. The ens-* crowd swells rise behind the drops.
  6. SMOOTHER STRETCH. Wherever a word must fit a slot, the stretch is WORLD frame-warp (phonemes lengthen from inside), never a truncation, never a granular smear.

Then the frame changed mid-carve — "i wanna go from bedroom ballad to nice dance track" · "keep a pretty strict beat that the lyrics can now regulate around, with a kick and stuff, sidechained into the lyrics" · "i want this all to be in c code" — so v4 became the lane's first C-native score AND its dance cut, in one move:

  • The chart. 122 BPM, four on the floor. bin/halo3.py warps every charted phrase per-word onto the beat grid (voiced frames absorb the stretch at weight 1.0, consonants ride near 1:1 at 0.18 — the smooth stretching; the slice tail always plays 1:1 after the last word, so nothing ends at a fade). Stretch past 1.8× flattens to the unit's grid tone with vibrato fading in — "stone" and "pass" hold whole bars. Snap is 0.92: the regulation.
  • The bank. 9 phrases × 5 renders each in vox4/: lead, the octave halo pair, and the full-word low-3rd/low-5th backup — Camille singing backup for Camille, words and all, from the same warp so every layer locks sample-for-sample.
  • The C engine. c/lonerremix.c (single file, fleet-standard) is the score. halo3.py emits c/loner-chart.h — per word, its beat slot and measured semitone — and the engine's pluck plays ONLY chart notes: the band's melody is her melody, doubling under her and answering in the gaps. Kick every beat sidechained into the lyrics (vox ducks 0.34, bed 0.52, drums never); clap 2 & 4; offbeat air-hat; 16th ticks; offbeat house sub. v3's dub delay and Schroeder pair ported intact.

Form, 76 bars at 122 (2:35 incl. tail):

act at bars what happens
V1 0:00 0–16 her first word ON beat 0 of bar 0; pluck doubles, then answers up an octave
HOOK 0:31 16–24 of-a-stone + hk unison + stone-long canon; ens crowd
V2 0:47 24–32 the "not again!" phrases; backup 3rds enter
BREAK 1:03 32–40 kick out — the naked regulated line, stone-long-echo, riser
DROP 1:18 40–60 everything: both backups, harps every 2 bars, stone-long-17 crown
OUT 1:58 60–76 the pluck alone finishes the sentence; pass-long ghost
pop/.venv/bin/python pop/loner/bin/halo3.py   # vox4/ bank + c/loner-chart.h
bash pop/loner/c/build.sh                     # cc → c/lonerremix
pop/loner/c/lonerremix                        # → out/loner-remix-v4-full.wav (~1.3 s)
bash pop/loner/c/cut-v4.sh                    # → out/loner-remix-v4.mp3 (−14 LUFS)

The kick+vocals study, and reading it #

MINIMAL=1 c/lonerremix renders out/loner-kickvox — kick plus the unbroken vocal, nothing else — and bin/timeline.py draws it: a scrolling piano roll where every word is a block at its chart slot and sung pitch, with the word's real waveform drawn inside it (per-column peak of the vox4/ lead render, normalized to the whole take), bars numbered, the kick lane flashing, a live timecode and bar · beat address in the corner. The active word outlines in pink rather than filling, so the waveform stays readable while it plays.

Watching that video is also how the wrong syllable got caught — @jeffrey: "hmm ur mistaken, the 'led' is actually the 'up' utterance in the data · you need to fix that alignment too · did you try using whisper to find the original word boundary · if not we should pass the audio to openai for that pls". He was right, and it was the deepest bug in the lane.

The original receipts came from whisper.cpp ggml-small run with -ml 1 (max one word per segment), which returns sub-word tokens: it cut curled into cur + led, and every label after that slid by a syllable. The span we were calling led is where she sings up; the spans up + in were both the one word in; and stone began 1.3 s early, inside the held octave of "of a" — which is why the octave kept bleeding into stone's block. A note segmentation of the take agrees: the first phrase is seven sung notes, F4 · D#4 · C#4 · C4 · A#3 · D#4 · C4, for seven syllables — sit·ting curled up in my·self. There is no "led" note; it was the tail of curled plus the breath before up.

bin/align.py re-aligns every slice through OpenAI whisper-1 with word timestamps, primed with the lyric so short clips aren't misheard (f-sitting-curled came back as "in my cell phone" without it), and repairs whisper-1's occasional zero-width word by finding that word's real onset in the audio. Anything it can't align cleanly stays on the old receipt and says so. Receipt: samples/.align.json.

Two things fixed themselves once the labels were right. "up" is C4 and "in" is A#3 — two different notes, so bar 2 stopped reading as one fused tonic. And "a" is G#4, not A#4, so it is no longer the same pitch as "of": the octave now steps A#4 → G#4 → D#4 across of a stone instead of holding one unbroken note through all three blocks.

Watching that video is also how the boundary repair got found — @jeffrey: "has word boundary wrong · led has up within it · that's definitely causing bugs". Whisper times a word where the transcript hands over, not where the singing changes. It put led 240 ms late, so led's slot opened inside "cur"'s note and replayed cur's pitch for half a beat before the real note arrived. snap_boundaries() now pulls every word start onto the nearest real acoustic event — a sustained pitch step (medians over 120 ms windows either side; 80 ms was too narrow to see led's 0.67 st move), or, for words that open on an unvoiced consonant like st-one, an energy valley — inside a ±250 ms search that can never reorder words or starve one below 80 ms. On the whole line fourteen boundaries move; the big ones are led +240 ms · pass +165 ms · to +140 ms · waiting −130 ms · time −110 ms. Receipt: snaps in vox4/.manifest.json.

Drawing the audio into the blocks is what exposed the energy trim. Whisper's word boundaries are handoffs, not note ends: it gave "led" 0.99 s when Camille stops singing after ~0.55 and the rest is decay, and those dead frames were being time-stretched across the slot along with the note — "led" sang to 78% of its block and then sat there. Each unit's source span now ends where its audio actually ends (5 ms RMS against a −36 dB gate of the take's peak, plus a 50 ms release margin), and the silence is dropped rather than warped, so only sung frames stretch. The cut lands at the start of the first sustained silence, not at the last loud frame — the next word's attack routinely leaks across the boundary, and a last-loud-frame search reads that leak as "the word runs to the end" and trims nothing (it was exactly what hid led's dead half-beat). Requiring 120 ms of quiet also protects a stop closure inside a word (patiently's /t/ is ~60 ms). Guards: the last unit is untouched (the tail/release machinery owns it), trims under 80 ms aren't worth the surgery, and no unit can lose more than 65% of its span. Nine fire on the whole line, and no word now has more than 150 ms of hole inside its block (led's was a full beat). The gate is deliberately conservative — at −26 dB it would cut 2.1 s out of "stone". Receipt: trims in vox4/.manifest.json.

The first half is now one word per bar: sitting = bar 0, curled = bar 1 (@jeffrey: "curled is too short" — and with the real alignment it is one word, so it simply takes the bar), "up in" = bar 2 ("'up in' should last a full bar"; in holds longer because she sings it twice as long), my·self·i = bar 3, so think keeps the bar-4 downbeat and of/a/stone never move.

OPENAI_API_KEY=... pop/.venv/bin/python pop/loner/bin/align.py
MINIMAL=1 pop/loner/c/lonerremix       # → out/loner-kickvox-full.wav
python3 pop/loner/bin/timeline.py      # → out/loner-kickvox-timeline.mp4

timeline.py wants system python3 (PIL), not pop/.venv.

Measured #

check v1 v2 v3
duration 171.2 s (2:51 incl. tail) 171.2 s 159.2 s (2:39)
integrated loudness −15.9 LUFS −15.9 LUFS −15.9 LUFS
true peak −1.5 dBFS −1.5 dBFS −1.5 dBFS
loudness range 10.9 LU 12.2 LU 14.7 LU
max sample 0.840 0.840 0.840
max sample-to-sample step 0.221 (threshold 0.35) 0.226 0.285
mono fold-down −0.02 dB −0.02 dB −0.10 dB (worst 2 s window −1.05, the all-side intro)
bus balance vox −11.1 · music −17.6 · drums −23.4 LUFS (verse 2 + lift) — the voice ~6.5 dB proud unchanged score vox −12.8 · music −19.3 · drums −19.2 (drop 1)

Not in v1 #

  • s-whole-line and o-whole-line (the ensemble performance with Jeffrey and Alex) are sliced and measured but unplayed — both sit in a different register and want the speech-to-singing pipeline (pop/cult/bin/sing.py) or careful repitching before they can join. A later cut could end on the three of them.
  • No sung-note synthesis at all: v1 is the real takes, placed. The WORLD/Saitou machinery stays in the drawer until the arrangement earns it.