2015-01-10 17:23:43 +01:00
|
|
|
// Copyright 2015 Hajime Hoshi
|
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
|
2015-01-23 15:04:56 +01:00
|
|
|
package audio
|
2015-01-10 17:23:43 +01:00
|
|
|
|
|
|
|
import (
|
2016-03-03 03:57:25 +01:00
|
|
|
"fmt"
|
2016-02-10 18:04:23 +01:00
|
|
|
"io"
|
2016-03-03 03:57:25 +01:00
|
|
|
"sync"
|
2015-01-10 17:23:43 +01:00
|
|
|
)
|
|
|
|
|
2016-03-03 03:57:25 +01:00
|
|
|
// TODO: In JavaScript, mixing should be done by WebAudio for performance.
|
|
|
|
type mixedPlayersStream struct {
|
|
|
|
context *Context
|
|
|
|
}
|
|
|
|
|
|
|
|
func min(a, b int) int {
|
|
|
|
if a < b {
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
2016-03-05 08:49:35 +01:00
|
|
|
func max(a, b int) int {
|
|
|
|
if a > b {
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
2016-03-03 03:57:25 +01:00
|
|
|
func (s *mixedPlayersStream) Read(b []byte) (int, error) {
|
|
|
|
s.context.Lock()
|
|
|
|
defer s.context.Unlock()
|
|
|
|
|
2016-03-05 14:42:16 +01:00
|
|
|
l := len(b) / 4 * 4
|
2016-03-03 03:57:25 +01:00
|
|
|
if len(s.context.players) == 0 {
|
2016-03-05 14:42:16 +01:00
|
|
|
ll := min(4096, len(b))
|
|
|
|
copy(b, make([]byte, ll))
|
|
|
|
return ll, nil
|
2016-03-03 03:57:25 +01:00
|
|
|
}
|
|
|
|
closed := []*Player{}
|
2016-03-05 12:09:29 +01:00
|
|
|
bb := make([]byte, l)
|
2016-03-05 08:49:35 +01:00
|
|
|
ll := l
|
2016-03-03 03:57:25 +01:00
|
|
|
for p := range s.context.players {
|
2016-03-05 08:49:35 +01:00
|
|
|
n, err := p.src.Read(bb)
|
2016-03-03 03:57:25 +01:00
|
|
|
if 0 < n {
|
2016-03-05 08:49:35 +01:00
|
|
|
p.buf = append(p.buf, bb[:n]...)
|
2016-03-03 03:57:25 +01:00
|
|
|
}
|
|
|
|
if err == io.EOF {
|
|
|
|
closed = append(closed, p)
|
2016-03-05 14:42:16 +01:00
|
|
|
} else if err != nil {
|
2016-03-05 08:49:35 +01:00
|
|
|
return 0, err
|
2016-03-03 03:57:25 +01:00
|
|
|
}
|
2016-03-05 08:49:35 +01:00
|
|
|
ll = min(len(p.buf)/4*4, ll)
|
2016-03-03 03:57:25 +01:00
|
|
|
}
|
2016-03-05 08:49:35 +01:00
|
|
|
for i := 0; i < ll/2; i++ {
|
2016-03-03 04:22:10 +01:00
|
|
|
x := 0
|
2016-03-03 03:57:25 +01:00
|
|
|
for p := range s.context.players {
|
2016-03-03 04:22:10 +01:00
|
|
|
x += int(int16(p.buf[2*i]) | (int16(p.buf[2*i+1]) << 8))
|
|
|
|
}
|
|
|
|
if x > (1<<15)-1 {
|
|
|
|
x = (1 << 15) - 1
|
|
|
|
}
|
|
|
|
if x < -(1 << 15) {
|
|
|
|
x = -(1 << 15)
|
2016-03-03 03:57:25 +01:00
|
|
|
}
|
|
|
|
b[2*i] = byte(x)
|
|
|
|
b[2*i+1] = byte(x >> 8)
|
|
|
|
}
|
|
|
|
for p := range s.context.players {
|
2016-03-05 08:49:35 +01:00
|
|
|
p.buf = p.buf[ll:]
|
2016-03-03 03:57:25 +01:00
|
|
|
}
|
|
|
|
for _, p := range closed {
|
|
|
|
delete(s.context.players, p)
|
|
|
|
}
|
2016-03-05 08:49:35 +01:00
|
|
|
return ll, nil
|
2016-03-03 03:57:25 +01:00
|
|
|
}
|
|
|
|
|
2016-03-02 16:48:59 +01:00
|
|
|
// TODO: Enable to specify the format like Mono8?
|
|
|
|
|
|
|
|
type Context struct {
|
2016-03-04 17:52:28 +01:00
|
|
|
sampleRate int
|
|
|
|
stream *mixedPlayersStream
|
|
|
|
players map[*Player]struct{}
|
2016-03-03 03:57:25 +01:00
|
|
|
sync.Mutex
|
2016-03-02 16:48:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewContext(sampleRate int) *Context {
|
2016-03-03 03:57:25 +01:00
|
|
|
// TODO: Panic if one context exists.
|
|
|
|
c := &Context{
|
2016-03-02 16:48:59 +01:00
|
|
|
sampleRate: sampleRate,
|
2016-03-03 03:57:25 +01:00
|
|
|
players: map[*Player]struct{}{},
|
2016-03-02 16:48:59 +01:00
|
|
|
}
|
2016-03-03 03:57:25 +01:00
|
|
|
c.stream = &mixedPlayersStream{c}
|
2016-03-04 17:52:28 +01:00
|
|
|
if err := startPlaying(c.stream, c.sampleRate); err != nil {
|
2016-03-03 03:57:25 +01:00
|
|
|
panic(fmt.Sprintf("audio: NewContext error: %v", err))
|
|
|
|
}
|
|
|
|
return c
|
2016-03-02 16:48:59 +01:00
|
|
|
}
|
|
|
|
|
2016-02-10 18:18:39 +01:00
|
|
|
type Player struct {
|
2016-03-03 03:57:25 +01:00
|
|
|
context *Context
|
|
|
|
src io.ReadSeeker
|
|
|
|
buf []byte
|
2016-02-10 18:18:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewPlayer creates a new player with the given data to the given channel.
|
2016-02-07 16:51:25 +01:00
|
|
|
// The given data is queued to the end of the buffer.
|
|
|
|
// This may not be played immediately when data already exists in the buffer.
|
2015-01-24 07:48:48 +01:00
|
|
|
//
|
2016-02-10 18:04:23 +01:00
|
|
|
// src's format must be linear PCM (16bits, 2 channel stereo, little endian)
|
2016-02-07 16:51:25 +01:00
|
|
|
// without a header (e.g. RIFF header).
|
2016-02-09 15:04:00 +01:00
|
|
|
//
|
|
|
|
// TODO: Pass sample rate and num of channels.
|
2016-03-02 16:48:59 +01:00
|
|
|
func (c *Context) NewPlayer(src io.ReadSeeker) (*Player, error) {
|
2016-03-03 03:57:25 +01:00
|
|
|
c.Lock()
|
|
|
|
defer c.Unlock()
|
2016-02-10 18:18:39 +01:00
|
|
|
|
2016-03-03 03:57:25 +01:00
|
|
|
p := &Player{
|
|
|
|
context: c,
|
|
|
|
src: src,
|
|
|
|
buf: []byte{},
|
|
|
|
}
|
|
|
|
return p, nil
|
|
|
|
}
|
2016-02-12 13:39:48 +01:00
|
|
|
|
2016-02-10 18:18:39 +01:00
|
|
|
func (p *Player) Play() error {
|
2016-03-03 03:57:25 +01:00
|
|
|
p.context.Lock()
|
|
|
|
defer p.context.Unlock()
|
|
|
|
|
|
|
|
p.context.players[p] = struct{}{}
|
|
|
|
return nil
|
2015-01-22 19:02:23 +01:00
|
|
|
}
|
2016-02-11 11:55:59 +01:00
|
|
|
|
2016-03-03 03:57:25 +01:00
|
|
|
// TODO: IsPlaying
|
2016-03-04 17:01:57 +01:00
|
|
|
// TODO: Stop
|
|
|
|
// TODO: Seek
|
2016-03-03 03:57:25 +01:00
|
|
|
|
2016-03-04 17:01:57 +01:00
|
|
|
func (p *Player) Pause() error {
|
2016-03-03 03:57:25 +01:00
|
|
|
p.context.Lock()
|
|
|
|
defer p.context.Unlock()
|
|
|
|
|
|
|
|
delete(p.context.players, p)
|
|
|
|
return nil
|
2016-02-11 11:55:59 +01:00
|
|
|
}
|