From 03c4ef9bdd8d8bd5b63a5455b9107f2d7c98b0bd Mon Sep 17 00:00:00 2001 From: prompt.ac/@jeffrey Date: Tue, 18 Aug 2026 15:11:24 +0000 Subject: [PATCH] loner timeline: SYNC_MS — compensate the display, because the file is already exact @jeffrey: "the audio still comes sooner than visuals". Checked the file end to end before touching anything, and it is right: both streams start at PTS 0; the pickup kick sits at 0.0010 s in the render WAV and at 0.0010 s when decoded back out of the finished MP4, so neither the segment concat nor the AAC mux moves it; and the roll's geometry measures +3.0 ms mean, worst 1.1 px, against the beat grid. There is nothing left to fix inside the file. What remains is downstream of it. An LCD, a compositor and a player buffer put the picture on the retina one to three frames after the sound reaches the ear — so on any setup, sound arrives first. That is not something a correct file can fix; it has to be compensated. render_frame now draws the state of (t + SYNC_MS) at media time t, which hands the picture that head start back. Default 45 ms, about 2.7 frames. SYNC_MS=0 shows the raw file, a larger value suits a slower display. --- pop/loner/bin/timeline.py | 14 +++++++++++++- 1 file(s) changed, 13 insertion(s)(+), 1 deletion(s)(-) diff --git a/pop/loner/bin/timeline.py b/pop/loner/bin/timeline.py --- a/pop/loner/bin/timeline.py +++ b/pop/loner/bin/timeline.py @@ -95,6 +95,7 @@ PASSES = [0.0] # ONE pass, and no count-in: BARS_MAX = 16 # nothing past bar 16 KICK_BEATS = int(min(LINE_BARS + 1, BARS_MAX) * 4) # first word IS the downbeat TOTAL_BEATS = min(LINE_BARS + 2, BARS_MAX) * 4 +SYNC_MS = float(os.environ.get("SYNC_MS", "45")) # display-latency lead STRIP_PAD = 2.0 # beats of strip before beat 0, # so the pickup has somewhere to live @@ -357,7 +358,18 @@ dh.text((40, 84), sub, font=f_tiny, fill=BLUE) hdr_np = np.asarray(hdr, dtype=np.uint8) def render_frame(i): - t = (i + 0.5) / FPS # centre of the displayed interval + # SYNC_MS — display latency compensation, not a file fix. + # @jeffrey: "the audio still comes sooner than visuals". The file is + # exact: both streams start at PTS 0, the pickup kick sits at 0.0010 s + # in the render WAV and at 0.0010 s decoded back out of the MP4, and + # the roll's geometry measures +3.0 ms mean against the beat grid. The + # remaining offset is downstream of the file — an LCD and a compositor + # and a player buffer put the picture on the retina one to three + # frames after the sound reaches the ear, so sound always arrives + # first. Drawing the state of (t + SYNC_MS) at media time t hands the + # picture that head start back. Tune with SYNC_MS=0 to see the raw + # file, or a larger number on a slower display. + t = (i + 0.5) / FPS + SYNC_MS / 1000.0 beat = (t - LEAD_IN) / SPB # t=0 is the pickup, not beat 0 # SUB-PIXEL SCROLL. Rounding the scroll to a whole pixel left up to # half a pixel of error — 2.6 ms at 96 px a beat — on every frame, -- tangled.sh