mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-04 13:24:13 +08:00
Extend timing alignment test to three measures
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"""Explicitly attended calibration process; no import-time audio output."""
|
||||
import time
|
||||
RATE=48000
|
||||
from operator_output import COUNT, COUNT_IN, INTERVAL, MARKER_OFFSETS
|
||||
from operator_output import COUNT, COUNT_IN, TEST_COUNT, INTERVAL, MARKER_OFFSETS
|
||||
|
||||
class ClickSequence:
|
||||
"""Prebuilt low-level click PCM; callback only copies memory and timestamps."""
|
||||
@@ -13,8 +13,8 @@ class ClickSequence:
|
||||
self.index = 0
|
||||
self.failed = False
|
||||
start = 2.0
|
||||
if count not in (4,COUNT):raise ValueError('Unsupported calibration sequence')
|
||||
offsets=[start+i*INTERVAL for i in range(4 if count==4 else COUNT_IN)]
|
||||
if count not in (TEST_COUNT,COUNT):raise ValueError('Unsupported calibration sequence')
|
||||
offsets=[start+i*INTERVAL for i in range(TEST_COUNT if count==TEST_COUNT else COUNT_IN)]
|
||||
if count==COUNT:offsets+=list(MARKER_OFFSETS)
|
||||
self.beats = [round(at * RATE) for at in offsets]
|
||||
self.pcm = np.zeros((self.beats[-1] + RATE, 2), dtype='float32')
|
||||
@@ -28,7 +28,7 @@ class ClickSequence:
|
||||
second = round(.08 * RATE)
|
||||
marker[second:second + len(tone_t)] = .07 * np.sin(2 * np.pi * 1760 * tone_t) * envelope
|
||||
for index,beat in enumerate(self.beats):
|
||||
click = marker if index >= COUNT_IN else downbeat if index % 4 == 0 else beat_click
|
||||
click = marker if count == COUNT and index >= COUNT_IN else downbeat if index % 4 == 0 else beat_click
|
||||
self.pcm[beat:beat + len(click)] = click[:, None]
|
||||
self.stream = sd.OutputStream(device=device, samplerate=RATE, channels=2, dtype='float32', blocksize=480,
|
||||
callback=self.callback)
|
||||
@@ -60,7 +60,7 @@ def main():
|
||||
from pathlib import Path
|
||||
from bluetooth_output import prepare_output,select_device
|
||||
from operator_output import real_offroad,selected_output
|
||||
p=argparse.ArgumentParser();p.add_argument('--address',required=True);p.add_argument('--count',type=int,choices=[4,COUNT],required=True);args=p.parse_args()
|
||||
p=argparse.ArgumentParser();p.add_argument('--address',required=True);p.add_argument('--count',type=int,choices=[TEST_COUNT,COUNT],required=True);args=p.parse_args()
|
||||
output=selected_output()
|
||||
if not real_offroad() or not output or not output['connected'] or output['address']!=args.address:
|
||||
raise RuntimeError('Park and reconnect the selected Bluetooth speaker before calibration')
|
||||
|
||||
@@ -18,6 +18,7 @@ import time
|
||||
|
||||
BPM = 100
|
||||
INTERVAL = 60 / BPM
|
||||
TEST_COUNT = 12
|
||||
COUNT_IN = 8
|
||||
COUNT = 20
|
||||
MARKER_COUNT = COUNT - COUNT_IN
|
||||
@@ -268,7 +269,7 @@ class OutputOwner:
|
||||
token = secrets.token_hex(16)
|
||||
clicks = {}
|
||||
try:
|
||||
sink = self.sink_factory(lambda index, at: clicks.__setitem__(index, at), count=4 if action == 'test' else COUNT)
|
||||
sink = self.sink_factory(lambda index, at: clicks.__setitem__(index, at), count=TEST_COUNT if action == 'test' else COUNT)
|
||||
if self.output_provider() != state['output']:
|
||||
sink.close()
|
||||
raise ValueError('Output changed before calibration began')
|
||||
@@ -283,7 +284,7 @@ class OutputOwner:
|
||||
session_lease.__exit__(None, None, None); operator.__exit__(None, None, None)
|
||||
raise
|
||||
threading.Thread(target=self._watch, args=(token,), daemon=True).start()
|
||||
return dict(ok=True, session=token, interval_ms=round(INTERVAL * 1000), beats=4 if action=='test' else COUNT,
|
||||
return dict(ok=True, session=token, interval_ms=round(INTERVAL * 1000), beats=TEST_COUNT if action=='test' else COUNT,
|
||||
count_in=COUNT_IN,bpm=BPM,beats_per_bar=4,method=METHOD,marker_count=MARKER_COUNT,target_taps=MARKER_COUNT,
|
||||
marker_offsets_ms=[round(at*1000) for at in MARKER_OFFSETS],
|
||||
instructions='Listen to two bars without tapping. Then tap once at the START of each two-tone marker; wait through the silence. The estimate includes your reaction time.')
|
||||
|
||||
@@ -211,10 +211,12 @@ class OutputTests(unittest.TestCase):
|
||||
stamps=[]
|
||||
with patch.dict(sys.modules,{'sounddevice':fake}):
|
||||
from operator_click_process import ClickSequence
|
||||
sink=ClickSequence(lambda i,at:stamps.append((i,at)),0,count=4)
|
||||
second=ClickSequence(lambda *_:None,0,count=4)
|
||||
sink=ClickSequence(lambda i,at:stamps.append((i,at)),0,count=m.TEST_COUNT)
|
||||
second=ClickSequence(lambda *_:None,0,count=m.TEST_COUNT)
|
||||
markers=ClickSequence(lambda *_:None,0,count=m.COUNT)
|
||||
markers_again=ClickSequence(lambda *_:None,0,count=m.COUNT)
|
||||
self.assertEqual(len(sink.beats), 12)
|
||||
self.assertTrue(all(b-a == 28800 for a,b in zip(sink.beats,sink.beats[1:])))
|
||||
self.assertTrue(np.array_equal(sink.pcm,second.pcm))
|
||||
self.assertTrue(np.array_equal(markers.pcm,markers_again.pcm))
|
||||
self.assertEqual(len(markers.beats),20)
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</div>
|
||||
<!-- Snackbar for messages -->
|
||||
<div id="snackbar_wrapper"></div>
|
||||
<script type="module" src="/assets/mobile/js/app.js?v=roadscore-calibration-3"></script>
|
||||
<script type="module" src="/assets/mobile/js/app.js?v=roadscore-calibration-4"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RoadScore } from "./views/RoadScore.js?v=calibration-markers-3"
|
||||
import { RoadScore } from "./views/RoadScore.js?v=calibration-test-4"
|
||||
import { createApp, h } from "vue"
|
||||
import { AppShell } from "./components/AppShell.js?v=roadscore-calibration-2"
|
||||
import { Home } from "./views/Home.js"
|
||||
|
||||
@@ -73,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 && 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.beat = index >= 0 && index < (this.testMode ? 12 : 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 ? (index < 12 ? `Watch and listen · bar ${Math.floor(index / 4) + 1} of 3` : 'Test complete') : 'Tap once when you hear each chime · wait through the gaps'
|
||||
this.animation = requestAnimationFrame(() => this.animateBeat())
|
||||
},
|
||||
keyTap(event) {
|
||||
|
||||
Reference in New Issue
Block a user