From 0f20b7b21662a235959d6244145f975653830baf Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Sun, 18 Jun 2017 23:49:05 +0900 Subject: [PATCH] audio/mp3: Add JavaScript version --- audio/mp3/decode.go | 66 ------------------------ audio/mp3/decode_js.go | 106 ++++++++++++++++++++++++++++++++++++++ audio/mp3/decode_notjs.go | 49 +++++++++++++++++- 3 files changed, 153 insertions(+), 68 deletions(-) delete mode 100644 audio/mp3/decode.go create mode 100644 audio/mp3/decode_js.go diff --git a/audio/mp3/decode.go b/audio/mp3/decode.go deleted file mode 100644 index 3dcb65051..000000000 --- a/audio/mp3/decode.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2017 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 mp3 provides MP3 decoder. -// -// This package is very experimental. -package mp3 - -import ( - "github.com/hajimehoshi/ebiten/audio" - "github.com/hajimehoshi/ebiten/audio/internal/convert" -) - -type Stream struct { - inner audio.ReadSeekCloser - size int64 -} - -// Read is implementation of io.Reader's Read. -func (s *Stream) Read(buf []byte) (int, error) { - return s.inner.Read(buf) -} - -// Seek is implementation of io.Seeker's Seek. -func (s *Stream) Seek(offset int64, whence int) (int64, error) { - return s.inner.Seek(offset, whence) -} - -// Read is implementation of io.Closer's Close. -func (s *Stream) Close() error { - return s.inner.Close() -} - -// Size returns the size of decoded stream in bytes. -func (s *Stream) Size() int64 { - return s.size -} - -func Decode(context *audio.Context, src audio.ReadSeekCloser) (*Stream, error) { - d, err := decode(src) - if err != nil { - return nil, err - } - // TODO: Resampling - var s audio.ReadSeekCloser = convert.NewSeeker(d) - size := d.Length() - if d.SampleRate() != context.SampleRate() { - s = convert.NewResampling(s, d.Length(), d.SampleRate(), context.SampleRate()) - size = size * int64(context.SampleRate()) / int64(d.SampleRate()) - } - return &Stream{ - inner: s, - size: size, - }, nil -} diff --git a/audio/mp3/decode_js.go b/audio/mp3/decode_js.go new file mode 100644 index 000000000..09fb5fe2b --- /dev/null +++ b/audio/mp3/decode_js.go @@ -0,0 +1,106 @@ +// Copyright 2017 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. + +// +build js + +package mp3 + +import ( + "errors" + "io" + "io/ioutil" + + "github.com/gopherjs/gopherjs/js" + "github.com/hajimehoshi/ebiten/audio" +) + +// TODO: This just uses decodeAudioData can treat audio files other than MP3. + +type Stream struct { + leftData []float32 + rightData []float32 + posInBytes int +} + +func (s *Stream) Read(b []byte) (int, error) { + l := len(s.leftData)*4 - s.posInBytes + if l > len(b) { + l = len(b) + } + l = l / 4 * 4 + for i := 0; i < l/4; i++ { + il := int16(s.leftData[s.posInBytes/4+i] * (1 << 15)) + ir := int16(s.rightData[s.posInBytes/4+i] * (1 << 15)) + b[4*i] = uint8(il) + b[4*i+1] = uint8(il >> 8) + b[4*i+2] = uint8(ir) + b[4*i+3] = uint8(ir >> 8) + } + s.posInBytes += l + if s.posInBytes == len(s.leftData)*4 { + return l, io.EOF + } + return l, nil +} + +func (s *Stream) Seek(offset int64, whence int) (int64, error) { + next := int64(0) + switch whence { + case 0: + next = offset + case 1: + next = int64(s.posInBytes) + offset + case 2: + next = int64(len(s.leftData)*4) + offset + } + s.posInBytes = int(next) + return next, nil +} + +func (s *Stream) Close() error { + return nil +} + +func (s *Stream) Size() int64 { + return int64(len(s.leftData) * 4) +} + +func Decode(context *audio.Context, src audio.ReadSeekCloser) (*Stream, error) { + b, err := ioutil.ReadAll(src) + if err != nil { + return nil, err + } + if err := src.Close(); err != nil { + return nil, err + } + s := &Stream{} + ch := make(chan struct{}) + + klass := js.Global.Get("OfflineAudioContext") + if klass == js.Undefined { + klass = js.Global.Get("webkitOfflineAudioContext") + } + if klass == js.Undefined { + return nil, errors.New("vorbis: OfflineAudioContext is not available") + } + // TODO: 1 is a correct second argument? + oc := klass.New(2, 1, context.SampleRate()) + oc.Call("decodeAudioData", js.NewArrayBuffer(b), func(buf *js.Object) { + s.leftData = buf.Call("getChannelData", 0).Interface().([]float32) + s.rightData = buf.Call("getChannelData", 1).Interface().([]float32) + close(ch) + }) + <-ch + return s, nil +} diff --git a/audio/mp3/decode_notjs.go b/audio/mp3/decode_notjs.go index 37a5749bd..b1b24fed4 100644 --- a/audio/mp3/decode_notjs.go +++ b/audio/mp3/decode_notjs.go @@ -12,14 +12,59 @@ // See the License for the specific language governing permissions and // limitations under the License. +// +build !js + +// Package mp3 provides MP3 decoder. +// +// This package is very experimental. package mp3 import ( "github.com/hajimehoshi/go-mp3" "github.com/hajimehoshi/ebiten/audio" + "github.com/hajimehoshi/ebiten/audio/internal/convert" ) -func decode(src audio.ReadSeekCloser) (*mp3.Decoded, error) { - return mp3.Decode(src) +type Stream struct { + inner audio.ReadSeekCloser + size int64 +} + +// Read is implementation of io.Reader's Read. +func (s *Stream) Read(buf []byte) (int, error) { + return s.inner.Read(buf) +} + +// Seek is implementation of io.Seeker's Seek. +func (s *Stream) Seek(offset int64, whence int) (int64, error) { + return s.inner.Seek(offset, whence) +} + +// Read is implementation of io.Closer's Close. +func (s *Stream) Close() error { + return s.inner.Close() +} + +// Size returns the size of decoded stream in bytes. +func (s *Stream) Size() int64 { + return s.size +} + +func Decode(context *audio.Context, src audio.ReadSeekCloser) (*Stream, error) { + d, err := mp3.Decode(src) + if err != nil { + return nil, err + } + // TODO: Resampling + var s audio.ReadSeekCloser = convert.NewSeeker(d) + size := d.Length() + if d.SampleRate() != context.SampleRate() { + s = convert.NewResampling(s, d.Length(), d.SampleRate(), context.SampleRate()) + size = size * int64(context.SampleRate()) / int64(d.SampleRate()) + } + return &Stream{ + inner: s, + size: size, + }, nil }