feat(dictate): add voice-reactive listening waveform

This commit is contained in:
jb
2026-08-26 13:40:47 -04:00
parent e234468a73
commit d1a811a5c5
16 changed files with 383 additions and 73 deletions
+38 -5
View File
@@ -3,7 +3,7 @@ from collections.abc import Sequence
from dataclasses import dataclass
import unittest
from pi_dictate_service.protocol import MAX_TRANSCRIPT_BYTES, parse_request
from pi_dictate_service.protocol import MAX_TRANSCRIPT_BYTES, PROTOCOL_VERSION, parse_request
from pi_dictate_service.recorder import Capture, RecorderError
from pi_dictate_service.server import DictateServiceCore, Outbound
@@ -73,6 +73,7 @@ class FakeRecorder:
self.started = False
self.cancelled = False
self.capture = Capture(b"\x00\x00\x00\x40", -15, 15, b"")
self.audio_level = 0.0
self.stop_error: RecorderError | None = None
self.unexpected: RecorderError | None = None
@@ -124,7 +125,7 @@ class CoreTestCase(unittest.TestCase):
generation_id: str | None = None,
) -> list[dict[str, object]]:
frame: dict[str, object] = {
"version": 1,
"version": PROTOCOL_VERSION,
"type": request_type,
"requestId": request_id,
}
@@ -217,6 +218,38 @@ class CancellationTests(CoreTestCase):
self.assertEqual(self.core.active_generation_id, None)
class AudioLevelTests(CoreTestCase):
def test_emits_owned_normalized_audio_levels_at_no_more_than_ten_hertz(self) -> None:
recorder = self.load_to_listening()
recorder.audio_level = 0.625
events: list[Outbound] = []
for _ in range(40):
self.clock.advance(0.025)
self.core.poll()
events.extend(self.core.drain_events())
self.assertGreaterEqual(len(events), 9)
self.assertLessEqual(len(events), 10)
self.assertEqual({event.client_id for event in events}, {"a"})
self.assertEqual({event.frame["type"] for event in events}, {"level"})
self.assertEqual({event.frame["level"] for event in events}, {0.625})
def test_clamps_invalid_levels_and_stops_emitting_after_listening(self) -> None:
recorder = self.load_to_listening()
for raw_level, expected in ((2.0, 1.0), (-1.0, 0.0), (float("nan"), 0.0)):
recorder.audio_level = raw_level
self.clock.advance(0.1)
self.core.poll()
level = self.frames(self.core.drain_events(), "a")
self.assertEqual(level[0]["level"], expected)
self.request("a", "stop", "stop", "g1")
self.clock.advance(0.2)
self.core.poll()
self.assertEqual(self.core.drain_events(), [])
class StopAndInferenceTests(CoreTestCase):
def test_stop_drains_then_converts_before_inference_and_emits_result(self) -> None:
self.load_to_listening()
@@ -271,11 +304,11 @@ class StopAndInferenceTests(CoreTestCase):
executor=self.executor,
clock=self.clock,
)
core.handle_request("a", parse_request({"version": 1, "type": "hello", "requestId": "h"}))
core.handle_request("a", parse_request({"version": PROTOCOL_VERSION, "type": "hello", "requestId": "h"}))
core.drain_events()
core.handle_request("a", parse_request({"version": 1, "type": "start", "requestId": "s", "generationId": "g"}))
core.handle_request("a", parse_request({"version": PROTOCOL_VERSION, "type": "start", "requestId": "s", "generationId": "g"}))
core.drain_events()
core.handle_request("a", parse_request({"version": 1, "type": "stop", "requestId": "t", "generationId": "g"}))
core.handle_request("a", parse_request({"version": PROTOCOL_VERSION, "type": "stop", "requestId": "t", "generationId": "g"}))
self.executor.run_next()
core.poll()
core.drain_events()