From 0ed635026d45daafe8b7e98e020de741a9a8addc Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Sat, 19 Sep 2026 18:04:01 -0700 Subject: [PATCH] Apply per-speaker calibration only to displayed music cues --- roadscore/prototype/app.py | 4 +++- roadscore/prototype/operator_output.py | 3 ++- roadscore/prototype/test_operator_output.py | 19 +++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/roadscore/prototype/app.py b/roadscore/prototype/app.py index 2a6511fbd5..78be11af39 100644 --- a/roadscore/prototype/app.py +++ b/roadscore/prototype/app.py @@ -35,6 +35,8 @@ output_device=select_device(sd.query_devices(),output_metadata) if output_metada if output_device is not None: sd.check_output_settings(device=output_device,channels=2,dtype='float32',samplerate=48000) output_metadata['portaudio_device']=output_device +from operator_output import PresentationDelay +presentation_delay=PresentationDelay(root,output_provider=lambda: {'id':output_metadata['output_identity']} if output_metadata.get('bluetooth_selected') else None) (run/'output_device.json').write_text(json.dumps(output_metadata,indent=2)+'\n') if os.environ.get('ROADSCORE_RESIDENT')=='1': import atexit @@ -390,7 +392,7 @@ try: if songform:snapshot.update(songform.snapshot()) if composition:snapshot.update(composition.snapshot(frames/rate)) if gestures:snapshot.update(gestures.status()) - trace.write(json.dumps(snapshot)+'\n');f=run/'status.tmp';f.write_text(json.dumps(snapshot));f.replace(run/'status.json') + trace.write(json.dumps(snapshot)+'\n');f=run/'status.tmp';f.write_text(json.dumps(presentation_delay.apply(snapshot)));f.replace(run/'status.json') if time.monotonic()-last_progress>15:raise RuntimeError('Replay model input stalled') finally: audio_started=False diff --git a/roadscore/prototype/operator_output.py b/roadscore/prototype/operator_output.py index 855d1cf711..c47cd4ed65 100644 --- a/roadscore/prototype/operator_output.py +++ b/roadscore/prototype/operator_output.py @@ -294,7 +294,8 @@ class OutputOwner: PRESENTATION_FIELDS = ('phase', 'kind', 'amount', 'activation', 'strength', 'section', 'next_section', - 'gesture_active', 'gesture_queued', 'turn_signal_music') + 'gesture_active', 'gesture_queued', 'turn_signal_music', 'lead', 'predicted_peak', 'scheduled', + 'signal_shaker', 'core_apex', 'alert_accent', 'engagement_presentation') class PresentationDelay: diff --git a/roadscore/prototype/test_operator_output.py b/roadscore/prototype/test_operator_output.py index 54a29b76c5..75c84d5d07 100644 --- a/roadscore/prototype/test_operator_output.py +++ b/roadscore/prototype/test_operator_output.py @@ -86,6 +86,25 @@ class OutputTests(unittest.TestCase): self.assertEqual(shown['phase'],'curve');self.assertEqual(shown['readiness'],'DEGRADED') self.assertEqual(shown['buffered'],80);self.assertEqual(next_snapshot['phase'],'apex') + def test_rendered_cues_delay_without_touching_raw_audio_clock_or_core(self): + import copy + self.owner.dispatch('set_latency',latency_ms=250) + delay=m.PresentationDelay(self.root,lambda:self.output,self.clock) + raw=dict(signal_shaker={'rendered_active':True},core_apex={'rendered_active':True},alert_accent={'rendered_active':False},engagement_presentation={'active':True},elapsed=50,route_t=10,source_cutoff_ns=123,buffered=80,readiness='READY') + original=copy.deepcopy(raw) + first=delay.apply(raw) + self.assertNotIn('signal_shaker',first) + self.assertEqual(first['elapsed'],50);self.assertEqual(first['source_cutoff_ns'],123) + self.clock.value+=.26 + current={**raw,'signal_shaker':{'rendered_active':False},'route_t':11,'elapsed':50.26,'readiness':'DEGRADED'} + shown=delay.apply(current) + self.assertTrue(shown['signal_shaker']['rendered_active']) + self.assertEqual(shown['readiness'],'DEGRADED');self.assertEqual(shown['route_t'],11) + self.assertEqual(raw,original);self.assertFalse(current['signal_shaker']['rendered_active']) + self.output=None;self.clock.value+=3 + bypass=delay.apply(current) + self.assertEqual(bypass['presentation_latency_ms'],0);self.assertFalse(bypass['signal_shaker']['rendered_active']) + def test_watchdog_cancels_own_audio_and_releases_leases(self): token=self.owner.dispatch('calibration_start',attended=True)['session'] sink=self.owner.session['sink']