forked from ebitengine/oto
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontext.go
168 lines (148 loc) · 4.56 KB
/
context.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
// Copyright 2019 The Oto Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package oto
import (
"errors"
"io"
"sync"
"time"
"github.com/chekt/oto/internal/mux"
)
// Context is the main object in Oto. It interacts with the audio drivers.
//
// To play sound with Oto, first create a context. Then use the context to create
// an arbitrary number of players. Then use the players to play sound.
//
// There can only be one context at any time. Closing a context and opening a new one is allowed.
type Context struct {
driverWriter *driverWriter
mux *mux.Mux
errCh chan error
}
var (
theContext *Context
contextM sync.Mutex
)
var errClosed = errors.New("closed")
// NewContext creates a new context, that creates and holds ready-to-use Player objects.
//
// The sampleRate argument specifies the number of samples that should be played during one second.
// Usual numbers are 44100 or 48000.
//
// The channelNum argument specifies the number of channels. One channel is mono playback. Two
// channels are stereo playback. No other values are supported.
//
// The bitDepthInBytes argument specifies the number of bytes per sample per channel. The usual value
// is 2. Only values 1 and 2 are supported.
//
// The bufferSizeInBytes argument specifies the size of the buffer of the Context. This means, how
// many bytes can Context remember before actually playing them. Bigger buffer can reduce the number
// of Player's Write calls, thus reducing CPU time. Smaller buffer enables more precise timing. The
// longest delay between when samples were written and when they started playing is equal to the size
// of the buffer.
func NewContext(sampleRate, channelNum, bitDepthInBytes, bufferSizeInBytes int) (*Context, error) {
contextM.Lock()
defer contextM.Unlock()
if theContext != nil {
panic("oto: NewContext can be called only once")
}
d, err := newDriver(sampleRate, channelNum, bitDepthInBytes, bufferSizeInBytes)
if err != nil {
return nil, err
}
dw := &driverWriter{
driver: d,
bufferSize: bufferSizeInBytes,
bytesPerSecond: sampleRate * channelNum * bitDepthInBytes,
}
c := &Context{
driverWriter: dw,
mux: mux.New(channelNum, bitDepthInBytes),
errCh: make(chan error, 1),
}
theContext = c
go func() {
if _, err := io.Copy(c.driverWriter, c.mux); err != nil {
c.errCh <- err
}
close(c.errCh)
c.Close()
}()
return c, nil
}
// NewPlayer creates a new, ready-to-use Player belonging to the Context.
func (c *Context) NewPlayer() *Player {
return newPlayer(c)
}
// Close closes the Context and its Players and frees any resources associated with it. The Context is no longer
// usable after calling Close.
func (c *Context) Close() error {
contextM.Lock()
theContext = nil
contextM.Unlock()
if err := c.driverWriter.Close(); err != nil {
return err
}
for _, r := range c.mux.Sources() {
if err := r.(io.Closer).Close(); err != nil {
return err
}
}
if err := c.mux.Close(); err != nil {
return err
}
return nil
}
type tryWriteCloser interface {
io.Closer
TryWrite([]byte) (int, error)
}
type driverWriter struct {
driver tryWriteCloser
bufferSize int
bytesPerSecond int
m sync.Mutex
}
func (d *driverWriter) Write(buf []byte) (int, error) {
d.m.Lock()
defer d.m.Unlock()
written := 0
for len(buf) > 0 {
if d.driver == nil {
return written, errClosed
}
n, err := d.driver.TryWrite(buf)
written += n
if err != nil {
return written, err
}
buf = buf[n:]
// When not all buf is written, the underlying buffer is full.
// Mitigate the busy loop by sleeping (#10).
if len(buf) > 0 {
t := time.Second * time.Duration(d.bufferSize) / time.Duration(d.bytesPerSecond) / 8
time.Sleep(t)
}
}
return written, nil
}
func (d *driverWriter) Close() error {
d.m.Lock()
defer d.m.Unlock()
// Close should be wait until the buffer data is consumed (#36).
// This is the simplest (but ugly) fix.
// TODO: Implement player's Close to wait the buffer played.
time.Sleep(time.Second * time.Duration(d.bufferSize) / time.Duration(d.bytesPerSecond))
return d.driver.Close()
}