diff --git a/exp/audio/audio.go b/exp/audio/audio.go index 642f932cf..28e41b876 100644 --- a/exp/audio/audio.go +++ b/exp/audio/audio.go @@ -21,6 +21,7 @@ import ( "time" "github.com/hajimehoshi/ebiten" + "github.com/hajimehoshi/ebiten/exp/audio/internal/driver" ) type mixingStream struct { @@ -44,7 +45,6 @@ func min(a, b int) int { const ( channelNum = 2 bytesPerSample = 2 - bitsPerSample = bytesPerSample * 8 // TODO: This assumes that channelNum is a power of 2. mask = ^(channelNum*bytesPerSample - 1) @@ -197,15 +197,15 @@ func NewContext(sampleRate int) (*Context, error) { players: map[*Player]struct{}{}, } // TODO: Rename this other than player - p, err := newPlayer(c.stream, sampleRate) + p, err := driver.NewPlayer(c.stream, sampleRate, channelNum, bytesPerSample) if err != nil { return nil, err } go func() { // TODO: Is it OK to close asap? - defer p.close() + defer p.Close() for { - err := p.proceed() + err := p.Proceed() if err == io.EOF { break } diff --git a/exp/audio/audio_js.go b/exp/audio/internal/driver/driver_js.go similarity index 79% rename from exp/audio/audio_js.go rename to exp/audio/internal/driver/driver_js.go index 0d073609f..8869661a0 100644 --- a/exp/audio/audio_js.go +++ b/exp/audio/internal/driver/driver_js.go @@ -14,25 +14,26 @@ // +build js -package audio +package driver import ( "errors" "io" "github.com/gopherjs/gopherjs/js" - "github.com/hajimehoshi/ebiten" ) -type player struct { +type Player struct { src io.Reader sampleRate int + channelNum int + bytesPerSample int positionInSamples int64 context *js.Object bufferSource *js.Object } -func newPlayer(src io.Reader, sampleRate int) (*player, error) { +func NewPlayer(src io.Reader, sampleRate, channelNum, bytesPerSample int) (*Player, error) { class := js.Global.Get("AudioContext") if class == js.Undefined { class = js.Global.Get("webkitAudioContext") @@ -40,11 +41,13 @@ func newPlayer(src io.Reader, sampleRate int) (*player, error) { if class == js.Undefined { return nil, errors.New("audio: audio couldn't be initialized") } - p := &player{ - src: src, - sampleRate: sampleRate, - bufferSource: nil, - context: class.New(), + p := &Player{ + src: src, + sampleRate: sampleRate, + channelNum: channelNum, + bytesPerSample: bytesPerSample, + bufferSource: nil, + context: class.New(), } p.positionInSamples = int64(p.context.Get("currentTime").Float() * float64(p.sampleRate)) return p, nil @@ -67,8 +70,12 @@ func max64(a, b int64) int64 { return b } -func (p *player) proceed() error { - bufferSize := p.sampleRate * bytesPerSample * channelNum / ebiten.FPS +const ( + // 1024 seems not enough (some noise remains after the tab is deactivated). + bufferSize = 2048 +) + +func (p *Player) Proceed() error { c := int64(p.context.Get("currentTime").Float() * float64(p.sampleRate)) if p.positionInSamples < c { p.positionInSamples = c @@ -76,7 +83,7 @@ func (p *player) proceed() error { b := make([]byte, bufferSize) n, err := p.src.Read(b) if 0 < n { - buf := p.context.Call("createBuffer", channelNum, n/bytesPerSample/channelNum, p.sampleRate) + buf := p.context.Call("createBuffer", p.channelNum, n/p.bytesPerSample/p.channelNum, p.sampleRate) l := buf.Call("getChannelData", 0) r := buf.Call("getChannelData", 1) il, ir := toLR(b[:n]) @@ -96,7 +103,7 @@ func (p *player) proceed() error { return err } -func (p *player) close() error { +func (p *Player) Close() error { if p.bufferSource == nil { return nil } diff --git a/exp/audio/audio_openal.go b/exp/audio/internal/driver/driver_openal.go similarity index 92% rename from exp/audio/audio_openal.go rename to exp/audio/internal/driver/driver_openal.go index c1f9390aa..2edff16ac 100644 --- a/exp/audio/audio_openal.go +++ b/exp/audio/internal/driver/driver_openal.go @@ -14,7 +14,7 @@ // +build !js,!windows -package audio +package driver import ( "fmt" @@ -28,7 +28,7 @@ const ( maxBufferNum = 8 ) -type player struct { +type Player struct { alSource al.Source alBuffers []al.Buffer source io.Reader @@ -36,7 +36,7 @@ type player struct { isClosed bool } -func newPlayer(src io.Reader, sampleRate int) (*player, error) { +func NewPlayer(src io.Reader, sampleRate, channelNum, bytesPerSample int) (*Player, error) { if e := al.OpenDevice(); e != nil { return nil, fmt.Errorf("audio: OpenAL initialization failed: %v", e) } @@ -44,13 +44,13 @@ func newPlayer(src io.Reader, sampleRate int) (*player, error) { if err := al.Error(); err != 0 { return nil, fmt.Errorf("audio: al.GenSources error: %d", err) } - p := &player{ + p := &Player{ alSource: s[0], alBuffers: []al.Buffer{}, source: src, sampleRate: sampleRate, } - runtime.SetFinalizer(p, (*player).close) + runtime.SetFinalizer(p, (*Player).Close) bs := al.GenBuffers(maxBufferNum) emptyBytes := make([]byte, bufferSize) @@ -72,7 +72,7 @@ var ( tmpAlBuffers = make([]al.Buffer, maxBufferNum) ) -func (p *player) proceed() error { +func (p *Player) Proceed() error { if err := al.Error(); err != 0 { return fmt.Errorf("audio: before proceed: %d", err) } @@ -113,7 +113,7 @@ func (p *player) proceed() error { return nil } -func (p *player) close() error { +func (p *Player) Close() error { if err := al.Error(); err != 0 { return fmt.Errorf("audio: error before closing: %d", err) } diff --git a/exp/audio/audio_windows.c b/exp/audio/internal/driver/driver_windows.c similarity index 100% rename from exp/audio/audio_windows.c rename to exp/audio/internal/driver/driver_windows.c diff --git a/exp/audio/audio_windows.go b/exp/audio/internal/driver/driver_windows.go similarity index 90% rename from exp/audio/audio_windows.go rename to exp/audio/internal/driver/driver_windows.go index d8bbcf8a8..9c3e16990 100644 --- a/exp/audio/audio_windows.go +++ b/exp/audio/internal/driver/driver_windows.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package audio +package driver // #cgo LDFLAGS: -lwinmm // @@ -75,7 +75,7 @@ func releaseSemaphore() { <-sem } -type player struct { +type Player struct { src io.Reader out C.HWAVEOUT buffer []byte @@ -84,21 +84,21 @@ type player struct { const bufferSize = 1024 -func newPlayer(src io.Reader, sampleRate int) (*player, error) { - const numBlockAlign = channelNum * bitsPerSample / 8 +func NewPlayer(src io.Reader, sampleRate, channelNum, bytesPerSample int) (*Player, error) { + const numBlockAlign = channelNum * bytesPerSample f := C.WAVEFORMATEX{ wFormatTag: C.WAVE_FORMAT_PCM, nChannels: channelNum, nSamplesPerSec: C.DWORD(sampleRate), nAvgBytesPerSec: C.DWORD(sampleRate) * numBlockAlign, - wBitsPerSample: bitsPerSample, + wBitsPerSample: bytesPerSample * 8, nBlockAlign: numBlockAlign, } var w C.HWAVEOUT if err := C.waveOutOpen2(&w, &f); err != C.MMSYSERR_NOERROR { return nil, fmt.Errorf("audio: waveOutOpen error: %d", err) } - p := &player{ + p := &Player{ src: src, out: w, buffer: []byte{}, @@ -114,7 +114,7 @@ func newPlayer(src io.Reader, sampleRate int) (*player, error) { return p, nil } -func (p *player) proceed() error { +func (p *Player) Proceed() error { if len(p.buffer) < bufferSize { b := make([]byte, bufferSize) n, err := p.src.Read(b) @@ -130,7 +130,7 @@ func (p *player) proceed() error { headerToWrite := (*header)(nil) for _, h := range p.headers { // TODO: Need to check WHDR_DONE? - if h.waveHdr.dwFlags & C.WHDR_INQUEUE == 0 { + if h.waveHdr.dwFlags&C.WHDR_INQUEUE == 0 { headerToWrite = h break } @@ -146,6 +146,6 @@ func (p *player) proceed() error { return nil } -func (p *player) close() { +func (p *Player) Close() { // TODO: Implement this }