-
Notifications
You must be signed in to change notification settings - Fork 155
Expand file tree
/
Copy pathcontext.go
More file actions
225 lines (196 loc) · 7.12 KB
/
Copy pathcontext.go
File metadata and controls
225 lines (196 loc) · 7.12 KB
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
// Copyright 2021 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"
"fmt"
"io"
"sync"
"time"
"github.com/ebitengine/oto/v3/internal/mathutil"
"github.com/ebitengine/oto/v3/internal/mux"
)
var (
contextCreated bool
contextCreationMutex sync.Mutex
)
// 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.
//
// Creating multiple contexts is NOT supported.
type Context struct {
context *context
}
// Format is the format of sources.
type Format int
const (
// FormatFloat32LE is the format of 32-bit floats in little endian.
FormatFloat32LE Format = iota
// FormatUnsignedInt8 is the format of 8-bit integers.
FormatUnsignedInt8
// FormatSignedInt16LE is the format of 16-bit integers in little endian.
FormatSignedInt16LE
)
// NewContextOptions represents options for NewContext.
type NewContextOptions struct {
// SampleRate specifies the number of samples that should be played during one second.
// Typical values are 44100 or 48000. One context has only one sample rate. You cannot play multiple audio
// sources with different sample rates at the same time.
SampleRate int
// ChannelCount specifies the number of channels. One channel is mono playback. Two
// channels are stereo playback. No other values are supported.
ChannelCount int
// Format specifies the format of sources.
Format Format
// BufferSize specifies a buffer size in the underlying device.
// BufferSize must not be negative.
//
// If 0 is specified, the driver's default buffer size is used.
// Set BufferSize to adjust the buffer size if you want to adjust latency or reduce noise.
// A buffer size that is too big increases the latency.
// On the other hand, a buffer size that is too small can cause glitch noises due to buffer shortage.
BufferSize time.Duration
// ApplicationName specifies the name of the client application.
// It is used for PulseAudio's volume control UI and so on.
ApplicationName string
}
// NewContext creates a new context with given options.
// The options must not be nil.
// A context creates and holds ready-to-use Player objects.
// NewContext returns a context, a channel that closes when initialization finishes, and an error, if any.
// After the channel closes, call Context.Err to check whether initialization succeeded.
//
// Creating multiple contexts is NOT supported.
func NewContext(options *NewContextOptions) (*Context, chan struct{}, error) {
if options == nil {
return nil, nil, fmt.Errorf("oto: options must not be nil")
}
if options.BufferSize < 0 {
return nil, nil, fmt.Errorf("oto: buffer size must not be negative: %s", options.BufferSize)
}
contextCreationMutex.Lock()
defer contextCreationMutex.Unlock()
if contextCreated {
return nil, nil, fmt.Errorf("oto: context is already created")
}
contextCreated = true
bufferSizeInBytes, err := durationToBufferSize(options.BufferSize, options.SampleRate, options.ChannelCount)
if err != nil {
return nil, nil, err
}
ctx, ready, err := newContext(options.SampleRate, options.ChannelCount, mux.Format(options.Format), bufferSizeInBytes, options.ApplicationName)
if err != nil {
return nil, nil, err
}
return &Context{context: ctx}, ready, nil
}
func durationToBufferSize(duration time.Duration, sampleRate, channelCount int) (int, error) {
if duration == 0 {
return 0, nil
}
if duration < 0 || sampleRate <= 0 || channelCount <= 0 {
return 0, fmt.Errorf("oto: invalid buffer duration, sample rate, or channel count")
}
// The underlying driver always uses 32-bit floats.
const maxInt = int(^uint(0) >> 1)
if channelCount > maxInt/4 {
return 0, fmt.Errorf("oto: buffer frame size exceeds int range")
}
bytesPerFrame := channelCount * 4
frames, ok := mathutil.MulDiv(int64(duration), int64(sampleRate), int64(time.Second))
if !ok || frames > int64(maxInt/bytesPerFrame) {
return 0, fmt.Errorf("oto: buffer size exceeds int range")
}
return int(frames) * bytesPerFrame, nil
}
// NewPlayer creates a new, ready-to-use Player belonging to the Context.
// It is safe to create multiple players.
//
// The returned player must be kept reachable as long as it should keep playing.
// A player is closed when it becomes unreachable, even in the middle of playing.
//
// The format of r is as follows:
//
// [data] = [sample 1] [sample 2] [sample 3] ...
// [sample *] = [channel 1] [channel 2] ...
// [channel *] = [byte 1] [byte 2] ...
//
// Byte ordering is little endian.
//
// A player has some amount of an underlying buffer.
// Read data from r is queued to the player's underlying buffer.
// The underlying buffer is consumed by its playing.
// Then, r's position and the current playing position don't necessarily match.
// If you want to seek the position of r, call the player's Seek function,
// which also clears the underlying buffer.
// If you want to stop using r (e.g. you want to close r), call the player's PauseAndStopReading function.
//
// You cannot share r by multiple players.
//
// The returned player is a *Player, which has functions like SetBufferSize and Seek.
// You can modify the buffer size of a player by the SetBufferSize function.
// A small buffer size is useful if you want to play a real-time PCM for example.
// Note that the audio quality might be affected if you modify the buffer size.
//
// If r does not implement io.Seeker, the returned player's Seek returns an error.
//
// NewPlayer is concurrent-safe.
//
// All the functions of a Player returned by NewPlayer are concurrent-safe.
func (c *Context) NewPlayer(r io.Reader) *Player {
return &Player{
player: c.context.mux.NewPlayer(r),
}
}
// Suspend suspends the entire audio play.
//
// Suspend is concurrent-safe.
func (c *Context) Suspend() error {
return c.context.Suspend()
}
// Resume resumes the entire audio play, which was suspended by Suspend.
//
// Resume is concurrent-safe.
func (c *Context) Resume() error {
return c.context.Resume()
}
// Err returns an error that occurred in the audio driver, if any.
// Errors reported by Err are fatal: once Err returns a non-nil error,
// this context is no longer usable.
//
// Err is concurrent-safe.
func (c *Context) Err() error {
return c.context.Err()
}
type atomicError struct {
err error
m sync.Mutex
}
// Join records err in addition to the errors recorded so far. A nil err is
// ignored.
func (a *atomicError) Join(err error) {
if err == nil {
return
}
a.m.Lock()
defer a.m.Unlock()
a.err = errors.Join(a.err, err)
}
func (a *atomicError) Load() error {
a.m.Lock()
defer a.m.Unlock()
return a.err
}