From 2ff7ed501cd8f6d428084e9ac864cfdc5cfba526 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Sun, 20 Sep 2026 11:29:31 -0700 Subject: [PATCH] Calibrate prepared audio from silent callback timestamps --- roadscore/prototype/mac_showcase.py | 16 ++++++++++++++-- roadscore/prototype/test_replay_sync.py | 14 +++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/roadscore/prototype/mac_showcase.py b/roadscore/prototype/mac_showcase.py index 08b6483cb9..db5d7c02c4 100644 --- a/roadscore/prototype/mac_showcase.py +++ b/roadscore/prototype/mac_showcase.py @@ -222,6 +222,7 @@ def audio_worker(a): done = False errors = [] clock_errors = [] + clock_observations = [] bridge = None flags = 0 max_drift = 0. @@ -231,7 +232,11 @@ def audio_worker(a): def callback(out, n, ti, status): nonlocal position,rendered,done,flags,max_drift,first_frame,callback_revision out.fill(0) - if anchor is None or bridge is None or done:return + if bridge is None: + if len(clock_observations)<24: + clock_observations.append((float(time.monotonic()),float(ti.currentTime),float(ti.outputBufferDacTime))) + return + if anchor is None or done:return now=time.monotonic() active_anchor=anchor expected=None;dac=None;start=position @@ -266,7 +271,14 @@ def audio_worker(a): server = control_server(a.project_root,a.out,shared,a.port,forwarder,follow_peer=forwarder is not None) write_json(a.out/'controls.json',{'url':f'http://127.0.0.1:{server.server_port}/'}) with sd.OutputStream(device=a.audio_device,samplerate=rate,channels=2,blocksize=960,dtype='float32',callback=callback) as stream: - bridge=StreamClockBridge.measure(lambda:stream.time) + calibration_deadline=time.monotonic()+2. + while len(clock_observations)<20: + if not stream.active:raise RuntimeError('Output stream stopped during silent clock calibration') + if time.monotonic()>=calibration_deadline:raise TimeoutError('Output clock did not provide 20 silent callbacks') + time.sleep(.01) + observations=list(clock_observations[:20]) + write_json(a.out/'stream_clock_calibration.json',dict(silent_callback_samples=observations)) + bridge=StreamClockBridge.from_callbacks(observations) (a.out/'prepared_ready').write_text('ready') while not done: sm.update(50);now=time.monotonic();controls.poll() diff --git a/roadscore/prototype/test_replay_sync.py b/roadscore/prototype/test_replay_sync.py index 6538907bef..79de4e581b 100644 --- a/roadscore/prototype/test_replay_sync.py +++ b/roadscore/prototype/test_replay_sync.py @@ -120,9 +120,21 @@ class ReplaySyncTests(unittest.TestCase): playback_clock=PreparedClock(),audio=np.zeros((480000,2),np.float32), processor=SimpleNamespace(process=lambda chunk,*args:(chunk,{})),state={}, controls=SimpleNamespace(selection=('recorded','recorded')),a=SimpleNamespace(muted=True), - errors=[],clock_errors=[]) + errors=[],clock_errors=[],clock_observations=[]) exec(compile(ast.Module(body=[callback],type_ignores=[]),str(path),'exec'),context) out=np.ones((960,2),np.float32) + # Startup gathers only a bounded set of silent observations. No stream.time + # getter, model anchor, source-frame processing or output is required. + saved_bridge=context['bridge'];context['bridge']=None + for i in range(30): + context['time'].monotonic=lambda i=i:99.+i*.02 + context['callback'](out,960,SimpleNamespace(currentTime=499.+i*.02,outputBufferDacTime=499.04+i*.02),False) + self.assertFalse(out.any()) + self.assertEqual(len(context['clock_observations']),24) + self.assertIsNone(context['position']) + calibrated=StreamClockBridge.from_callbacks(context['clock_observations'][:20]) + self.assertAlmostEqual(calibrated.offset,-400.) + context['bridge']=saved_bridge;context['time'].monotonic=lambda:100.08 context['callback'](out,960,SimpleNamespace(currentTime=500.,outputBufferDacTime=500.056),False) context['time'].monotonic=lambda:100.04 context['callback'](out,960,SimpleNamespace(currentTime=500.02,outputBufferDacTime=500.076),False)