-
Notifications
You must be signed in to change notification settings - Fork 22
/
vox.go
181 lines (151 loc) · 3.74 KB
/
vox.go
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package vox
import (
"fmt"
"log"
"sync"
"time"
"github.com/chewxy/math32"
"github.com/dh1tw/remoteAudio/audio"
)
// Vox is an Audio Node which detects if the audio level raises above or falls
// below a defined threshold level.
type Vox struct {
sync.Mutex
enabled bool
active bool
lastActivation time.Time
cb audio.OnDataCb
onStateChange func(voxOn bool)
threshold float32
holdTime time.Duration
chWarning sync.Once
}
// New is the constructor method for a Vox Object. Vox implements
// an audio.Node and emits a StateChanged callback when the RMS
// (root mean square) has fallen above or below the set threshold. By
// default the threshold is set to 0.1 and the hold time to 500ms.
func New(opts ...Option) *Vox {
v := &Vox{
cb: nil,
holdTime: time.Millisecond * 500,
threshold: 0.1,
lastActivation: time.Time{},
}
for _, opt := range opts {
opt(v)
}
return v
}
// Write is the entry point into this audio Node. Writing an audio.Msg
// will start the processing.
func (v *Vox) Write(msg audio.Msg) error {
v.Lock()
defer v.Unlock()
if v.cb == nil {
return nil
}
// forward the msg asap to the next node
go v.cb(msg)
if !v.enabled {
return nil
}
if msg.Channels > 1 {
v.multiChannelWarning()
}
// empty frame
if len(msg.Data) == 0 {
return nil
}
rmsValue, err := rms(msg.Data)
if err != nil {
return err
}
if rmsValue >= v.threshold {
v.lastActivation = time.Now()
if !v.active {
v.active = true
// log.Println("activating vox")
go v.onStateChange(true)
}
} else {
if v.active && time.Since(v.lastActivation) > v.holdTime {
v.active = false
// log.Println("deactivating vox")
go v.onStateChange(false)
}
}
return nil
}
// SetCb sets the callback which will be called when the data has been
// processed and is ready to be sent to the next audio.Node or audio.Sink.
func (v *Vox) SetCb(cb audio.OnDataCb) {
v.Lock()
defer v.Unlock()
v.cb = cb
}
// Enable or disable the vox. If the vox is disabled, the audio data
// will be passed on to the next audio node in the chain.
func (v *Vox) Enable(state bool) {
v.Lock()
defer v.Unlock()
v.enabled = state
}
// Enabled returns a boolean value indicating if the vox is
// enabled. If not, the audio data is directly passed on to the
// next audio node in the chain.
func (v *Vox) Enabled() bool {
v.Lock()
defer v.Unlock()
return v.enabled
}
// SetThreshold sets the value for the vox threshold. Only values
// between 0...1 are allowed. Values below or above will be clipped
// to the minimum or maximum.
func (v *Vox) SetThreshold(value float32) {
v.Lock()
defer v.Unlock()
if value > 1.0 {
v.threshold = 1.0
} else if value < 0.0 {
v.threshold = 0.0
} else {
v.threshold = value
}
}
// Threshold returns the vox threshold value.
func (v *Vox) Threshold() float32 {
v.Lock()
defer v.Unlock()
return v.threshold
}
// SetHoldTime sets the vox hold time. The hold time is the duration
// which will be waited until a statechange event is emitted.
func (v *Vox) SetHoldTime(t time.Duration) {
v.Lock()
defer v.Unlock()
v.holdTime = t
}
// Holdtime returns the current vox holdtime
func (v *Vox) Holdtime() time.Duration {
v.Lock()
defer v.Unlock()
return v.holdTime
}
// calculate the root mean square for a non-interlaced audio
// frame
func rms(data []float32) (float32, error) {
var sum float32
if len(data) == 0 {
return sum, fmt.Errorf("empty slice provided")
}
for _, el := range data {
sum = sum + el*el
}
sum = sum / float32(len(data))
return math32.Sqrt(sum), nil
}
func (v *Vox) multiChannelWarning() {
v.chWarning.Do(func() {
log.Println("WARNING: multiple input channels detected; RMS for Vox will be calculated over all channel samples")
})
}