Files
Sylpheed/tools/re-capture/menu_loop_firstpass.py
sylph-decoder e1f2a7f714 re: the menu loop starts at 9.44 s -- measured, and the fix was scheduling
Tailing the log from BEFORE the music starts cut the unsampled backlog from 616
samples spanning offsets 32..2,559,033 to 125 spanning 32..515,239, so the first
pass is sampled like any later cycle. Offsets below loop_start play exactly once,
which is why the previous run could not measure them.

Wraps at 96.46 / 158.33 / 220.21 s, gaps 61.87 / 61.87, both contexts together.

Two derivations, neither converting bits to seconds:
  (a) time to read_offset crossing loop_start, plus a 1.33 s head correction at a
      rate measured on 748 timestamped samples of that same stretch
  (b) first pass (offset 32 -> loop_end) minus the cycle
Both give 9.44 s on both contexts -- four numbers, one value.

So the loop region is [9.44, 71.31] s of an 87.744 s wave, cycling every 61.87 s.
The first 9.44 s is an intro played once; the last 16.4 s, the fade-out
bgm-two-stems.md documents, is never played at all.

The decoder reads ahead of playback, but both endpoints are read_offset events so
the lead cancels in the difference. One boot, one bank.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 10:13:14 +00:00

85 lines
3.1 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Measure where the menu loop STARTS, by sampling the first pass properly.
`menu-bgm-loop-fields-conflict.md` settles the loop's *length* (61.81 s, three
wraps) but not where in the wave it begins. Offsets below `loop_start` are played
**exactly once**, before the first wrap — and the previous trace started after the
music and swallowed that whole stretch in one read, stamping 616 samples spanning
offsets 32…2 559 033 at `t=0.002`.
The fix is scheduling, not analysis: **tail the log from before the music starts**,
so the first pass is sampled at the same cadence as every later cycle. Then
loop_start_time = (first pass, offset 32 → loop_end) (cycle, wrap to wrap)
with no bits→seconds conversion anywhere — the step that is refuted (the rate
varies 4.4 % within one stream).
⚠️ Context ids are reused: `ADV`'s streams are also ctx0/1/2. Everything is
timestamped and the BGM start time is recorded, so the analysis can discard
anything before it rather than relying on the ids.
menu_loop_firstpass.py LOG OUTDIR [hold_s]
"""
import os
import re
import subprocess
import sys
import time
LOG, OUT = sys.argv[1], sys.argv[2]
HOLD = float(sys.argv[3]) if len(sys.argv) > 3 else 200
PAD = os.path.join(os.path.dirname(os.path.abspath(__file__)), "pad.py")
ADV = {1_294_336, 1_118_208, 1_171_456}
BGM = {3_876_864, 3_930_112}
SIZE = re.compile(rb"byte_size=(\d+)")
LOOP = re.compile(rb"XmaContext (\d+): Looped Data: (\d+) < (\d+) \(Start: (\d+)\)")
T0 = time.time()
off = 0
adv_seen = False
bgm_at = None
taps = 0
tsv = open(f"{OUT}/readoff.tsv", "w")
tsv.write("# t_s\tctx\tread_offset\tloop_end\tloop_start\n")
ev = open(f"{OUT}/events.tsv", "w")
def tap():
subprocess.run([sys.executable, PAD, "tap", "A", "0.12"], check=False)
print(f"[{time.time()-T0:7.1f}s] tapped A", flush=True)
while time.time() - T0 < 900:
try:
with open(LOG, "rb") as f:
f.seek(off)
chunk = f.read()
off += len(chunk)
except FileNotFoundError:
time.sleep(0.3); continue
now = time.time() - T0
sizes = {int(m.group(1)) for m in SIZE.finditer(chunk)}
# the loop trace runs from the very first poll, so the first pass is sampled
for m in LOOP.finditer(chunk):
tsv.write(f"{now:.3f}\t{int(m.group(1))}\t{int(m.group(2))}\t"
f"{int(m.group(3))}\t{int(m.group(4))}\n")
tsv.flush()
if sizes & ADV and not adv_seen:
adv_seen = True
print(f"[{now:7.1f}s] ADV — intro playing", flush=True)
ev.write(f"{now:.3f}\tadv\n"); ev.flush()
time.sleep(3); tap(); taps += 1
if sizes & BGM and bgm_at is None:
bgm_at = now
print(f"[{now:7.1f}s] BGM_103 — ON THE MENU, holding {HOLD}s", flush=True)
ev.write(f"{now:.3f}\tbgm\n"); ev.flush()
if bgm_at is None and adv_seen and taps <= 4 and now % 25 < 0.4:
tap(); taps += 1; time.sleep(1)
if bgm_at is not None and now - bgm_at > HOLD:
print(f"[{now:7.1f}s] hold complete", flush=True)
break
time.sleep(0.3)
tsv.close(); ev.close()
print(f"bgm_at={bgm_at} taps={taps}", flush=True)