From 2c7f4992bbffdad51acf543e749518aea1c374e6 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Sat, 19 Sep 2026 19:37:23 -0700 Subject: [PATCH] Guide isolated chime calibration and calculate completed taps automatically --- .../the_galaxy/assets/mobile/index.html | 2 +- .../system/the_galaxy/assets/mobile/js/app.js | 2 +- .../assets/mobile/js/views/RoadScore.js | 28 +++++++++++-------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/starpilot/system/the_galaxy/assets/mobile/index.html b/starpilot/system/the_galaxy/assets/mobile/index.html index 083aa2938f..ecbc12ada3 100644 --- a/starpilot/system/the_galaxy/assets/mobile/index.html +++ b/starpilot/system/the_galaxy/assets/mobile/index.html @@ -36,7 +36,7 @@
- + diff --git a/starpilot/system/the_galaxy/assets/mobile/js/app.js b/starpilot/system/the_galaxy/assets/mobile/js/app.js index 2adbc73bee..0eb4a2aebc 100644 --- a/starpilot/system/the_galaxy/assets/mobile/js/app.js +++ b/starpilot/system/the_galaxy/assets/mobile/js/app.js @@ -1,4 +1,4 @@ -import { RoadScore } from "./views/RoadScore.js?v=calibration-bars-2" +import { RoadScore } from "./views/RoadScore.js?v=calibration-markers-3" import { createApp, h } from "vue" import { AppShell } from "./components/AppShell.js?v=roadscore-calibration-2" import { Home } from "./views/Home.js" diff --git a/starpilot/system/the_galaxy/assets/mobile/js/views/RoadScore.js b/starpilot/system/the_galaxy/assets/mobile/js/views/RoadScore.js index 7311c7db2d..ed2ca0a425 100644 --- a/starpilot/system/the_galaxy/assets/mobile/js/views/RoadScore.js +++ b/starpilot/system/the_galaxy/assets/mobile/js/views/RoadScore.js @@ -2,7 +2,7 @@ import { GalaxySection } from '../components/GalaxySection.js' export const RoadScore = { name: 'RoadScore', components: { GalaxySection }, - data() { return { status: {}, error: '', busy: false, profile: 'prism', latency: 0, session: null, taps: 0, result: null, timer: null, calibrationTimer: null, clockOffset: 0, uncertainty: null, testMode: false, beat: '', sessionStarted: 0, latencyInitialized: false, outputIdentity: null, testClicks: [], animation: null, countIn: 8, bpm: 100, beatIndex: -1, cue: 'Getting ready…', lastTapInput: -1000 } }, + data() { return { status: {}, error: '', busy: false, profile: 'prism', latency: 0, session: null, taps: 0, result: null, timer: null, calibrationTimer: null, clockOffset: 0, uncertainty: null, testMode: false, beat: '', sessionStarted: 0, latencyInitialized: false, outputIdentity: null, testClicks: [], animation: null, countIn: 8, bpm: 100, beatIndex: -1, cue: 'Getting ready…', lastTapInput: -1000, targetTaps: 12, finishing: false } }, mounted() { window.addEventListener('keydown', this.keyTap); this.refresh(); this.timer = setInterval(() => this.refresh(), 2000) }, beforeUnmount() { window.removeEventListener('keydown', this.keyTap); clearInterval(this.timer); clearInterval(this.calibrationTimer); cancelAnimationFrame(this.animation); if (this.session) this.action('calibration_cancel', {session: this.session}) }, methods: { @@ -46,7 +46,7 @@ export const RoadScore = { if (best.uncertainty > 25) throw new Error('Connection timing is too uncertain. Try again on a faster connection.') this.clockOffset = best.offset; this.uncertainty = best.uncertainty const value = await this.quick(test ? 'test' : 'calibration_start', {attended: true}) - this.session = value.session; this.taps = 0; this.result = null; this.testMode = test; this.sessionStarted = performance.now() + this.targetTaps = value.target_taps || 12; this.finishing = false; this.lastTapInput = -1000; this.session = value.session; this.taps = 0; this.result = null; this.testMode = test; this.sessionStarted = performance.now() this.calibrationTimer = setInterval(() => this.pollClicks(), 100) this.countIn = value.count_in || 8; this.bpm = value.bpm || 100; this.beatIndex = -1; this.testClicks = []; this.cue = 'Getting ready…'; this.animateBeat() } catch (error) { this.error = error.message } @@ -60,7 +60,10 @@ export const RoadScore = { const value = await this.quick('calibration_poll', {session: token}) if (this.session !== token) return this.testClicks = value.clicks - if (performance.now() - this.sessionStarted > (this.testMode ? 14000 : 30000)) await this.cancel() + if (performance.now() - this.sessionStarted > (this.testMode ? 14000 : 55000)) { + if (!this.testMode && this.taps >= 8) await this.finish() + else { await this.cancel(); if (!this.testMode) this.error = 'Not enough chime taps. Start again and tap only when you hear each chime.' } + } } catch (error) { if (this.session === token) { this.error = error.message; this.session = null; clearInterval(this.calibrationTimer) } } finally { this.polling = false } }, @@ -70,8 +73,8 @@ export const RoadScore = { const first = this.testClicks.find(click => click.beat === 0) const index = first ? Math.floor((now - first.server_ms) / (60000 / this.bpm)) : -1 this.beatIndex = index - this.beat = index >= 0 ? String(index % 4 + 1) : '·' - this.cue = index < 0 ? 'Getting ready…' : index < this.countIn && !this.testMode ? `Listen · count-in bar ${Math.floor(index / 4) + 1} of 2` : this.testMode ? 'Watch and listen' : index < 24 ? `Tap now · bar ${Math.floor(index / 4) + 1}` : 'Finish and calculate' + this.beat = index >= 0 && index < this.countIn ? String(index % 4 + 1) : '♪' + this.cue = index < 0 ? 'Getting ready…' : index < this.countIn && !this.testMode ? `Listen · count-in bar ${Math.floor(index / 4) + 1} of 2` : this.testMode ? 'Watch and listen' : 'Tap once when you hear each chime · wait through the gaps' this.animation = requestAnimationFrame(() => this.animateBeat()) }, keyTap(event) { @@ -82,13 +85,13 @@ export const RoadScore = { if (!event.repeat) this.tap(event) }, async tap(event) { - if (!this.session || this.testMode || this.beatIndex < this.countIn || this.taps >= 16) return + if (!this.session || this.testMode || this.beatIndex < this.countIn || this.taps >= this.targetTaps) return const stamp = performance.now() if (stamp - this.lastTapInput < 150) return this.lastTapInput = stamp try { const value = await this.quick('calibration_tap', {session: this.session, server_ms: stamp + this.clockOffset, uncertainty_ms: this.uncertainty}) - this.taps = value.accepted_taps; this.error = '' + this.taps = value.accepted_taps; this.error = ''; if (this.taps >= this.targetTaps) await this.finish() } catch (error) { this.error = error.message } }, async cancel() { @@ -97,10 +100,13 @@ export const RoadScore = { if (token) await this.action('calibration_cancel', {session: token}) }, async finish() { + if (this.finishing || !this.session) return + this.finishing = true clearInterval(this.calibrationTimer); cancelAnimationFrame(this.animation) const token = this.session; this.session = null this.result = await this.action('calibration_result', {session: token}) if (this.result) this.latency = this.result.latency_ms + this.finishing = false }, async saveLatency(value) { const result = await this.action('settings', {latency_ms: Number(value)}); if (result) { this.result = null; this.latency = Number(value) } }, }, @@ -123,15 +129,15 @@ export const RoadScore = {

Current: {{status.latency_ms == null ? 'Unavailable' : status.latency_ms + ' ms'}}

Delay music cues on screen to align with heard audio. This cannot make live sound arrive earlier or read ahead on the route.

-

100 BPM · 4/4. Listen to two full bars (8 clicks), then tap every beat from bar three for four bars. On a computer, press Space. The estimate includes human timing bias; a missed beat can shift it by a whole beat.

+

Listen to two full bars (8 clicks), then tap only when you hear each two-tone chime. The gaps vary: wait and listen, don’t keep tapping the beat. Use the screen or Space. After 12 taps, the estimate calculates automatically. It includes your reaction time.

-

Suggested correction: {{result.latency_ms}} ms · {{result.accepted_taps}} accepted taps. Review and save above.

+

Suggested correction: {{result.latency_ms}} ms · {{result.accepted_taps}} accepted taps · {{result.spread_ms}} ms spread. Includes tap reaction time. Review and Save above; the saved value has not changed.

{{status.session_muted ? 'Audio is muted by the session owner. Unmute intentionally before calibration.' : 'Connect an output and finish playback or judging before calibration.'}}