-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathtarget.go
226 lines (179 loc) · 4.79 KB
/
target.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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
package goertzel
import (
"bufio"
"encoding/binary"
"io"
"math"
"sync"
"time"
)
// NewTarget creates a Goertzel processor tuned to the given frequency
func NewTarget(freq, sampleRate float64, minDuration time.Duration) *Target {
t := &Target{
Frequency: freq,
sampleRate: sampleRate,
blockSize: optimalBlockSize2(freq, sampleRate, minDuration),
Threshold: ToneThreshold,
}
t.generateConstants()
return t
}
// Target is a target frequency detector. It is a low-level tool which
// implements the Goertzel algorithm to detect the presence of a frequency on a
// block-wise basis.
type Target struct {
//
// Constants
//
// UseOptimized indicates that an optimized (phase-insensitive) Goertzel should be used for faster arithmetic
UseOptimized bool
// Frequency in Hz
Frequency float64
// Threshold is the threshold at which this frequency is determined to be present
Threshold float64
// sampleRate is the number of times per second that we should receive a sample
sampleRate float64
sin float64
cos float64
coeff float64
blockSize int
//
// Working Variables
//
realM float64
imagM float64
// Magnitude2 is the square of the magnitude of the last-processed block
Magnitude2 float64
// blockReader variables for managing output of block summaries
blockReaderPresent bool
blockReader chan *BlockSummary
blockReaderMu sync.Mutex
stopped bool
mu sync.Mutex
}
// SetBlockSize overrides automatic calculation of the optimal N (block size) value and uses the one provided instead
func (t *Target) SetBlockSize(n int) {
t.mu.Lock()
t.blockSize = n
t.mu.Unlock()
t.generateConstants()
}
func (t *Target) generateConstants() {
t.mu.Lock()
defer t.mu.Unlock()
N := float64(t.blockSize)
rate := t.sampleRate
k := math.Floor(0.5 + (N*t.Frequency)/rate)
w := (2.0 * math.Pi / N) * float64(k)
t.cos = math.Cos(w)
t.sin = math.Sin(w)
t.coeff = 2.0 * t.cos
}
// Read processes incoming samples through the Target goertzel
func (t *Target) Read(in io.Reader) error {
return t.ingest(in)
}
func (t *Target) ingest(in io.Reader) (err error) {
var i int
var sample int16
var q, q1, q2 float64
defer t.Stop()
r := bufio.NewReader(in)
for {
var buf = make([]byte, 2)
buf[0], _ = r.ReadByte()
buf[1], err = r.ReadByte()
if err != nil {
return err
}
sample = int16(binary.LittleEndian.Uint16(buf))
i++
q = t.coeff*q1 - q2 + float64(sample)
q2 = q1
q1 = q
if i == t.blockSize {
t.calculateMagnitude(q1, q2)
t.sendBlockSummary()
i = 0
q1 = 0
q2 = 0
t.mu.Lock()
if t.stopped {
t.mu.Unlock()
return
}
t.mu.Unlock()
}
}
}
func (t *Target) calculateMagnitude(q1, q2 float64) {
if t.UseOptimized {
t.Magnitude2 = q1*q1 + q2*q2 - q1*q2*t.coeff
return
}
var scalingFactor = float64(t.blockSize) / 2.0
t.mu.Lock()
t.realM = (q1 - q2*t.cos) / scalingFactor
t.imagM = (q2 * t.sin) / scalingFactor
t.Magnitude2 = t.realM*t.realM + t.imagM*t.imagM
t.mu.Unlock()
}
func (t *Target) sendBlockSummary() {
t.blockReaderMu.Lock()
if t.blockReaderPresent {
select {
case t.blockReader <- t.blockSummary():
default:
}
}
t.blockReaderMu.Unlock()
}
func (t *Target) blockSummary() *BlockSummary {
return &BlockSummary{
Magnitude2: t.Magnitude2,
Frequency: t.Frequency,
Duration: time.Duration(float64(t.blockSize)/t.sampleRate) * time.Second,
Samples: t.blockSize,
Present: t.Magnitude2 > t.Threshold,
}
}
// Stop terminates the Target processing. It will close the Events channel and stop processing new data.
func (t *Target) Stop() {
t.blockReaderMu.Lock()
if t.blockReaderPresent {
close(t.blockReader)
t.blockReader = nil
t.blockReaderPresent = false
}
t.blockReaderMu.Unlock()
t.mu.Lock()
t.stopped = true
t.mu.Unlock()
}
// Blocks returns a channel over which the summary of each resulting block from
// the Target frequency processor will be returned. If Blocks() has already
// been called, nil will be returned.
func (t *Target) Blocks() <-chan *BlockSummary {
t.blockReaderMu.Lock()
if t.blockReaderPresent {
t.blockReaderMu.Unlock()
return nil
}
t.blockReaderPresent = true
t.blockReader = make(chan *BlockSummary, BlockBufferSize)
t.blockReaderMu.Unlock()
return t.blockReader
}
// BlockSummary describes the result of a single block of processing for a Target frequency
type BlockSummary struct {
// Magnitude2 is the square of the relative magnitude of the frequency in this block
Magnitude2 float64
// Frequency is the frequency which was being detected
Frequency float64
// Duration is the elapsed time which this block represents
Duration time.Duration
// Samples is the number of samples this block represents
Samples int
// Present indicates whether the frequency was found in the block, as determined by the target's threshold
Present bool
}