feat(dictate): add voice-reactive listening waveform
This commit is contained in:
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
from collections.abc import Sequence
|
||||
from concurrent.futures import Executor, Future, ThreadPoolExecutor
|
||||
from dataclasses import dataclass
|
||||
import math
|
||||
import os
|
||||
from queue import Empty, SimpleQueue
|
||||
import selectors
|
||||
@@ -30,9 +31,13 @@ from .recorder import Capture, PwRecorder, RecorderError
|
||||
DEFAULT_IDLE_SECONDS = 15 * 60
|
||||
MAX_CLIENT_CONNECTIONS = 16
|
||||
MAX_CONNECTION_OUTPUT_BYTES = MAX_FRAME_BYTES
|
||||
LEVEL_EVENT_INTERVAL_SECONDS = 0.1
|
||||
|
||||
|
||||
class Recorder(Protocol):
|
||||
@property
|
||||
def audio_level(self) -> float: ...
|
||||
|
||||
def start(self) -> None: ...
|
||||
def stop(self) -> Capture: ...
|
||||
def cancel(self) -> None: ...
|
||||
@@ -53,6 +58,7 @@ class _Generation:
|
||||
state: str
|
||||
recorder: Recorder | None = None
|
||||
tombstoned: bool = False
|
||||
last_level_event_at: float | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -156,6 +162,8 @@ class DictateServiceCore:
|
||||
if failure is not None:
|
||||
self._async_error(active, failure.code, failure.detail)
|
||||
self._finish_generation(active, notify_idle=True)
|
||||
return
|
||||
self._emit_audio_level(active)
|
||||
|
||||
def drain_events(self) -> list[Outbound]:
|
||||
events, self._events = self._events, []
|
||||
@@ -216,8 +224,31 @@ class DictateServiceCore:
|
||||
self._finish_generation(active, notify_idle=True)
|
||||
return
|
||||
active.recorder = recorder
|
||||
active.last_level_event_at = self._clock()
|
||||
self._state(active, "listening")
|
||||
|
||||
def _emit_audio_level(self, active: _Generation) -> None:
|
||||
now = self._clock()
|
||||
if (
|
||||
active.last_level_event_at is not None
|
||||
and now - active.last_level_event_at < LEVEL_EVENT_INTERVAL_SECONDS
|
||||
):
|
||||
return
|
||||
level = active.recorder.audio_level if active.recorder is not None else 0.0
|
||||
if not math.isfinite(level):
|
||||
level = 0.0
|
||||
active.last_level_event_at = now
|
||||
self._emit(
|
||||
active.owner,
|
||||
{
|
||||
"version": PROTOCOL_VERSION,
|
||||
"type": "level",
|
||||
"requestId": active.request_id,
|
||||
"generationId": active.generation_id,
|
||||
"level": round(max(0.0, min(1.0, level)), 3),
|
||||
},
|
||||
)
|
||||
|
||||
def _stop(self, client_id: str, request: Request) -> None:
|
||||
active = self._owned_generation(client_id, request)
|
||||
if active is None:
|
||||
@@ -433,7 +464,7 @@ def inherited_listener(environ: dict[str, str] | None = None) -> socket.socket:
|
||||
|
||||
|
||||
def serve(listener: socket.socket, core: DictateServiceCore) -> None:
|
||||
"""Serve protocol v1 on an explicit listener until the core becomes idle."""
|
||||
"""Serve protocol v2 on an explicit listener until the core becomes idle."""
|
||||
selector = selectors.DefaultSelector()
|
||||
connections: dict[str, _Connection] = {}
|
||||
sequence = 0
|
||||
|
||||
Reference in New Issue
Block a user