-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsilence.go
More file actions
107 lines (91 loc) · 2.24 KB
/
silence.go
File metadata and controls
107 lines (91 loc) · 2.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package main
import (
"sync/atomic"
"time"
)
const (
tickInterval = 100 * time.Millisecond
silenceWarnEvery = 8 * time.Second
silenceAutoCloseDur = 30 * time.Second
speechMinRatio = 0.10
speechClearRatio = 0.25 // higher threshold to clear warning (hysteresis)
)
type SilenceEvent int
const (
SilenceNone SilenceEvent = iota
SilenceWarn // no voice detected
SilenceWarnClear // speech resumed after warning
SilenceRepeat // repeat beep (every 8s)
SilenceAutoClose // 30s auto-close (toggle mode)
)
type silenceMonitor struct {
warnAt int
windowSz int
silenceClose *atomic.Bool
ticks int
window []bool
speechCount int
warned bool
lastBeep int
}
func newSilenceMonitor(silenceClose *atomic.Bool) *silenceMonitor {
warnAt := int(silenceWarnEvery / tickInterval)
windowSz := int(silenceAutoCloseDur / tickInterval)
return &silenceMonitor{
warnAt: warnAt,
windowSz: windowSz,
silenceClose: silenceClose,
window: make([]bool, windowSz),
}
}
func (m *silenceMonitor) ratio(n int) float64 {
if m.ticks < n {
n = m.ticks
}
if n == 0 {
return 1.0
}
count := 0
for i := 0; i < n; i++ {
if m.window[(m.ticks-1-i+m.windowSz)%m.windowSz] {
count++
}
}
return float64(count) / float64(n)
}
func (m *silenceMonitor) Tick(hasSpeech bool) SilenceEvent {
idx := m.ticks % m.windowSz
if m.ticks >= m.windowSz && m.window[idx] {
m.speechCount--
}
m.window[idx] = hasSpeech
if hasSpeech {
m.speechCount++
}
m.ticks++
r := m.ratio(m.warnAt)
// Warn: 8s window below threshold
if m.ticks >= m.warnAt && r < speechMinRatio && !m.warned {
m.warned = true
m.lastBeep = m.ticks
return SilenceWarn
}
// Clear: speech ratio above clear threshold
if m.warned && r >= speechClearRatio {
m.warned = false
return SilenceWarnClear
}
if !m.silenceClose.Load() {
return SilenceNone
}
// Auto-close: 30s window below threshold (checked before repeat)
if m.ticks >= m.windowSz && float64(m.speechCount)/float64(m.windowSz) < speechMinRatio {
return SilenceAutoClose
}
// Repeat beep every 8s
if m.warned && m.ticks-m.lastBeep >= m.warnAt {
m.lastBeep = m.ticks
return SilenceRepeat
}
return SilenceNone
}