-
Notifications
You must be signed in to change notification settings - Fork 153
/
speaker.go
130 lines (111 loc) · 2.92 KB
/
speaker.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
// Package speaker implements playback of beep.Streamer values through physical speakers.
package speaker
import (
"sync"
"github.com/faiface/beep"
"github.com/hajimehoshi/oto"
"github.com/pkg/errors"
)
var (
mu sync.Mutex
mixer beep.Mixer
samples [][2]float64
buf []byte
context *oto.Context
player *oto.Player
done chan struct{}
)
// Init initializes audio playback through speaker. Must be called before using this package.
//
// The bufferSize argument specifies the number of samples of the speaker's buffer. Bigger
// bufferSize means lower CPU usage and more reliable playback. Lower bufferSize means better
// responsiveness and less delay.
func Init(sampleRate beep.SampleRate, bufferSize int) error {
mu.Lock()
defer mu.Unlock()
Close()
mixer = beep.Mixer{}
numBytes := bufferSize * 4
samples = make([][2]float64, bufferSize)
buf = make([]byte, numBytes)
var err error
context, err = oto.NewContext(int(sampleRate), 2, 2, numBytes)
if err != nil {
return errors.Wrap(err, "failed to initialize speaker")
}
player = context.NewPlayer()
done = make(chan struct{})
go func() {
for {
select {
default:
update()
case <-done:
return
}
}
}()
return nil
}
// Close closes the playback and the driver. In most cases, there is certainly no need to call Close
// even when the program doesn't play anymore, because in properly set systems, the default mixer
// handles multiple concurrent processes. It's only when the default device is not a virtual but hardware
// device, that you'll probably want to manually manage the device from your application.
func Close() {
if player != nil {
if done != nil {
done <- struct{}{}
done = nil
}
player.Close()
context.Close()
player = nil
}
}
// Lock locks the speaker. While locked, speaker won't pull new data from the playing Streamers. Lock
// if you want to modify any currently playing Streamers to avoid race conditions.
//
// Always lock speaker for as little time as possible, to avoid playback glitches.
func Lock() {
mu.Lock()
}
// Unlock unlocks the speaker. Call after modifying any currently playing Streamer.
func Unlock() {
mu.Unlock()
}
// Play starts playing all provided Streamers through the speaker.
func Play(s ...beep.Streamer) {
mu.Lock()
mixer.Add(s...)
mu.Unlock()
}
// Clear removes all currently playing Streamers from the speaker.
func Clear() {
mu.Lock()
mixer.Clear()
mu.Unlock()
}
// update pulls new data from the playing Streamers and sends it to the speaker. Blocks until the
// data is sent and started playing.
func update() {
mu.Lock()
mixer.Stream(samples)
mu.Unlock()
for i := range samples {
for c := range samples[i] {
val := samples[i][c]
if val < -1 {
val = -1
}
if val > +1 {
val = +1
}
valInt16 := int16(val * (1<<15 - 1))
low := byte(valInt16)
high := byte(valInt16 >> 8)
buf[i*4+c*2+0] = low
buf[i*4+c*2+1] = high
}
}
player.Write(buf)
}