22package speaker
33
44import (
5- "sync"
6-
75 "github.com/faiface/beep"
8- "github.com/hajimehoshi/oto"
6+ "github.com/hajimehoshi/oto/v2 "
97 "github.com/pkg/errors"
8+ "io"
9+ "sync"
1010)
1111
12+ const channelCount = 2
13+ const bitDepthInBytes = 2
14+ const bytesPerSample = bitDepthInBytes * channelCount
15+
1216var (
1317 mu sync.Mutex
1418 mixer beep.Mixer
15- samples [][2 ]float64
16- buf []byte
1719 context * oto.Context
18- player * oto.Player
19- done chan struct {}
20+ player oto.Player
2021)
2122
2223// Init initializes audio playback through speaker. Must be called before using this package.
@@ -25,36 +26,23 @@ var (
2526// bufferSize means lower CPU usage and more reliable playback. Lower bufferSize means better
2627// responsiveness and less delay.
2728func Init (sampleRate beep.SampleRate , bufferSize int ) error {
28- mu .Lock ()
29- defer mu .Unlock ()
30-
31- Close ()
29+ if context != nil {
30+ return errors .New ("speaker cannot be initialized more than once" )
31+ }
3232
3333 mixer = beep.Mixer {}
3434
35- numBytes := bufferSize * 4
36- samples = make ([][2 ]float64 , bufferSize )
37- buf = make ([]byte , numBytes )
38-
3935 var err error
40- context , err = oto .NewContext (int (sampleRate ), 2 , 2 , numBytes )
36+ var readyChan chan struct {}
37+ context , readyChan , err = oto .NewContext (int (sampleRate ), channelCount , bitDepthInBytes )
4138 if err != nil {
4239 return errors .Wrap (err , "failed to initialize speaker" )
4340 }
44- player = context .NewPlayer ()
45-
46- done = make (chan struct {})
41+ <- readyChan
4742
48- go func () {
49- for {
50- select {
51- default :
52- update ()
53- case <- done :
54- return
55- }
56- }
57- }()
43+ player = context .NewPlayer (newReaderFromStreamer (& mixer ))
44+ player .(oto.BufferSizeSetter ).SetBufferSize (bufferSize * bytesPerSample )
45+ player .Play ()
5846
5947 return nil
6048}
@@ -63,19 +51,16 @@ func Init(sampleRate beep.SampleRate, bufferSize int) error {
6351// even when the program doesn't play anymore, because in properly set systems, the default mixer
6452// handles multiple concurrent processes. It's only when the default device is not a virtual but hardware
6553// device, that you'll probably want to manually manage the device from your application.
54+ //
55+ // TODO: investigate what happens now that oto.Context doesn't have a Close method.
6656func Close () {
6757 if player != nil {
68- if done != nil {
69- done <- struct {}{}
70- done = nil
71- }
7258 player .Close ()
73- context .Close ()
7459 player = nil
7560 }
7661}
7762
78- // Lock locks the speaker. While locked, speaker won't pull new data from the playing Stramers . Lock
63+ // Lock locks the speaker. While locked, speaker won't pull new data from the playing Streamers . Lock
7964// if you want to modify any currently playing Streamers to avoid race conditions.
8065//
8166// Always lock speaker for as little time as possible, to avoid playback glitches.
@@ -102,16 +87,46 @@ func Clear() {
10287 mu .Unlock ()
10388}
10489
105- // update pulls new data from the playing Streamers and sends it to the speaker. Blocks until the
106- // data is sent and started playing.
107- func update () {
90+ // sampleReader is a wrapper for beep.Streamer to implement io.Reader.
91+ type sampleReader struct {
92+ s beep.Streamer
93+ buf [][2 ]float64
94+ }
95+
96+ func newReaderFromStreamer (s beep.Streamer ) * sampleReader {
97+ return & sampleReader {
98+ s : s ,
99+ }
100+ }
101+
102+ // Read pulls samples from the reader and fills buf with the encoded
103+ // samples. Read expects the size of buf be divisible by the length
104+ // of a sample (= channel count * bit depth in bytes).
105+ func (s * sampleReader ) Read (buf []byte ) (n int , err error ) {
106+ // Read samples from streamer
107+ if len (buf )% bytesPerSample != 0 {
108+ return 0 , errors .New ("requested number of bytes do not align with the samples" )
109+ }
110+ ns := len (buf ) / bytesPerSample
111+ if len (s .buf ) < ns {
112+ s .buf = make ([][2 ]float64 , ns )
113+ }
108114 mu .Lock ()
109- mixer . Stream (samples )
115+ ns , ok := s . s . Stream (s . buf [: ns ] )
110116 mu .Unlock ()
117+ if ! ok {
118+ if s .s .Err () != nil {
119+ return 0 , errors .Wrap (s .s .Err (), "streamer returned error when requesting samples" )
120+ }
121+ if ns == 0 {
122+ return 0 , io .EOF
123+ }
124+ }
111125
112- for i := range samples {
113- for c := range samples [i ] {
114- val := samples [i ][c ]
126+ // Convert samples to bytes
127+ for i := range s .buf [:ns ] {
128+ for c := range s .buf [i ] {
129+ val := s.buf [i ][c ]
115130 if val < - 1 {
116131 val = - 1
117132 }
@@ -121,10 +136,10 @@ func update() {
121136 valInt16 := int16 (val * (1 << 15 - 1 ))
122137 low := byte (valInt16 )
123138 high := byte (valInt16 >> 8 )
124- buf [i * 4 + c * 2 + 0 ] = low
125- buf [i * 4 + c * 2 + 1 ] = high
139+ buf [i * bytesPerSample + c * bitDepthInBytes + 0 ] = low
140+ buf [i * bytesPerSample + c * bitDepthInBytes + 1 ] = high
126141 }
127142 }
128143
129- player . Write ( buf )
144+ return ns * bytesPerSample , nil
130145}
0 commit comments