From 8406f4af38a2bfbc757bf092474d5ed7331320b0 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Mon, 17 Aug 2026 18:16:57 -0400 Subject: [PATCH] =?UTF-8?q?loner:=20the=20labels=20were=20on=20the=20wrong?= =?UTF-8?q?=20syllables=20=E2=80=94=20re-align=20every=20slice=20through?= =?UTF-8?q?=20OpenAI,=20and=20'led'=20turns=20out=20to=20be=20'up'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @jeffrey, watching the study: "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 this was the deepest bug in the lane — everything I'd been tuning sat on top of it. The original receipts came from whisper.cpp ggml-small run with `-ml 1`, which returns SUB-WORD TOKENS: it cut "curled" into cur + led, and every label after that slid by a syllable. The span we called `led` is where she sings **up**; `up` + `in` were both the one word **in**; and `stone` started 1.3 s early, inside the held octave of "of a" — which is exactly why the octave kept bleeding into stone's block. A note segmentation of the take agrees with OpenAI and not with the old receipts: 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". (The 420 ms of dead air the trim keeps cutting is now correctly logged against `up`.) bin/align.py re-aligns each slice through OpenAI whisper-1 with word timestamps, primed with the lyric so the 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 (it emitted the whole line's `just` as 12.38–12.38; the repair puts it at 11.12, where the note segmentation says it is). Anything it can't align cleanly stays on the old receipt and says so — two n- slices do. Receipt: samples/.align.json. The key is read from env or vault and never written down. Two things fixed themselves once the labels were right, both of them complaints from earlier today. "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. Verified in the render: curled holds C#4 through bar 1, up enters C4 at 8.07, in enters A#3 at 9.54. The chart re-indexes accordingly (curled is one word, so splits moves 5 → 4) and the bar map is now one word per bar: sitting 0 · curled 1 · up in 2 · my self i 3 · think on the bar-4 downbeat · of 4 · a stone 4, so nothing downstream of "think" moved. --- pop/loner/README.md | 51 ++++- pop/loner/bin/align.py | 178 ++++++++++++++++++ pop/loner/bin/halo3.py | 59 +++--- pop/loner/c/loner-chart.h | 83 +++++---- pop/loner/samples/.align.json | 337 ++++++++++++++++++++++++++++++++++ pop/loner/vox4/.chart.json | 164 ++++++++--------- pop/loner/vox4/.manifest.json | 164 ++++++++--------- 7 files changed, 796 insertions(+), 240 deletions(-) create mode 100644 pop/loner/bin/align.py create mode 100644 pop/loner/samples/.align.json diff --git a/pop/loner/README.md b/pop/loner/README.md index e35065004..b1795252d 100644 --- a/pop/loner/README.md +++ b/pop/loner/README.md @@ -308,6 +308,37 @@ 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 @@ -344,16 +375,16 @@ 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`. -Bar 1 and 2 are hand-pinned against that picture: **curled** alone fills -bar 1 (cur 2 + led 2 — her own split says led ≥ cur), and **"up in" -owns all of bar 2** (up 1.5 + in 2.5 — in gets the longer hold because -she sings it twice as long). Those beats came out of "myself", which was -stretching 1.92× into synthesized tone and now sits at 0.96×, her real -voice. Worth knowing what that costs: her "up in" is 0.75 s of source -against a 1.97 s bar, so filling it is a 2.6× stretch — past the 1.8× -line where the engine stops replaying her and starts holding a -synthesized grid tone. Both words are the tonic, so bar 2 reads as one -held A#3 with no seam between them. +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. + +```bash +OPENAI_API_KEY=... pop/.venv/bin/python pop/loner/bin/align.py +``` ```bash MINIMAL=1 pop/loner/c/lonerremix # → out/loner-kickvox-full.wav diff --git a/pop/loner/bin/align.py b/pop/loner/bin/align.py new file mode 100644 index 000000000..a87b9dd01 --- /dev/null +++ b/pop/loner/bin/align.py @@ -0,0 +1,178 @@ +# align.py — word alignment for the loner slices, from OpenAI whisper-1. +# +# @jeffrey, after watching the study video and hearing the wrong syllable +# inside a block: "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. The lane's 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 then every +# label after it slid by a syllable. The span labelled `led` is where +# she actually sings **up**; the spans labelled `up` + `in` are 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 with OpenAI and not with the +# old receipts: 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 separate "led" note; it was the tail of "curled" +# plus the breath before "up". +# +# OPENAI_API_KEY=... pop/.venv/bin/python pop/loner/bin/align.py +# → samples/.align.json (per slice, per word: start, end, f0, note) +# +# The key is read from the environment, or from the vault env file if +# that is present. It is never written to the receipt. + +import json, os, subprocess, sys +import numpy as np +import soundfile as sf +import pyworld as pw + +HERE = os.path.dirname(os.path.abspath(__file__)) +LANE = os.path.dirname(HERE) +REPO = os.path.dirname(os.path.dirname(LANE)) +SAMPLES = os.path.join(LANE, "samples") +TONIC = 237.0 +FLOOR = 70.0 +VAULT_ENV = os.path.join(REPO, "aesthetic-computer-vault", + ".devcontainer", "envs", "devcontainer.env") + +# the slices the v4 chart plays — see CHART in halo3.py +SLICES = [ + "f-whole-line", "f-sitting-curled", "f-i-think", "f-of-a-stone", + "f-just-waiting", "f-very-patiently", "f-for-time-to-pass", + "n-getting-curled", "n-stone-waiting", "n-for-time-to-pass", +] + +CHROM = ["A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A"] + + +def note_name(hz): + st = int(round(12.0 * np.log2(hz / TONIC))) + return f"{CHROM[st % 12]}{3 + (st + 10) // 12}" + + +def api_key(): + k = os.environ.get("OPENAI_API_KEY") + if k: + return k + if os.path.exists(VAULT_ENV): + for line in open(VAULT_ENV): + if line.startswith("OPENAI_API_KEY"): + return line.split("=", 1)[1].strip().strip("'\"") + sys.exit("no OPENAI_API_KEY (env or vault)") + + +# The song is one known sentence; priming whisper with it stops the +# short clips from being misheard (f-sitting-curled came back as "in my +# cell phone", f-for-time-to-pass as "the time to pass"). +LYRIC = ("sitting curled up in myself, i think of a stone, " + "just waiting very patiently for time to pass") + + +def transcribe(path, key): + """whisper-1 with word-level timestamps. curl keeps the key off the + process list of anything but this call.""" + out = subprocess.run( + ["curl", "-s", "https://api.openai.com/v1/audio/transcriptions", + "-H", f"Authorization: Bearer {key}", + "-F", f"file=@{path}", + "-F", "model=whisper-1", + "-F", f"prompt={LYRIC}", + "-F", "response_format=verbose_json", + "-F", "timestamp_granularities[]=word"], + capture_output=True, text=True, check=True).stdout + d = json.loads(out) + if "error" in d: + sys.exit(f"{os.path.basename(path)}: {d['error'].get('message')}") + return d + + +def repair(words, x, fs): + """whisper-1 occasionally emits a zero-width word (it gave the whole + line's `just` as 12.38–12.38). Rather than discard an otherwise + perfect alignment, find that word's real onset in the audio: the + first sustained-loud frame between its neighbours.""" + fixed = [] + peak = np.max(np.abs(x)) or 1.0 + n = int(round(fs * 0.005)) + for i, w in enumerate(words): + w = dict(w) + if w["end"] - w["start"] > 0.05: + fixed.append(w) + continue + lo = fixed[-1]["end"] if fixed else 0.0 + hi = words[i + 1]["start"] if i + 1 < len(words) else len(x) / fs + a, b = int(lo * fs / n), int(hi * fs / n) + seg = x[a * n:b * n] + m = len(seg) // n + if m < 4 or hi <= lo: + fixed.append(w) + continue + rms = np.sqrt((seg[:m * n].reshape(m, n) ** 2).mean(axis=1)) + loud = rms > peak * 10.0 ** (-36.0 / 20.0) + run, onset = int(0.060 / 0.005), None + for k in range(m - run): + if loud[k:k + run].all(): + onset = k + break + w["start"] = round(lo + (onset or 0) * 0.005, 3) + w["end"] = round(hi, 3) + fixed.append(w) + print(f" repaired zero-width «{w['word']}» → {w['start']:.2f}–{w['end']:.2f}") + return fixed + + +def measure(x, fs, words): + """Median f0 over each word's voiced frames — the note she sings.""" + f0r, t = pw.harvest(x, fs, f0_floor=FLOOR, f0_ceil=600.0, frame_period=5.0) + f0 = pw.stonemask(x, f0r, t, fs) + out = [] + for w in words: + a, b = int(w["start"] / 0.005), int(w["end"] / 0.005) + seg = f0[max(0, a):max(1, min(b, len(f0)))] + seg = seg[seg > 0] + hz = float(np.median(seg)) if len(seg) else 0.0 + out.append(dict(t=w["word"], start=round(w["start"], 3), + end=round(w["end"], 3), f0_hz=round(hz, 1), + note=note_name(hz) if hz else "")) + return out + + +def main(): + key = api_key() + align = {} + for name in SLICES: + path = os.path.join(SAMPLES, f"{name}.wav") + if not os.path.exists(path): + print(f" {name:22s} — missing, skipped") + continue + d = transcribe(path, key) + words = d.get("words") or [] + x, fs = sf.read(path, dtype="float64") + if x.ndim > 1: + x = x.mean(axis=1) + words = repair(words, x, fs) + entry = measure(x, fs, words) + # a usable alignment is strictly ordered with no zero-width word + # (n-stone-waiting once returned just@3.86 AND waiting@3.86) + ok = all(b["start"] > a["start"] and b["start"] >= a["end"] - 0.001 + for a, b in zip(entry[:-1], entry[1:])) \ + and all(w["end"] - w["start"] > 0.05 for w in entry) and len(entry) > 1 + if not ok: + print(f" {name:22s} — DEGENERATE alignment, left on the old receipt") + continue + align[name] = dict(model="whisper-1 (openai, word timestamps)", + text=d.get("text", "").strip(), words=entry) + print(f" {name:22s} {len(entry):2d} words «{d.get('text','').strip()[:52]}»") + print(" " + " · ".join(f"{w['t']}@{w['start']:.2f}" for w in entry)) + dest = os.path.join(SAMPLES, ".align.json") + json.dump(align, open(dest, "w"), indent=1) + print(f"WROTE {dest} ({len(align)} slices)") + + +if __name__ == "__main__": + main() diff --git a/pop/loner/bin/halo3.py b/pop/loner/bin/halo3.py index 322927f31..0e2a4283b 100644 --- a/pop/loner/bin/halo3.py +++ b/pop/loner/bin/halo3.py @@ -158,26 +158,25 @@ CHART = { "w-whole-line": { "slice": "f-whole-line", "beats": 56.0, # splits: original unit indices to cut at their # internal fricative (myself → my·self), so the - # bar map can be: sitting = bar 1 · curled up = - # bar 2 · in my = bar 3 · self i = bar 4 — and - # "think" lands on the bar-5 downbeat. durs are - # POST-split indices. - "splits": [5], - # then: think = 2 · OF (the octave) = 4 · - # a stone = 4. "curled is too short · up - # comes too soon": CURLED alone fills bar 1 - # (cur 2 + led 2 — her own 37/50 split says - # led ≥ cur). "up and in should be much - # longer": UP IN now owns the whole of bar - # 2 (1.5 + 2.5 — in gets the longer hold - # because she sings it twice as long), and - # the beats come OUT of myself, which was - # stretching 1.92× into a synthesized tone - # and now sits at 0.96× — her real voice. - # think still lands on the bar-4 downbeat. - "durs": { 0: 4.0, 1: 2.0, 2: 2.0, 3: 1.5, - 4: 2.5, 5: 1.5, 6: 1.5, 7: 1.0, - 8: 2.0, 9: 4.0, 10: 0.5, 11: 3.5 } }, + # bar map can be one word per bar. Index 4 is + # "myself" under the OpenAI alignment, where + # "curled" is ONE word — under whisper.cpp's + # sub-word tokens it was cur+led and this was 5. + # durs are POST-split indices. + "splits": [4], + # One word per bar through the first half: + # 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 lands on the + # bar-4 downbeat · OF (the octave) = 4 · + # a stone = 4. + "durs": { 0: 4.0, 1: 4.0, 2: 1.5, 3: 2.5, + 4: 1.5, 5: 1.5, 6: 1.0, 7: 2.0, + 8: 4.0, 9: 0.5, 10: 3.5 } }, "w-sitting-curled": { "slice": "f-sitting-curled", "beats": 11.0 }, "w-i-think": { "slice": "f-i-think", "beats": 3.5 }, "w-of-a-stone": { "slice": "f-of-a-stone", "beats": 8.0 }, @@ -210,6 +209,18 @@ def derive_units(words, beats_total, stretch=None, durs=None): SLICES = json.load(open(os.path.join(LANE, "samples", ".manifest.json"))) +# ── the alignment — OpenAI's words, not whisper.cpp's sub-word tokens ── +# The lane's original receipts came from whisper.cpp ggml-small at -ml 1, +# which returns TOKENS: it cut "curled" into "cur" + "led", and every +# label after slid by a syllable, so the span we were calling `led` is +# where she sings **up**, and `stone` started 1.3 s early inside the held +# octave of "of a". bin/align.py re-aligns each slice through OpenAI +# whisper-1 with word timestamps; anything it can't align cleanly stays +# on the old receipt. Times there are slice-relative, so they come back +# onto the manifest's clock by adding the slice start. +ALIGN_PATH = os.path.join(LANE, "samples", ".align.json") +ALIGN = json.load(open(ALIGN_PATH)) if os.path.exists(ALIGN_PATH) else {} + # ── boundary repair — snap whisper's word times to the real note ────── # @jeffrey, watching the study: "has word boundary wrong · led has up # within it · that's definitely causing bugs · also of a stone, the @@ -477,7 +488,13 @@ for name, ch in CHART.items(): a = analyze(x, fs) F = len(a["f0c"]) t0_slice = entry["start"] - words = list(entry["word_f0"]) + aligned = slice_name in ALIGN + if aligned: + words = [dict(t=w["t"], start=t0_slice + w["start"], + end=t0_slice + w["end"], f0_hz=w["f0_hz"], + note=w["note"]) for w in ALIGN[slice_name]["words"]] + else: + words = list(entry["word_f0"]) words, snaps = snap_boundaries(a, words, t0_slice) # sub-split units at their internal fricative (e.g. myself → my·self) for ui in sorted(ch.get("splits", []), reverse=True): diff --git a/pop/loner/c/loner-chart.h b/pop/loner/c/loner-chart.h index 426643561..51882b812 100644 --- a/pop/loner/c/loner-chart.h +++ b/pop/loner/c/loner-chart.h @@ -14,56 +14,55 @@ typedef struct { const char *name; double leadIn; double beats; static const ChartNote w_whole_line_notes[] = { { 0.00, 4.00, 7 }, - { 4.00, 2.00, 3 }, - { 6.00, 2.00, 2 }, - { 8.00, 1.50, 0 }, + { 4.00, 4.00, 3 }, + { 8.00, 1.50, 2 }, { 9.50, 2.50, 0 }, { 12.00, 1.50, 5 }, { 13.50, 1.50, 5 }, { 15.00, 1.00, -2 }, - { 16.00, 2.00, -2 }, + { 16.00, 2.00, -5 }, { 18.00, 4.00, 12 }, - { 22.00, 0.50, 12 }, - { 22.50, 3.50, 6 }, - { 26.00, 3.00, 2 }, - { 29.00, 4.00, 3 }, - { 33.00, 3.50, 0 }, - { 36.50, 5.50, 5 }, - { 42.00, 2.00, 5 }, - { 44.00, 4.00, 7 }, - { 48.00, 4.00, 3 }, - { 52.00, 4.00, 3 }, + { 22.00, 0.50, 10 }, + { 22.50, 3.50, 5 }, + { 26.00, 3.50, 2 }, + { 29.50, 3.50, 3 }, + { 33.00, 4.50, 0 }, + { 37.50, 2.50, 7 }, + { 40.00, 5.00, 3 }, + { 45.00, 2.50, 7 }, + { 47.50, 5.50, 3 }, + { 53.00, 3.50, 3 }, }; static const ChartNote w_sitting_curled_notes[] = { - { 0.00, 3.00, 7 }, + { 0.00, 3.00, 6 }, { 3.00, 2.00, 3 }, { 5.00, 1.50, 2 }, - { 6.50, 0.50, 0 }, - { 7.00, 1.00, 0 }, - { 8.00, 3.00, 5 }, + { 6.50, 1.50, 0 }, + { 8.00, 3.00, 4 }, }; static const ChartNote w_i_think_notes[] = { - { 0.00, 0.50, -2 }, - { 0.50, 3.00, -2 }, + { 0.00, 2.50, -2 }, + { 2.50, 1.00, -5 }, }; static const ChartNote w_of_a_stone_notes[] = { - { 0.00, 1.50, 12 }, - { 1.50, 1.00, 12 }, - { 2.50, 5.50, 6 }, + { 0.00, 4.00, 12 }, + { 4.00, 2.00, 10 }, + { 6.00, 2.00, 5 }, }; static const ChartNote w_just_waiting_notes[] = { - { 0.00, 3.00, 2 }, - { 3.00, 3.50, 3 }, + { 0.00, 3.50, 2 }, + { 3.50, 3.00, 3 }, }; static const ChartNote w_very_patiently_notes[] = { - { 0.00, 3.50, 0 }, - { 3.50, 5.00, 5 }, + { 0.00, 2.50, 0 }, + { 2.50, 3.50, 7 }, + { 6.00, 2.50, 3 }, }; static const ChartNote w_for_time_to_pass_notes[] = { - { 0.00, 1.50, 5 }, - { 1.50, 4.00, 7 }, - { 5.50, 3.50, 3 }, - { 9.00, 3.50, 3 }, + { 0.00, 2.00, 5 }, + { 2.00, 2.50, 7 }, + { 4.50, 4.00, 3 }, + { 8.50, 4.00, 3 }, }; static const ChartNote w_n_getting_curled_notes[] = { { 0.00, 3.00, 5 }, @@ -85,22 +84,22 @@ static const ChartNote w_n_stone_waiting_notes[] = { { 11.50, 5.00, 5 }, }; static const ChartNote w_n_for_time_to_pass_notes[] = { - { 0.00, 2.00, 7 }, - { 2.00, 1.50, 7 }, - { 3.50, 1.50, 3 }, - { 5.00, 1.50, 3 }, + { 0.00, 0.50, 5 }, + { 0.50, 1.00, 7 }, + { 1.50, 2.50, 3 }, + { 4.00, 1.50, 3 }, }; static const ChartPhrase CHART[] = { - { "w-whole-line", 0.050, 56.00, 20, w_whole_line_notes }, - { "w-sitting-curled", 0.050, 11.00, 6, w_sitting_curled_notes }, - { "w-i-think", 0.060, 3.50, 2, w_i_think_notes }, - { "w-of-a-stone", 0.060, 8.00, 3, w_of_a_stone_notes }, + { "w-whole-line", 0.020, 56.50, 19, w_whole_line_notes }, + { "w-sitting-curled", 0.020, 11.00, 5, w_sitting_curled_notes }, + { "w-i-think", 0.020, 3.50, 2, w_i_think_notes }, + { "w-of-a-stone", 0.000, 8.00, 3, w_of_a_stone_notes }, { "w-just-waiting", 0.075, 6.50, 2, w_just_waiting_notes }, - { "w-very-patiently", 0.030, 8.50, 2, w_very_patiently_notes }, - { "w-for-time-to-pass", 0.040, 12.50, 4, w_for_time_to_pass_notes }, + { "w-very-patiently", 0.000, 8.50, 3, w_very_patiently_notes }, + { "w-for-time-to-pass", 0.035, 12.50, 4, w_for_time_to_pass_notes }, { "w-n-getting-curled", 0.060, 10.50, 8, w_n_getting_curled_notes }, { "w-n-stone-waiting", 0.060, 16.50, 7, w_n_stone_waiting_notes }, - { "w-n-for-time-to-pass", 0.060, 6.50, 4, w_n_for_time_to_pass_notes }, + { "w-n-for-time-to-pass", 0.000, 5.50, 4, w_n_for_time_to_pass_notes }, }; #define CHART_N 10 diff --git a/pop/loner/samples/.align.json b/pop/loner/samples/.align.json new file mode 100644 index 000000000..abbbb1165 --- /dev/null +++ b/pop/loner/samples/.align.json @@ -0,0 +1,337 @@ +{ + "f-whole-line": { + "model": "whisper-1 (openai, word timestamps)", + "text": "sitting curled up in myself i think of a stone, just waiting very patiently for time to pass", + "words": [ + { + "t": "sitting", + "start": 0.0, + "end": 1.4, + "f0_hz": 348.8, + "note": "F4" + }, + { + "t": "curled", + "start": 1.4, + "end": 2.44, + "f0_hz": 278.2, + "note": "C#4" + }, + { + "t": "up", + "start": 2.44, + "end": 3.14, + "f0_hz": 265.3, + "note": "C4" + }, + { + "t": "in", + "start": 3.14, + "end": 3.94, + "f0_hz": 237.3, + "note": "A#3" + }, + { + "t": "myself", + "start": 3.94, + "end": 5.32, + "f0_hz": 309.4, + "note": "D#4" + }, + { + "t": "i", + "start": 5.32, + "end": 6.26, + "f0_hz": 211.0, + "note": "G#3" + }, + { + "t": "think", + "start": 6.26, + "end": 6.86, + "f0_hz": 175.6, + "note": "F3" + }, + { + "t": "of", + "start": 6.86, + "end": 8.8, + "f0_hz": 470.6, + "note": "A#4" + }, + { + "t": "a", + "start": 8.8, + "end": 9.6, + "f0_hz": 429.4, + "note": "G#4" + }, + { + "t": "stone", + "start": 9.6, + "end": 10.48, + "f0_hz": 317.4, + "note": "D#4" + }, + { + "t": "just", + "start": 11.115, + "end": 12.38, + "f0_hz": 268.1, + "note": "C4" + }, + { + "t": "waiting", + "start": 12.38, + "end": 14.04, + "f0_hz": 279.9, + "note": "C#4" + }, + { + "t": "very", + "start": 14.04, + "end": 15.96, + "f0_hz": 237.3, + "note": "A#3" + }, + { + "t": "patiently", + "start": 15.96, + "end": 17.26, + "f0_hz": 357.2, + "note": "F4" + }, + { + "t": "for", + "start": 17.26, + "end": 19.16, + "f0_hz": 283.8, + "note": "C#4" + }, + { + "t": "time", + "start": 19.16, + "end": 20.5, + "f0_hz": 357.5, + "note": "F4" + }, + { + "t": "to", + "start": 20.5, + "end": 22.64, + "f0_hz": 282.4, + "note": "C#4" + }, + { + "t": "pass", + "start": 22.64, + "end": 24.2, + "f0_hz": 282.0, + "note": "C#4" + } + ] + }, + "f-sitting-curled": { + "model": "whisper-1 (openai, word timestamps)", + "text": "sitting curled up in myself", + "words": [ + { + "t": "sitting", + "start": 0.0, + "end": 1.54, + "f0_hz": 341.1, + "note": "E4" + }, + { + "t": "curled", + "start": 1.54, + "end": 2.46, + "f0_hz": 278.0, + "note": "C#4" + }, + { + "t": "up", + "start": 2.46, + "end": 3.16, + "f0_hz": 265.2, + "note": "C4" + }, + { + "t": "in", + "start": 3.16, + "end": 3.76, + "f0_hz": 237.0, + "note": "A#3" + }, + { + "t": "myself", + "start": 3.76, + "end": 5.22, + "f0_hz": 302.7, + "note": "D4" + } + ] + }, + "f-i-think": { + "model": "whisper-1 (openai, word timestamps)", + "text": "i think", + "words": [ + { + "t": "i", + "start": 0.0, + "end": 0.72, + "f0_hz": 211.2, + "note": "G#3" + }, + { + "t": "think", + "start": 0.72, + "end": 1.24, + "f0_hz": 179.5, + "note": "F3" + } + ] + }, + "f-of-a-stone": { + "model": "whisper-1 (openai, word timestamps)", + "text": "of a stone", + "words": [ + { + "t": "of", + "start": 0.0, + "end": 1.62, + "f0_hz": 471.1, + "note": "A#4" + }, + { + "t": "a", + "start": 1.62, + "end": 2.42, + "f0_hz": 429.4, + "note": "G#4" + }, + { + "t": "stone", + "start": 2.42, + "end": 3.16, + "f0_hz": 316.9, + "note": "D#4" + } + ] + }, + "f-just-waiting": { + "model": "whisper-1 (openai, word timestamps)", + "text": "just waiting", + "words": [ + { + "t": "just", + "start": 0.0, + "end": 1.46, + "f0_hz": 268.1, + "note": "C4" + }, + { + "t": "waiting", + "start": 1.46, + "end": 2.96, + "f0_hz": 280.0, + "note": "C#4" + } + ] + }, + "f-very-patiently": { + "model": "whisper-1 (openai, word timestamps)", + "text": "very patiently for", + "words": [ + { + "t": "very", + "start": 0.0, + "end": 1.38, + "f0_hz": 236.4, + "note": "A#3" + }, + { + "t": "patiently", + "start": 1.38, + "end": 2.92, + "f0_hz": 357.2, + "note": "F4" + }, + { + "t": "for", + "start": 2.92, + "end": 4.14, + "f0_hz": 280.4, + "note": "C#4" + } + ] + }, + "f-for-time-to-pass": { + "model": "whisper-1 (openai, word timestamps)", + "text": "for time to pass", + "words": [ + { + "t": "for", + "start": 0.0, + "end": 0.62, + "f0_hz": 320.1, + "note": "D#4" + }, + { + "t": "time", + "start": 0.62, + "end": 2.06, + "f0_hz": 357.4, + "note": "F4" + }, + { + "t": "to", + "start": 2.06, + "end": 3.84, + "f0_hz": 282.4, + "note": "C#4" + }, + { + "t": "pass", + "start": 3.84, + "end": 5.7, + "f0_hz": 282.0, + "note": "C#4" + } + ] + }, + "n-for-time-to-pass": { + "model": "whisper-1 (openai, word timestamps)", + "text": "for time to pass", + "words": [ + { + "t": "for", + "start": 0.0, + "end": 0.16, + "f0_hz": 310.4, + "note": "D#4" + }, + { + "t": "time", + "start": 0.16, + "end": 1.06, + "f0_hz": 349.5, + "note": "F4" + }, + { + "t": "to", + "start": 1.06, + "end": 2.84, + "f0_hz": 275.6, + "note": "C#4" + }, + { + "t": "pass", + "start": 2.84, + "end": 3.56, + "f0_hz": 279.5, + "note": "C#4" + } + ] + } +} \ No newline at end of file diff --git a/pop/loner/vox4/.chart.json b/pop/loner/vox4/.chart.json index 90c9b2d66..61861000a 100644 --- a/pop/loner/vox4/.chart.json +++ b/pop/loner/vox4/.chart.json @@ -1,30 +1,24 @@ { "w-whole-line": { - "leadIn": 0.05, - "beats": 56.0, + "leadIn": 0.02, + "beats": 56.5, "notes": [ { "beat": 0.0, "dur": 4.0, "st": 7, - "t": "Sitting" + "t": "sitting" }, { "beat": 4.0, - "dur": 2.0, + "dur": 4.0, "st": 3, - "t": "cur" - }, - { - "beat": 6.0, - "dur": 2.0, - "st": 2, - "t": "led" + "t": "curled" }, { "beat": 8.0, "dur": 1.5, - "st": 0, + "st": 2, "t": "up" }, { @@ -49,12 +43,12 @@ "beat": 15.0, "dur": 1.0, "st": -2, - "t": "I" + "t": "i" }, { "beat": 16.0, "dur": 2.0, - "st": -2, + "st": -5, "t": "think" }, { @@ -66,145 +60,139 @@ { "beat": 22.0, "dur": 0.5, - "st": 12, + "st": 10, "t": "a" }, { "beat": 22.5, "dur": 3.5, - "st": 6, + "st": 5, "t": "stone" }, { "beat": 26.0, - "dur": 3.0, + "dur": 3.5, "st": 2, "t": "just" }, { - "beat": 29.0, - "dur": 4.0, + "beat": 29.5, + "dur": 3.5, "st": 3, "t": "waiting" }, { "beat": 33.0, - "dur": 3.5, + "dur": 4.5, "st": 0, "t": "very" }, { - "beat": 36.5, - "dur": 5.5, - "st": 5, + "beat": 37.5, + "dur": 2.5, + "st": 7, "t": "patiently" }, { - "beat": 42.0, - "dur": 2.0, - "st": 5, + "beat": 40.0, + "dur": 5.0, + "st": 3, "t": "for" }, { - "beat": 44.0, - "dur": 4.0, + "beat": 45.0, + "dur": 2.5, "st": 7, "t": "time" }, { - "beat": 48.0, - "dur": 4.0, + "beat": 47.5, + "dur": 5.5, "st": 3, "t": "to" }, { - "beat": 52.0, - "dur": 4.0, + "beat": 53.0, + "dur": 3.5, "st": 3, "t": "pass" } ] }, "w-sitting-curled": { - "leadIn": 0.05, + "leadIn": 0.02, "beats": 11.0, "notes": [ { "beat": 0.0, "dur": 3.0, - "st": 7, - "t": "Sitting" + "st": 6, + "t": "sitting" }, { "beat": 3.0, "dur": 2.0, "st": 3, - "t": "cur" + "t": "curled" }, { "beat": 5.0, "dur": 1.5, "st": 2, - "t": "led" - }, - { - "beat": 6.5, - "dur": 0.5, - "st": 0, "t": "up" }, { - "beat": 7.0, - "dur": 1.0, + "beat": 6.5, + "dur": 1.5, "st": 0, "t": "in" }, { "beat": 8.0, "dur": 3.0, - "st": 5, + "st": 4, "t": "myself" } ] }, "w-i-think": { - "leadIn": 0.06, + "leadIn": 0.02, "beats": 3.5, "notes": [ { "beat": 0.0, - "dur": 0.5, + "dur": 2.5, "st": -2, - "t": "I" + "t": "i" }, { - "beat": 0.5, - "dur": 3.0, - "st": -2, + "beat": 2.5, + "dur": 1.0, + "st": -5, "t": "think" } ] }, "w-of-a-stone": { - "leadIn": 0.06, + "leadIn": 0.0, "beats": 8.0, "notes": [ { "beat": 0.0, - "dur": 1.5, + "dur": 4.0, "st": 12, "t": "of" }, { - "beat": 1.5, - "dur": 1.0, - "st": 12, + "beat": 4.0, + "dur": 2.0, + "st": 10, "t": "a" }, { - "beat": 2.5, - "dur": 5.5, - "st": 6, + "beat": 6.0, + "dur": 2.0, + "st": 5, "t": "stone" } ] @@ -215,61 +203,67 @@ "notes": [ { "beat": 0.0, - "dur": 3.0, + "dur": 3.5, "st": 2, "t": "just" }, { - "beat": 3.0, - "dur": 3.5, + "beat": 3.5, + "dur": 3.0, "st": 3, "t": "waiting" } ] }, "w-very-patiently": { - "leadIn": 0.03, + "leadIn": 0.0, "beats": 8.5, "notes": [ { "beat": 0.0, - "dur": 3.5, + "dur": 2.5, "st": 0, "t": "very" }, { - "beat": 3.5, - "dur": 5.0, - "st": 5, + "beat": 2.5, + "dur": 3.5, + "st": 7, "t": "patiently" + }, + { + "beat": 6.0, + "dur": 2.5, + "st": 3, + "t": "for" } ] }, "w-for-time-to-pass": { - "leadIn": 0.04, + "leadIn": 0.035, "beats": 12.5, "notes": [ { "beat": 0.0, - "dur": 1.5, + "dur": 2.0, "st": 5, "t": "for" }, { - "beat": 1.5, - "dur": 4.0, + "beat": 2.0, + "dur": 2.5, "st": 7, "t": "time" }, { - "beat": 5.5, - "dur": 3.5, + "beat": 4.5, + "dur": 4.0, "st": 3, "t": "to" }, { - "beat": 9.0, - "dur": 3.5, + "beat": 8.5, + "dur": 4.0, "st": 3, "t": "pass" } @@ -378,29 +372,29 @@ ] }, "w-n-for-time-to-pass": { - "leadIn": 0.06, - "beats": 6.5, + "leadIn": 0.0, + "beats": 5.5, "notes": [ { "beat": 0.0, - "dur": 2.0, - "st": 7, - "t": "For" + "dur": 0.5, + "st": 5, + "t": "for" }, { - "beat": 2.0, - "dur": 1.5, + "beat": 0.5, + "dur": 1.0, "st": 7, "t": "time" }, { - "beat": 3.5, - "dur": 1.5, + "beat": 1.5, + "dur": 2.5, "st": 3, "t": "to" }, { - "beat": 5.0, + "beat": 4.0, "dur": 1.5, "st": 3, "t": "pass" diff --git a/pop/loner/vox4/.manifest.json b/pop/loner/vox4/.manifest.json index 0972a4fcc..edc264b02 100644 --- a/pop/loner/vox4/.manifest.json +++ b/pop/loner/vox4/.manifest.json @@ -1,93 +1,97 @@ { "w-whole-line": { "slice": "f-whole-line", - "lead_in": 0.05, - "beats": 56.0, + "lead_in": 0.02, + "beats": 56.5, "renders": { - "lead": 27.99, - "8ve-a": 27.99, - "8ve-b": 27.99, - "low3": 27.99, - "low5": 27.99 + "lead": 28.315, + "8ve-a": 28.315, + "8ve-b": 28.315, + "low3": 28.315, + "low5": 28.315 }, "snaps": [ - "cur +5ms", - "led +240ms", - "up +30ms", + "curled +65ms", + "in +80ms", "myself -50ms", - "I +20ms", - "of -95ms", - "just +55ms", - "waiting -130ms", - "very +35ms", - "patiently +30ms", - "for -30ms", - "time -110ms", - "to +140ms", - "pass +165ms" + "i +180ms", + "think -45ms", + "of +225ms", + "a -40ms", + "stone -45ms", + "just +80ms", + "waiting +220ms", + "patiently +80ms", + "for -35ms", + "time +230ms", + "to -15ms", + "pass +130ms" ], "trims": [ - "led \u2212420ms", + "up \u2212420ms", "myself\u00b7b \u2212265ms", - "think \u2212890ms", + "think \u2212350ms", "stone \u2212675ms", "just \u2212335ms", "very \u2212520ms", - "patiently \u22121295ms", - "time \u2212855ms", - "to \u2212850ms" + "time \u221280ms", + "to \u2212615ms" ], "words": "the whole lyric, one take" }, "w-sitting-curled": { "slice": "f-sitting-curled", - "lead_in": 0.05, + "lead_in": 0.02, "beats": 11.0, "renders": { - "lead": 5.905, - "8ve-a": 5.905, - "8ve-b": 5.905, - "low3": 5.905, - "low5": 5.905 + "lead": 5.735, + "8ve-a": 5.735, + "8ve-b": 5.735, + "low3": 5.735, + "low5": 5.735 }, "snaps": [ - "cur +80ms", - "led +240ms", - "up +30ms", - "myself -50ms" + "up -20ms", + "in +60ms", + "myself +130ms" ], "trims": [ - "led \u2212400ms" + "up \u2212400ms" ], "words": "sitting curled up in myself" }, "w-i-think": { "slice": "f-i-think", - "lead_in": 0.06, + "lead_in": 0.02, "beats": 3.5, "renders": { - "lead": 2.225, - "8ve-a": 2.225, - "8ve-b": 2.225, - "low3": 2.225, - "low5": 2.225 + "lead": 2.305, + "8ve-a": 2.305, + "8ve-b": 2.305, + "low3": 2.305, + "low5": 2.305 }, - "snaps": [], + "snaps": [ + "think +80ms" + ], "trims": [], "words": "i think" }, "w-of-a-stone": { "slice": "f-of-a-stone", - "lead_in": 0.06, + "lead_in": 0.0, "beats": 8.0, "renders": { - "lead": 4.395, - "8ve-a": 4.395, - "8ve-b": 4.395, - "low3": 4.395, - "low5": 4.395 + "lead": 4.095, + "8ve-a": 4.095, + "8ve-b": 4.095, + "low3": 4.095, + "low5": 4.095 }, - "snaps": [], + "snaps": [ + "a -40ms", + "stone -40ms" + ], "trims": [], "words": "of a stone" }, @@ -103,7 +107,7 @@ "low5": 3.69 }, "snaps": [ - "waiting -5ms" + "waiting +195ms" ], "trims": [ "just \u2212340ms" @@ -112,42 +116,39 @@ }, "w-very-patiently": { "slice": "f-very-patiently", - "lead_in": 0.03, + "lead_in": 0.0, "beats": 8.5, "renders": { - "lead": 4.645, - "8ve-a": 4.645, - "8ve-b": 4.645, - "low3": 4.645, - "low5": 4.645 + "lead": 4.615, + "8ve-a": 4.615, + "8ve-b": 4.615, + "low3": 4.615, + "low5": 4.615 }, "snaps": [ - "patiently +30ms" - ], - "trims": [ - "very \u2212520ms" + "patiently -250ms", + "for -75ms" ], + "trims": [], "words": "very patiently" }, "w-for-time-to-pass": { "slice": "f-for-time-to-pass", - "lead_in": 0.04, + "lead_in": 0.035, "beats": 12.5, "renders": { - "lead": 6.59, - "8ve-a": 6.59, - "8ve-b": 6.59, - "low3": 6.59, - "low5": 6.59 + "lead": 6.695, + "8ve-a": 6.695, + "8ve-b": 6.695, + "low3": 6.695, + "low5": 6.695 }, "snaps": [ - "time -110ms", - "to +140ms", - "pass +165ms" + "time +245ms", + "to -75ms" ], "trims": [ - "time \u2212850ms", - "to \u2212850ms" + "to \u2212185ms" ], "words": "for time to pass" }, @@ -202,19 +203,18 @@ }, "w-n-for-time-to-pass": { "slice": "n-for-time-to-pass", - "lead_in": 0.06, - "beats": 6.5, + "lead_in": 0.0, + "beats": 5.5, "renders": { - "lead": 3.78, - "8ve-a": 3.78, - "8ve-b": 3.78, - "low3": 3.78, - "low5": 3.78 + "lead": 3.13, + "8ve-a": 3.13, + "8ve-b": 3.13, + "low3": 3.13, + "low5": 3.13 }, "snaps": [ - "time +240ms", - "to -75ms", - "pass +240ms" + "time +200ms", + "pass -150ms" ], "trims": [], "words": "for time to pass" -- 2.51.2