feat(dictate): add voice-reactive listening waveform
This commit is contained in:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user