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"