-
Notifications
You must be signed in to change notification settings - Fork 135
/
context.go
181 lines (158 loc) · 5.51 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
169
170
171
172
173
174
175
176
177
178
179
180
181
// 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 (
"fmt"
"io"
"sync"
"time"
"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 bits floats little endian.
FormatFloat32LE Format = iota
// FormatUnsignedInt8 is the format of 8 bits integers.
FormatUnsignedInt8
//FormatSignedInt16LE is the format of 16 bits integers little endian.
FormatSignedInt16LE
)
// NewContextOptions represents options for NewContext.
type NewContextOptions struct {
// SampleRate specifies the number of samples that should be played during one second.
// Usual numbers 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.
//
// 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 noises.
// Too big buffer size can increase the latency time.
// On the other hand, too small buffer size can cause glitch noises due to buffer shortage.
BufferSize time.Duration
}
// NewContext creates a new context with given options.
// A context creates and holds ready-to-use Player objects.
// NewContext returns a context, a channel that is closed when the context is ready, and an error if it exists.
//
// Creating multiple contexts is NOT supported.
func NewContext(options *NewContextOptions) (*Context, chan struct{}, error) {
contextCreationMutex.Lock()
defer contextCreationMutex.Unlock()
if contextCreated {
return nil, nil, fmt.Errorf("oto: context is already created")
}
contextCreated = true
var bufferSizeInBytes int
if options.BufferSize != 0 {
// The underying driver always uses 32bit floats.
bytesPerSample := options.ChannelCount * 4
bytesPerSecond := options.SampleRate * bytesPerSample
bufferSizeInBytes = int(int64(options.BufferSize) * int64(bytesPerSecond) / int64(time.Second))
bufferSizeInBytes = bufferSizeInBytes / bytesPerSample * bytesPerSample
}
ctx, ready, err := newContext(options.SampleRate, options.ChannelCount, mux.Format(options.Format), bufferSizeInBytes)
if err != nil {
return nil, nil, err
}
return &Context{context: ctx}, ready, nil
}
// NewPlayer creates a new, ready-to-use Player belonging to the Context.
// It is safe to create multiple players.
//
// 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 clear the underlying buffer for some reasons e.g., you want to seek the position of r,
// call the player's Reset function.
//
// You cannot share r by multiple players.
//
// The returned player implements Player, BufferSizeSetter, and io.Seeker.
// 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 the current error.
//
// Err is concurrent-safe.
func (c *Context) Err() error {
return c.context.Err()
}
type atomicError struct {
err error
m sync.Mutex
}
func (a *atomicError) TryStore(err error) {
a.m.Lock()
defer a.m.Unlock()
if a.err == nil {
a.err = err
}
}
func (a *atomicError) Load() error {
a.m.Lock()
defer a.m.Unlock()
return a.err
}