-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstreamer.go
265 lines (234 loc) · 7.11 KB
/
streamer.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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
package main
import (
"encoding/binary"
"fmt"
"github.com/gordonklaus/portaudio"
"math"
"math/rand"
"os"
"sort"
"sync"
)
// This is a high quality audio quality.
const rate = 44100
// How frequently we update the sound per second.
const hertz = 100
// Choose "harmonic notes", these could be changed.
var multipliers = []float64{1.0, 5.0 / 4, 4.0 / 3, 3.0 / 2, 5.0 / 3, 2.0, 5.0 / 2, 3.0}
// Streamer stores the last 256 data points.
// This code intentionally avoids as much heap allocation as possible by
// statically defining all sizes. GCs can cause blips in the audio.
type Streamer struct {
// Store the last 256 points.
data [256]uint32
wrapCounter byte
counter int
// phase is the "x-axis" of the sine curve.
phase [8]float64
includeIds []byte
wg sync.WaitGroup
quit chan struct{}
file *os.File
}
// Chose 8 steps along the way from smallest to biggest.
func (s *Streamer) getPercentiles() [8]uint32 {
// Copy all the values over since we don't want to sort the underlying
// array. This is "racy" with writes, but we don't want locking.
d := s.data
sort.Slice(d[:], func(i, j int) bool { return d[i] > d[j] })
var steps [8]uint32
for i := range steps {
// Take the sorted value at 1/2^i so ends up with the
// 1, 2, 4, ... 128 values
pxValue := d[1<<i]
// Store in reverse order for easier computation later. We base our rate
// on the P50 (128th) value.
// Store the inverse of the rate in microseconds.
steps[7-i] = pxValue
}
return steps
}
// If the base is 100 Hz, the 8th term will have a step of 12.8kHz
// Human hearing is ~20 Hz - 20kHz, so bound base step from 50 to 100 Hz.
// which bounds last step to range 128
// 1ms -> 23Hz
// 100ms -> 69Hz
func convertLatencyToStep(micro uint32) float64 {
rawStep := 30 * math.Log1p(math.Max(float64(micro), 1))
normal := math.Min(math.Max(rawStep, 100.0), 400.0)
// Normalize based on the sound base rate.
return normal / rate
}
// This is called repeatedly with a "small window" of time. We need to fill
// "rate" steps per second. so if step is 1.0, we will have a 1Hz sine wave.
func (s *Streamer) genAudio(out []float32) {
percentiles := s.getPercentiles()
// Reset all the values since the same array is reused each time.
for i := range out {
out[i] = 0
}
baseStep := convertLatencyToStep(percentiles[0])
prevP := 0.0
// fill with a superposition of the waves
// Add all the frequencies together (see fourier transform).
// Compute the next several steps of the sine waves based on the step and amp.
for i, p := range percentiles {
// We want all waves to have the same "period" which is computed by the P50 value.
// step is a multiple of the base rate, each step is half the previous step.
// higher P values have higher frequency steps.
step := baseStep * multipliers[i]
// amp is the height of the sine curve which is based on ratio from
// adjacent step. Start with amp 1/8 for the base, and increase for the
// others. Don't allow any individual amp to get above 1/8, or the total
// to get above 1.
amp := math.Min(float64(p)/prevP/8, 9.0/8) - 1
if amp > 0.2 {
panic(amp)
}
prevP = float64(p)
// Periodically print data. (Remove me)
if s.counter%441000 == 0 {
fmt.Printf("%d: %0.2f %d\n", i, amp, p)
}
for j := range out {
// get the next output value for this curve and add to the others.
diff := float32(amp * math.Sin(2*math.Pi*s.phase[i]))
if diff > 0.2 {
fmt.Printf("%d, %d, %d", amp, diff, j)
panic(diff)
}
out[j] += diff
// move phase along in small steps update for next time, resetting
// to 0 so that we can avoid wild swings when we change the step size.
// Phase always stays between 0 and 1.
_, s.phase[i] = math.Modf(s.phase[i] + step)
}
}
var converted []int16
converted = make([]int16, len(out))
if s.file != nil {
for i, b := range out {
fmt.Println(b)
if b > 1.5 {
b = 1.0
}
converted[i] = int16(b * (math.MaxInt16))
}
}
writeToFile(s.file, converted)
// Every 10 sec spit out the data.
if s.counter%441000 == 0 {
fmt.Println("Base freq: ", int(baseStep*rate))
}
s.counter += len(out)
}
// Record is not thread-safe. The caller of this method should ensure it is
// not called concurrently.
func (s *Streamer) Record(value uint32, id byte) {
if len(s.includeIds) > 0 {
found := false
for _, include := range s.includeIds {
if id == include {
found = true
break
}
}
if !found {
return
}
}
// This is a data race with the reader, but we don't care since we are the
// single writer, and we are writing single values.
s.data[s.wrapCounter] = value
// This will auto-wrap at 256 which is why we chose byte type.
s.wrapCounter += 1
}
// CreateStreamer creates a new streamer.
func CreateStreamer() (*Streamer, error) {
err := portaudio.Initialize()
if err != nil {
return nil, err
}
s := Streamer{quit: make(chan struct{})}
// Fill some random data, with a normal distribution.
for i := range s.data {
s.data[i] = uint32(math.Max(rand.NormFloat64()*10000+10000, 0))
}
for i := range &s.phase {
s.phase[i] = 0.0
}
return &s, nil
}
// StartPlaying will play audio with a tone based on the values passed into
// Record.
func (s *Streamer) StartPlaying() {
s.wg.Add(1)
stream, err := portaudio.OpenDefaultStream(0, 1, rate, rate/100, s.genAudio)
if err != nil {
panic(err)
}
err = stream.Start()
if err != nil {
panic(err)
}
// Run until Stop is called.
select {
case <-s.quit:
_ = portaudio.Terminate()
}
s.wg.Done()
}
func writeToFile(file *os.File, data any) {
err := binary.Write(file, binary.LittleEndian, data)
if err != nil {
panic(err)
}
}
// StartRecording will begin recording the output to a new file.
func (s *Streamer) StartRecording(filename string) {
s.wg.Add(1)
fmt.Printf("Writing to file %s", filename)
// Create a new file with the date as the timestamp.
file, err := os.Create(filename)
if err != nil {
panic(err)
}
s.file = file
writeToFile(file, []byte("RIFF")) // WAV header
writeToFile(file, uint32(0)) // data + header size (filled later)
writeToFile(file, []byte("WAVE")) // file type
writeToFile(file, []byte("fmt ")) // format header
writeToFile(file, uint32(16)) // format data length
writeToFile(file, uint16(1)) // 16-bit signed PCM
writeToFile(file, uint16(1)) // mono
writeToFile(file, uint32(rate)) // sample rate
writeToFile(file, uint32(rate*2)) // bytes/sample
writeToFile(file, uint16(2)) // block align
writeToFile(file, uint16(16)) // bits per sample
writeToFile(file, []byte("data")) // start of data section
writeToFile(file, uint32(0)) // length of data (filled later)
<-s.quit
dataLen := s.counter * 2
fmt.Printf("Data len %d", dataLen)
// Update the header with the amount we wrote.
_, err = file.Seek(4, 0)
if err != nil {
panic(err)
}
writeToFile(file, uint32(36+dataLen)) // data + header size
_, err = file.Seek(40, 0)
if err != nil {
panic(err)
}
writeToFile(file, uint32(dataLen)) // length of data
err = file.Close()
if err != nil {
panic(err)
}
s.wg.Done()
}
// Stop notifies the running loops to stop.
func (s *Streamer) Stop() {
close(s.quit)
s.wg.Wait()
}