From c81167530ce2defb8c384ed82cf92c72aef406c1 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Tue, 18 Aug 2026 01:09:50 -0400 Subject: [PATCH] loner: "very" becomes two notes, "waiting" takes bar 8, "pa" holds a full bar and drops a scale step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @jeffrey: "the pa in patiently should be pitched down i think · it feels too fast and abrupt, maybe it needs to be stretched to be a full bar · and can waiting start on bar 8:1 · and very needs to be two notes". The chart gains `shift`: N semitones on one unit, applied to her f0 AND to the chart note, so the pluck doubles the new pitch instead of the one she sang. "pa" goes down a scale step, F4 → D#4, so the phrase stops leaping up from very's A#3 before it descends — and it now holds bar 10 outright rather than two beats. "very" splits into ve · ry, and the split is not cosmetic: measured separately they are A#3 and G#3, two real notes that the single unit had been averaging into one. just and waiting take a bar each, so waiting starts on bar 8 beat 1. Also checked the sync he asked about, and the file is clean. The audio's first kick sits at 0.4910 s against a 0.490 s lead-in. For the video I measured the flashing kick cell sampled slightly off the beat, where it clears the playhead — mean +0.58 px, worst 1.2 px, i.e. +3.0 ms mean and 6.2 ms worst against a ±8.3 ms floor from 60 fps itself. Earlier attempts read −8 px only because the detector was finding the pink PLAYHEAD (4 px wide, and it crosses the kick lane) rather than the thing being measured. What likely reads as "a little early" is real but not a timing error: the roll scrolls through the pickup beat before the first kick sounds, so the eye sees motion before the ear gets anything. --- pop/loner/bin/halo3.py | 34 ++++++++++++++++++--- pop/loner/c/loner-chart.h | 23 +++++++------- pop/loner/vox4/.chart.json | 57 ++++++++++++++++++++--------------- pop/loner/vox4/.manifest.json | 17 ++++++----- 4 files changed, 82 insertions(+), 49 deletions(-) diff --git a/pop/loner/bin/halo3.py b/pop/loner/bin/halo3.py index b7f67182a7..30aa2eabe9 100644 --- a/pop/loner/bin/halo3.py +++ b/pop/loner/bin/halo3.py @@ -239,14 +239,23 @@ CHART = { 7: 2.0, 8: 2.0, 9: 4.0, 10: 3.0, 11: 3.0, # very longer, patiently slower - 14: 4.0, - # pa 2 · tient 1.5 · ly 1.5 = the - # same 5 beats patiently had - 15: 2.0, 16: 1.5, 17: 1.5 }, + # the tail on bars: just and waiting + # a bar each (waiting on bar 8:1), + # very as TWO notes, and "pa" held a + # full bar because at 2 beats it was + # too fast and abrupt + 12: 4.0, 13: 4.0, 14: 2.0, 15: 2.0, + 16: 4.0, 17: 1.5, 18: 1.5 }, # words whose syllables carry a melody must not # be flattened to one tone by THE HOLD # patiently is pa·tient·ly, three notes - "sylls": { 13: [(None, "pa"), (16.80, "tient"), (17.70, "ly")] }, + "sylls": { 12: [(None, "ve"), (15.05, "ry")], + 13: [(None, "pa"), (16.80, "tient"), + (17.70, "ly")] }, + # "pa" sings F4, a leap up from very's A#3. + # Down one scale step to D#4 keeps the phrase + # from jumping before it descends. + "shift": { 16: -2 }, # source seconds, read off her onsets, for the # words whisper-1 mistimed. PRE-split indices. "end": 24.25, @@ -830,6 +839,20 @@ for name, ch in CHART.items(): f0_o[o0:o1] = np.where(voiced_o[o0:o1], f0_o[o0:o1] * (1 - blend) + tgt * vib * blend, 0.0) + # PER-SYLLABLE TRANSPOSE. Sometimes the note she sang is not the note + # the score wants — @jeffrey: "the pa in patiently should be pitched + # down". `shift` moves one unit by N semitones, in her f0 AND in the + # chart, so the pluck doubles the new note rather than the old one. + for ui, semis in (ch.get("shift") or {}).items(): + if not (0 <= ui < len(ch["units"])): + continue + bt, du = ch["units"][ui] + o0 = Z + int(round(bt * SPB / FRAME_S)) + o1 = Z + int(round((bt + du) * SPB / FRAME_S)) + o0, o1 = max(0, o0), min(len(f0_o), o1) + if o1 > o0: + f0_o[o0:o1] *= 2.0 ** (semis / 12.0) + renders = {} out, _ = synth_from(a, idx, f0_o, fade=fade, rise=rise, rests=rests) sf.write(os.path.join(VOX4, f"{name}.wav"), out, fs) @@ -884,6 +907,7 @@ for name, ch in CHART.items(): notes = [] for u, (wd, (beat, durb)) in enumerate(zip(words, ch["units"])): st = int(np.round(12.0 * np.log2(wd["f0_hz"] / TONIC))) if wd["f0_hz"] else 0 + st += int((ch.get("shift") or {}).get(u, 0)) notes.append((beat, durb, st, wd["t"].strip(), round(ants[u] * FRAME_S / SPB, 4) if u < len(ants) else 0.0)) chart_c.append((name, lead_in, beats_total, notes)) diff --git a/pop/loner/c/loner-chart.h b/pop/loner/c/loner-chart.h index 6b64357c41..58e11f4d61 100644 --- a/pop/loner/c/loner-chart.h +++ b/pop/loner/c/loner-chart.h @@ -25,16 +25,17 @@ static const ChartNote w_whole_line_notes[] = { { 18.00, 4.00, 12 }, { 22.00, 3.00, 10 }, { 25.00, 3.00, 5 }, - { 28.00, 3.50, 2 }, - { 31.50, 4.50, 3 }, - { 36.00, 4.00, 0 }, - { 40.00, 2.00, 7 }, - { 42.00, 1.50, 5 }, - { 43.50, 1.50, 3 }, - { 45.00, 2.00, 5 }, - { 47.00, 4.50, 7 }, - { 51.50, 4.00, 3 }, - { 55.50, 3.00, 3 }, + { 28.00, 4.00, 2 }, + { 32.00, 4.00, 3 }, + { 36.00, 2.00, 0 }, + { 38.00, 2.00, -2 }, + { 40.00, 4.00, 5 }, + { 44.00, 1.50, 5 }, + { 45.50, 1.50, 3 }, + { 47.00, 2.00, 5 }, + { 49.00, 4.50, 7 }, + { 53.50, 4.00, 3 }, + { 57.50, 3.00, 3 }, }; static const ChartNote w_sitting_curled_notes[] = { { 0.00, 3.00, 6 }, @@ -91,7 +92,7 @@ static const ChartNote w_n_for_time_to_pass_notes[] = { }; static const ChartPhrase CHART[] = { - { "w-whole-line", 0.490, 58.50, 22, w_whole_line_notes }, + { "w-whole-line", 0.490, 60.50, 23, 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 }, diff --git a/pop/loner/vox4/.chart.json b/pop/loner/vox4/.chart.json index de3c8308c5..eb5224daa1 100644 --- a/pop/loner/vox4/.chart.json +++ b/pop/loner/vox4/.chart.json @@ -1,7 +1,7 @@ { "w-whole-line": { "leadIn": 0.49, - "beats": 58.5, + "beats": 60.5, "voiced": [ [ 0.0, @@ -21,27 +21,27 @@ ], [ 24.9998, - 31.2015 + 31.6997 ], [ - 31.4963, - 41.8968 + 32.0047, + 43.8997 ], [ - 41.9985, - 44.9367 + 44.0013, + 46.9395 ], [ - 44.9977, - 46.8988 + 47.0005, + 48.9017 ], [ - 47.0005, - 55.0627 + 49.0033, + 57.0655 ], [ - 55.2558, - 59.3123 + 57.2587, + 61.3152 ] ], "notes": [ @@ -131,69 +131,76 @@ }, { "beat": 28.0, - "dur": 3.5, + "dur": 4.0, "st": 2, "t": "just", "lead": 0.0 }, { - "beat": 31.5, - "dur": 4.5, + "beat": 32.0, + "dur": 4.0, "st": 3, "t": "waiting", "lead": 0.244 }, { "beat": 36.0, - "dur": 4.0, + "dur": 2.0, "st": 0, - "t": "very", + "t": "ve", "lead": 0.0 }, { - "beat": 40.0, + "beat": 38.0, "dur": 2.0, - "st": 7, + "st": -2, + "t": "ry", + "lead": 0.0 + }, + { + "beat": 40.0, + "dur": 4.0, + "st": 5, "t": "pa", "lead": 0.0 }, { - "beat": 42.0, + "beat": 44.0, "dur": 1.5, "st": 5, "t": "tient", "lead": 0.0915 }, { - "beat": 43.5, + "beat": 45.5, "dur": 1.5, "st": 3, "t": "ly", "lead": 0.0 }, { - "beat": 45.0, + "beat": 47.0, "dur": 2.0, "st": 5, "t": "for", "lead": 0.0508 }, { - "beat": 47.0, + "beat": 49.0, "dur": 4.5, "st": 7, "t": "time", "lead": 0.0712 }, { - "beat": 51.5, + "beat": 53.5, "dur": 4.0, "st": 3, "t": "to", "lead": 0.0 }, { - "beat": 55.5, + "beat": 57.5, "dur": 3.0, "st": 3, "t": "pass", diff --git a/pop/loner/vox4/.manifest.json b/pop/loner/vox4/.manifest.json index 93a401f8f9..0a1e4dc78b 100644 --- a/pop/loner/vox4/.manifest.json +++ b/pop/loner/vox4/.manifest.json @@ -2,13 +2,13 @@ "w-whole-line": { "slice": "f-whole-line", "lead_in": 0.49, - "beats": 58.5, + "beats": 60.5, "renders": { - "lead": 29.66, - "8ve-a": 29.66, - "8ve-b": 29.66, - "low3": 29.66, - "low5": 29.66 + "lead": 30.645, + "8ve-a": 30.645, + "8ve-b": 30.645, + "low3": 30.645, + "low5": 30.645 }, "snaps": [ "curled +65ms", @@ -21,7 +21,8 @@ "stone -45ms", "just +80ms", "waiting -100ms", - "very +65ms", + "ve +65ms", + "ry +160ms", "pa +80ms", "tient -15ms", "ly -5ms", @@ -36,7 +37,7 @@ "think \u2212350ms", "stone \u2212675ms", "just \u2212195ms", - "very \u2212520ms", + "ry \u2212520ms", "tient \u2212480ms", "time \u2212855ms" ], -- 2.51.2