ebiten/audio/mux.go
2019-01-10 02:34:58 +09:00

153 lines
2.7 KiB
Go

// Copyright 2019 The Ebiten 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 audio
import (
"io"
"runtime"
"sync"
)
type mux struct {
ps map[*playerImpl]struct{}
m sync.RWMutex
}
const (
channelNum = 2
bytesPerSample = 2 * channelNum
// TODO: This assumes that bytesPerSample is a power of 2.
mask = ^(bytesPerSample - 1)
)
func newMux() *mux {
return &mux{
ps: map[*playerImpl]struct{}{},
}
}
func (m *mux) Read(b []byte) (int, error) {
m.m.Lock()
defer m.m.Unlock()
if len(m.ps) == 0 {
l := len(b)
l &= mask
copy(b, make([]byte, l))
return l, nil
}
l := len(b)
l &= mask
allSkipped := true
// TODO: Now a player is not locked. Should we lock it?
for p := range m.ps {
if p.shouldSkip() {
continue
}
allSkipped = false
s := p.bufferSizeInBytes()
if l > s {
l = s
l &= mask
}
}
if allSkipped {
l = 0
}
if l == 0 {
// If l is 0, all the ps might reach EOF at the next update.
// However, this Read might block forever and never causes context switch
// on single-thread environment (e.g. browser).
// Call Gosched to cause context switch on purpose.
runtime.Gosched()
}
b16s := [][]int16{}
for p := range m.ps {
buf, err := p.bufferToInt16(l)
if err != nil {
return 0, err
}
b16s = append(b16s, buf)
}
for i := 0; i < l/2; i++ {
x := 0
for _, b16 := range b16s {
x += int(b16[i])
}
if x > (1<<15)-1 {
x = (1 << 15) - 1
}
if x < -(1 << 15) {
x = -(1 << 15)
}
b[2*i] = byte(x)
b[2*i+1] = byte(x >> 8)
}
closed := []*playerImpl{}
for p := range m.ps {
if p.eof() {
closed = append(closed, p)
}
}
for _, p := range closed {
if p.isFinalized() {
p.closeImpl()
}
delete(m.ps, p)
}
return l, nil
}
func (m *mux) addPlayer(player *playerImpl) {
m.m.Lock()
m.ps[player] = struct{}{}
m.m.Unlock()
}
func (m *mux) removePlayer(player *playerImpl) {
m.m.Lock()
delete(m.ps, player)
m.m.Unlock()
}
func (m *mux) hasPlayer(player *playerImpl) bool {
m.m.RLock()
_, ok := m.ps[player]
m.m.RUnlock()
return ok
}
func (m *mux) hasSource(src io.ReadCloser) bool {
m.m.RLock()
defer m.m.RUnlock()
for p := range m.ps {
if p.src == src {
return true
}
}
return false
}