diff --git a/pop/loner/bin/halo3.py b/pop/loner/bin/halo3.py index 21c2a17f04..d10bcb3140 100644 --- a/pop/loner/bin/halo3.py +++ b/pop/loner/bin/halo3.py @@ -65,6 +65,7 @@ BREATH = 0.14 HALO_DARK_HZ = 5500.0 HALO_BREATH_X = 1.5 UNVOICED_W = 0.18 # consonant share of any stretch +SILENT_W = 0.04 # a pause inside a word hardly stretches at all HOLD_RATIO = 1.8 # stretch beyond this → flat tone + vibrato BPM = 122.0 @@ -236,7 +237,7 @@ CHART = { # so "of" keeps beat 19 and stone its # bar 6 beat 2. 7: 2.0, - 8: 2.0, 9: 4.0, 10: 2.0, 11: 3.0, + 8: 2.0, 9: 5.0, 10: 2.0, 11: 3.0, # very longer, patiently slower 14: 4.0, 15: 5.0 }, # words whose syllables carry a melody must not @@ -244,15 +245,13 @@ CHART = { "nohold": [15], # patiently # source seconds, read off her onsets, for the # words whisper-1 mistimed. PRE-split indices. - # nothing after this in the slice belongs to - # the line — she goes again at 23.0 s - "end": 22.45, + "end": 24.25, "times": { 11: 12.70, # waiting 12: 14.40, # very - 14: 17.66, # for - 15: 18.54, # time - 16: 19.48, # to - 17: 21.20 }, # pass + 14: 18.54, # for + 15: 19.48, # time + 16: 21.20, # to + 17: 23.00 }, # pass # rest AFTER the given unit, in beats # "the in should start sooner — at start of # bar 2": the rest after up goes entirely, so @@ -272,7 +271,6 @@ CHART = { # 1-beat rest, which lands "just" square on the # bar 8 downbeat. "gaps": { 3: 0.5, # "in" leans in behind the downbeat - 8: 1.0, # air after think, before the octave } }, "w-sitting-curled": { "slice": "f-sitting-curled", "beats": 11.0 }, "w-i-think": { "slice": "f-i-think", "beats": 3.5 }, @@ -494,7 +492,21 @@ def build_warp(a, unit_src, beats, dursb, gapsb=None, rest_src=None, lead_b=0.0, of the phrase's beat 0 (the lead-in the C engine subtracts). """ F = len(a["f0c"]) + # Stretch weights: a vowel absorbs it (1.0), a consonant rides near + # 1:1 (0.18) — and SILENCE inside a word barely moves at all. Her + # 0.20 s pause between "patient" and "ly" was being stretched to + # 0.40 s along with the singing, which detached the last syllable so + # it read as a word of its own sitting next to "for". A pause is not + # a phoneme; lengthening it does not lengthen the word, it just + # breaks it in half. w = np.where(a["voiced"], 1.0, UNVOICED_W) + xs, fsr = a["x"], a["fs"] + spf = int(round(fsr * FRAME_S)) + nf = min(F, len(xs) // spf) + if nf > 0: + e = np.sqrt((xs[:nf * spf].reshape(nf, spf) ** 2).mean(axis=1)) + quiet = e <= (np.max(np.abs(xs)) or 1.0) * 10.0 ** (TRIM_GATE_DB / 20.0) + w[:nf][quiet] = SILENT_W ants = [] # consonant frames per unit for (s0, s1) in unit_src: v0 = s0 @@ -735,6 +747,20 @@ for name, ch in CHART.items(): first = dict(w, end=ts, t=w["t"] + "·a") second = dict(w, start=ts, t=w["t"] + "·b") words[ui:ui + 1] = [first, second] + # RE-MEASURE THE NOTE. f0_hz came from the aligner, over the span the + # aligner thought the word had. Once `times` repins a boundary or a + # split cuts a word in two, that number describes the wrong audio — + # which is how "for" kept its old pitch after being moved a whole + # word later. Read every note off her voice, over the span the chart + # will actually play. + for wd in words: + f_a = int(round((wd["start"] - t0_slice) / FRAME_S)) + f_b = int(round((wd["end"] - t0_slice) / FRAME_S)) + seg = a["f0"][max(0, f_a):max(1, min(f_b, len(a["f0"])))] + seg = seg[seg > 0] + if len(seg): + wd["f0_hz"] = float(np.median(seg)) + ch["units"] = derive_units(words, ch["beats"], ch.get("stretch"), ch.get("durs"), ch.get("gaps")) diff --git a/pop/loner/c/loner-chart.h b/pop/loner/c/loner-chart.h index 11281cb7c9..4ec9f2b663 100644 --- a/pop/loner/c/loner-chart.h +++ b/pop/loner/c/loner-chart.h @@ -14,25 +14,25 @@ typedef struct { const char *name; double leadIn; double beats; static const ChartNote w_whole_line_notes[] = { { 0.00, 2.00, 7 }, - { 2.00, 2.00, 7 }, + { 2.00, 2.00, 5 }, { 4.00, 2.50, 3 }, { 6.50, 1.50, 2 }, { 8.50, 1.50, 0 }, { 10.00, 2.00, 5 }, - { 12.00, 2.00, 5 }, + { 12.00, 2.00, 2 }, { 14.00, 2.00, -2 }, { 16.00, 2.00, -5 }, - { 19.00, 4.00, 12 }, + { 18.00, 5.00, 12 }, { 23.00, 2.00, 10 }, { 25.00, 3.00, 5 }, { 28.00, 3.50, 2 }, { 31.50, 4.50, 3 }, { 36.00, 4.00, 0 }, - { 40.00, 5.00, 7 }, - { 45.00, 2.00, 3 }, - { 47.00, 2.00, 7 }, - { 49.00, 4.50, 3 }, - { 53.50, 7.00, 3 }, + { 40.00, 5.00, 5 }, + { 45.00, 2.00, 5 }, + { 47.00, 4.50, 7 }, + { 51.50, 4.00, 3 }, + { 55.50, 3.00, 3 }, }; static const ChartNote w_sitting_curled_notes[] = { { 0.00, 3.00, 6 }, @@ -67,7 +67,7 @@ static const ChartNote w_for_time_to_pass_notes[] = { static const ChartNote w_n_getting_curled_notes[] = { { 0.00, 1.50, 5 }, { 1.50, 2.50, 3 }, - { 4.00, 0.50, 2 }, + { 4.00, 0.50, 1 }, { 4.50, 1.50, 0 }, { 6.00, 2.50, 4 }, { 8.50, 2.00, -2 }, @@ -76,7 +76,7 @@ static const ChartNote w_n_getting_curled_notes[] = { static const ChartNote w_n_stone_waiting_notes[] = { { 0.00, 2.00, 12 }, { 2.00, 2.00, 9 }, - { 4.00, 4.00, 4 }, + { 4.00, 4.00, 5 }, { 8.00, 4.00, 3 }, { 12.00, 3.00, 0 }, { 15.00, 2.00, 6 }, @@ -89,7 +89,7 @@ static const ChartNote w_n_for_time_to_pass_notes[] = { }; static const ChartPhrase CHART[] = { - { "w-whole-line", 0.490, 60.50, 20, w_whole_line_notes }, + { "w-whole-line", 0.490, 58.50, 20, 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 86db70aba4..f39a45cebb 100644 --- a/pop/loner/vox4/.chart.json +++ b/pop/loner/vox4/.chart.json @@ -1,7 +1,7 @@ { "w-whole-line": { "leadIn": 0.49, - "beats": 60.5, + "beats": 58.5, "voiced": [ [ 0.0, @@ -13,31 +13,39 @@ ], [ 11.9967, - 17.7408 + 17.7612 ], [ - 19.0015, + 17.995, 24.7457 ], [ 24.9998, - 31.0998 + 31.2015 ], [ 31.4963, - 43.2897 + 41.9883 ], [ - 43.3303, - 46.9192 + 42.0188, + 42.9033 + ], + [ + 42.944, + 44.9265 + ], + [ + 44.9977, + 46.8988 ], [ 47.0005, - 48.9017 + 55.0627 ], [ - 49.0033, - 61.3152 + 55.2558, + 59.3123 ] ], "notes": [ @@ -51,7 +59,7 @@ { "beat": 2.0, "dur": 2.0, - "st": 7, + "st": 5, "t": "sitting\u00b7b", "lead": 0.0 }, @@ -86,7 +94,7 @@ { "beat": 12.0, "dur": 2.0, - "st": 5, + "st": 2, "t": "myself\u00b7b", "lead": 0.1017 }, @@ -105,8 +113,8 @@ "lead": 0.0 }, { - "beat": 19.0, - "dur": 4.0, + "beat": 18.0, + "dur": 5.0, "st": 12, "t": "of", "lead": 0.2033 @@ -149,34 +157,34 @@ { "beat": 40.0, "dur": 5.0, - "st": 7, + "st": 5, "t": "patiently", "lead": 0.0 }, { "beat": 45.0, "dur": 2.0, - "st": 3, + "st": 5, "t": "for", - "lead": 0.0 + "lead": 0.0508 }, { "beat": 47.0, - "dur": 2.0, + "dur": 4.5, "st": 7, "t": "time", - "lead": 0.0508 + "lead": 0.0712 }, { - "beat": 49.0, - "dur": 4.5, + "beat": 51.5, + "dur": 4.0, "st": 3, "t": "to", - "lead": 0.0712 + "lead": 0.0 }, { - "beat": 53.5, - "dur": 7.0, + "beat": 55.5, + "dur": 3.0, "st": 3, "t": "pass", "lead": 0.0 @@ -189,10 +197,10 @@ "voiced": [ [ 0.0, - 9.821 + 9.8617 ], [ - 9.8413, + 9.882, 11.1223 ], [ @@ -244,7 +252,7 @@ "voiced": [ [ 0.0, - 3.4465 + 3.4567 ], [ 3.7617, @@ -278,7 +286,7 @@ "voiced": [ [ 0.0, - 5.795 + 5.8052 ], [ 5.9983, @@ -315,7 +323,7 @@ "voiced": [ [ 0.0, - 3.294 + 3.3855 ], [ 3.4973, @@ -345,10 +353,10 @@ "voiced": [ [ 0.0, - 7.1065 + 7.3912 ], [ - 7.137, + 7.4217, 8.4993 ], [ @@ -383,19 +391,19 @@ "voiced": [ [ 0.0, - 1.8707 + 1.8605 ], [ 2.0028, - 4.4835 + 4.4937 ], [ - 4.9817, + 4.6767, 8.4993 ], [ - 8.8755, - 12.383 + 8.662, + 12.4135 ] ], "notes": [ @@ -435,22 +443,22 @@ "voiced": [ [ 0.0, - 1.6572 + 1.6368 ], [ - 1.6673, - 4.6563 + 1.647, + 4.514 ], [ - 4.7275, + 4.5343, 7.4318 ], [ 7.4623, - 8.6722 + 8.5095 ], [ - 8.7332, + 8.5298, 11.1427 ] ], @@ -472,7 +480,7 @@ { "beat": 4.0, "dur": 0.5, - "st": 2, + "st": 1, "t": "up", "lead": 0.0 }, @@ -512,18 +520,18 @@ "voiced": [ [ 0.0, - 3.2432 + 3.3042 ], [ - 3.3652, + 3.4363, 8.0012 ], [ - 8.2248, - 16.3277 + 8.0622, + 16.3073 ], [ - 16.348, + 16.3378, 17.3443 ] ], @@ -545,7 +553,7 @@ { "beat": 4.0, "dur": 4.0, - "st": 4, + "st": 5, "t": "just", "lead": 0.0 }, @@ -578,18 +586,18 @@ "voiced": [ [ 0.0, - 0.3253 + 0.2338 ], [ - 0.3762, - 1.5657 + 0.3152, + 1.5047 ], [ - 1.83, - 4.1582 + 1.6267, + 4.0057 ], [ - 4.3615, + 4.0972, 6.3643 ] ], diff --git a/pop/loner/vox4/.manifest.json b/pop/loner/vox4/.manifest.json index c6cb68340b..47bbcf869c 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": 60.5, + "beats": 58.5, "renders": { - "lead": 30.645, - "8ve-a": 30.645, - "8ve-b": 30.645, - "low3": 30.645, - "low5": 30.645 + "lead": 29.66, + "8ve-a": 29.66, + "8ve-b": 29.66, + "low3": 29.66, + "low5": 29.66 }, "snaps": [ "curled +65ms", @@ -23,10 +23,10 @@ "waiting -100ms", "very +65ms", "patiently +80ms", - "for +35ms", - "time -30ms", - "to -90ms", - "pass +60ms" + "for -30ms", + "time -90ms", + "to +60ms", + "pass +5ms" ], "trims": [ "up \u2212420ms", @@ -35,8 +35,7 @@ "stone \u2212675ms", "just \u2212195ms", "very \u2212520ms", - "patiently \u2212480ms", - "to \u2212855ms" + "time \u2212855ms" ], "words": "the whole lyric, one take" },