Mercurial
annotate dictation/audio.py @ 280:49e9e591c9bb
Add persistent dictation, prewarmed WebRTC speech input, Copilot SDK routing, animated conversation lifecycle controls, parking, and architecture coverage.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Tue, 18 Aug 2026 19:14:53 -0700 |
| parents | 78699f810817 |
| children |
| rev | line source |
|---|---|
|
275
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
1 from __future__ import annotations |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
2 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
3 from dataclasses import dataclass |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
4 from enum import Enum |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
5 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
6 import numpy as np |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
7 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
8 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
9 class AudioEventKind(str, Enum): |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
10 SPEECH_STARTED = "speech_started" |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
11 PARTIAL_READY = "partial_ready" |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
12 FINAL_READY = "final_ready" |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
13 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
14 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
15 @dataclass(frozen=True) |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
16 class AudioEvent: |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
17 kind: AudioEventKind |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
18 samples: np.ndarray | None = None |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
19 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
20 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
21 class AudioSegmenter: |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
22 def __init__( |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
23 self, |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
24 *, |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
25 sample_rate: int = 16000, |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
26 speech_threshold: float = 0.012, |
|
280
49e9e591c9bb
Add persistent dictation, prewarmed WebRTC speech input, Copilot SDK routing, animated conversation lifecycle controls, parking, and architecture coverage.
MrJuneJune <me@mrjunejune.com>
parents:
275
diff
changeset
|
27 silence_ms: int = 400, |
|
49e9e591c9bb
Add persistent dictation, prewarmed WebRTC speech input, Copilot SDK routing, animated conversation lifecycle controls, parking, and architecture coverage.
MrJuneJune <me@mrjunejune.com>
parents:
275
diff
changeset
|
28 partial_interval_ms: int = 500, |
|
275
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
29 max_utterance_seconds: int = 30, |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
30 pre_roll_ms: int = 200, |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
31 ) -> None: |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
32 self.sample_rate = sample_rate |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
33 self.speech_threshold = speech_threshold |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
34 self.silence_samples = sample_rate * silence_ms // 1000 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
35 self.partial_samples = sample_rate * partial_interval_ms // 1000 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
36 self.max_samples = sample_rate * max_utterance_seconds |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
37 self.pre_roll_samples = sample_rate * pre_roll_ms // 1000 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
38 self._pre_roll = np.empty(0, dtype=np.float32) |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
39 self._utterance = np.empty(0, dtype=np.float32) |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
40 self._silence_count = 0 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
41 self._last_partial_size = 0 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
42 self._speaking = False |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
43 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
44 @property |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
45 def speaking(self) -> bool: |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
46 return self._speaking |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
47 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
48 def feed(self, samples: np.ndarray) -> list[AudioEvent]: |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
49 chunk = np.asarray(samples, dtype=np.float32).reshape(-1) |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
50 if chunk.size == 0: |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
51 return [] |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
52 rms = float(np.sqrt(np.mean(np.square(chunk), dtype=np.float64))) |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
53 is_speech = rms >= self.speech_threshold |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
54 events: list[AudioEvent] = [] |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
55 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
56 if not self._speaking: |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
57 if not is_speech: |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
58 self._append_pre_roll(chunk) |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
59 return events |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
60 self._speaking = True |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
61 self._utterance = np.concatenate((self._pre_roll, chunk)) |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
62 self._pre_roll = np.empty(0, dtype=np.float32) |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
63 self._silence_count = 0 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
64 self._last_partial_size = 0 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
65 events.append(AudioEvent(AudioEventKind.SPEECH_STARTED)) |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
66 else: |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
67 self._utterance = np.concatenate((self._utterance, chunk)) |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
68 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
69 self._silence_count = 0 if is_speech else self._silence_count + chunk.size |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
70 utterance_size = self._utterance.size |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
71 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
72 if ( |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
73 utterance_size - self._last_partial_size >= self.partial_samples |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
74 and self._silence_count < self.silence_samples |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
75 ): |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
76 self._last_partial_size = utterance_size |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
77 events.append( |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
78 AudioEvent( |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
79 AudioEventKind.PARTIAL_READY, |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
80 self._utterance.copy(), |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
81 ) |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
82 ) |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
83 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
84 if ( |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
85 self._silence_count >= self.silence_samples |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
86 or utterance_size >= self.max_samples |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
87 ): |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
88 final = self._utterance.copy() |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
89 self._reset() |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
90 events.append(AudioEvent(AudioEventKind.FINAL_READY, final)) |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
91 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
92 return events |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
93 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
94 def flush(self) -> AudioEvent | None: |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
95 if not self._speaking or self._utterance.size == 0: |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
96 self._reset() |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
97 return None |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
98 final = self._utterance.copy() |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
99 self._reset() |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
100 return AudioEvent(AudioEventKind.FINAL_READY, final) |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
101 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
102 def _append_pre_roll(self, chunk: np.ndarray) -> None: |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
103 self._pre_roll = np.concatenate((self._pre_roll, chunk)) |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
104 if self._pre_roll.size > self.pre_roll_samples: |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
105 self._pre_roll = self._pre_roll[-self.pre_roll_samples :] |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
106 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
107 def _reset(self) -> None: |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
108 self._pre_roll = np.empty(0, dtype=np.float32) |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
109 self._utterance = np.empty(0, dtype=np.float32) |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
110 self._silence_count = 0 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
111 self._last_partial_size = 0 |
|
78699f810817
Add Qwen3-VL and WebRTC dictation services
MrJuneJune <me@mrjunejune.com>
parents:
diff
changeset
|
112 self._speaking = False |