mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-24 02:38:53 +01:00
audio/vorbis: Use stb_vorbis.c on browsers
This change adds a Ogg/Vorbis decoder stb_vorbis.c usage that is compiled by Emscripten as WebAssembly. Fixes #623
This commit is contained in:
parent
21fd6a2edc
commit
992f6c767a
4
audio/vorbis/internal/stb/.gitignore
vendored
Normal file
4
audio/vorbis/internal/stb/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
*.wasm
|
||||
*.wasm.map
|
||||
*.wast
|
||||
|
2
audio/vorbis/internal/stb/build.sh
Executable file
2
audio/vorbis/internal/stb/build.sh
Executable file
@ -0,0 +1,2 @@
|
||||
emcc -Os -o stbvorbis.js -g -DSTB_VORBIS_NO_STDIO -s WASM=1 -s EXPORTED_FUNCTIONS='["_stb_vorbis_decode_memory"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall","cwrap"]' -s ALLOW_MEMORY_GROWTH=1 stb_vorbis.c
|
||||
go run genwasmjs.go < stbvorbis.wasm > wasm.js
|
47
audio/vorbis/internal/stb/decode.go
Normal file
47
audio/vorbis/internal/stb/decode.go
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2018 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 stb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gopherjs/gopherwasm/js"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Eval wasm.js first to set the Wasm binary to Module.
|
||||
js.Global().Get("window").Call("eval", string(wasm_js))
|
||||
|
||||
js.Global().Get("window").Call("eval", string(stbvorbis_js))
|
||||
js.Global().Get("window").Call("eval", string(decode_js))
|
||||
|
||||
ch := make(chan struct{})
|
||||
js.Global().Get("_ebiten").Call("initializeVorbisDecoder", js.NewCallback(func([]js.Value) {
|
||||
close(ch)
|
||||
}))
|
||||
<-ch
|
||||
}
|
||||
|
||||
func DecodeVorbis(buf []byte) ([]int16, int, int, error) {
|
||||
r := js.Global().Get("_ebiten").Call("decodeVorbis", buf)
|
||||
if r == js.Null() {
|
||||
return nil, 0, 0, fmt.Errorf("audio/vorbis/internal/stb: decode failed")
|
||||
}
|
||||
data := make([]int16, r.Get("data").Get("length").Int())
|
||||
// TODO: Use js.TypeArrayOf
|
||||
arr := js.ValueOf(data)
|
||||
arr.Call("set", r.Get("data"))
|
||||
return data, r.Get("channels").Int(), r.Get("sampleRate").Int(), nil
|
||||
}
|
80
audio/vorbis/internal/stb/decode.js
Normal file
80
audio/vorbis/internal/stb/decode.js
Normal file
@ -0,0 +1,80 @@
|
||||
// Copyright 2018 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.
|
||||
|
||||
var _ebiten = {};
|
||||
|
||||
(() => {
|
||||
var decodeMemory = null;
|
||||
var vorbisDecoderInitialized = null;
|
||||
|
||||
_ebiten.initializeVorbisDecoder = (callback) => {
|
||||
Module.run();
|
||||
vorbisDecoderInitialized = callback;
|
||||
};
|
||||
|
||||
Module.onRuntimeInitialized = () => {
|
||||
decodeMemory = Module.cwrap('stb_vorbis_decode_memory', 'number', ['number', 'number', 'number', 'number', 'number']);
|
||||
if (vorbisDecoderInitialized) {
|
||||
vorbisDecoderInitialized();
|
||||
}
|
||||
}
|
||||
|
||||
function arrayToHeap(typedArray){
|
||||
const ptr = Module._malloc(typedArray.byteLength);
|
||||
const heapBytes = new Uint8Array(Module.HEAPU8.buffer, ptr, typedArray.byteLength);
|
||||
heapBytes.set(new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength));
|
||||
return heapBytes;
|
||||
}
|
||||
|
||||
function ptrToInt32(ptr) {
|
||||
const a = new Int32Array(Module.HEAPU8.buffer, ptr, 1);
|
||||
return a[0];
|
||||
}
|
||||
|
||||
function ptrToFloat32(ptr) {
|
||||
const a = new Float32Array(Module.HEAPU8.buffer, ptr, 1);
|
||||
return a[0];
|
||||
}
|
||||
|
||||
function ptrToInt16s(ptr, length) {
|
||||
const buf = new ArrayBuffer(length * Int16Array.BYTES_PER_ELEMENT);
|
||||
const copied = new Int16Array(buf);
|
||||
copied.set(new Int16Array(Module.HEAPU8.buffer, ptr, length));
|
||||
return copied;
|
||||
}
|
||||
|
||||
_ebiten.decodeVorbis = (buf) => {
|
||||
const copiedBuf = arrayToHeap(buf);
|
||||
const channelsPtr = Module._malloc(4);
|
||||
const sampleRatePtr = Module._malloc(4);
|
||||
const outputPtr = Module._malloc(4);
|
||||
const length = decodeMemory(copiedBuf.byteOffset, copiedBuf.length, channelsPtr, sampleRatePtr, outputPtr);
|
||||
if (length < 0) {
|
||||
return null;
|
||||
}
|
||||
const channels = ptrToInt32(channelsPtr);
|
||||
const result = {
|
||||
data: ptrToInt16s(ptrToInt32(outputPtr), length * channels),
|
||||
channels: channels,
|
||||
sampleRate: ptrToInt32(sampleRatePtr),
|
||||
};
|
||||
|
||||
Module._free(copiedBuf.byteOffset);
|
||||
Module._free(channelsPtr);
|
||||
Module._free(sampleRatePtr);
|
||||
Module._free(ptrToInt32(outputPtr));
|
||||
Module._free(outputPtr);
|
||||
return result;
|
||||
};
|
||||
})();
|
6
audio/vorbis/internal/stb/decodejs_file_js.go
Normal file
6
audio/vorbis/internal/stb/decodejs_file_js.go
Normal file
@ -0,0 +1,6 @@
|
||||
// Code generated by file2byteslice. DO NOT EDIT.
|
||||
// (gofmt is fine after generating)
|
||||
|
||||
package stb
|
||||
|
||||
var decode_js = []byte("// Copyright 2018 The Ebiten Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nvar _ebiten = {};\n\n(() => {\n var decodeMemory = null;\n var vorbisDecoderInitialized = null;\n\n _ebiten.initializeVorbisDecoder = (callback) => {\n Module.run();\n vorbisDecoderInitialized = callback;\n };\n\n Module.onRuntimeInitialized = () => {\n decodeMemory = Module.cwrap('stb_vorbis_decode_memory', 'number', ['number', 'number', 'number', 'number', 'number']);\n if (vorbisDecoderInitialized) {\n vorbisDecoderInitialized();\n }\n }\n\n function arrayToHeap(typedArray){\n const ptr = Module._malloc(typedArray.byteLength);\n const heapBytes = new Uint8Array(Module.HEAPU8.buffer, ptr, typedArray.byteLength);\n heapBytes.set(new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength));\n return heapBytes;\n }\n\n function ptrToInt32(ptr) {\n const a = new Int32Array(Module.HEAPU8.buffer, ptr, 1);\n return a[0];\n }\n\n function ptrToFloat32(ptr) {\n const a = new Float32Array(Module.HEAPU8.buffer, ptr, 1);\n return a[0];\n }\n\n function ptrToInt16s(ptr, length) {\n const buf = new ArrayBuffer(length * Int16Array.BYTES_PER_ELEMENT);\n const copied = new Int16Array(buf);\n copied.set(new Int16Array(Module.HEAPU8.buffer, ptr, length));\n return copied;\n }\n\n _ebiten.decodeVorbis = (buf) => {\n const copiedBuf = arrayToHeap(buf);\n const channelsPtr = Module._malloc(4);\n const sampleRatePtr = Module._malloc(4);\n const outputPtr = Module._malloc(4);\n const length = decodeMemory(copiedBuf.byteOffset, copiedBuf.length, channelsPtr, sampleRatePtr, outputPtr);\n if (length < 0) {\n return null;\n }\n const channels = ptrToInt32(channelsPtr);\n const result = {\n data: ptrToInt16s(ptrToInt32(outputPtr), length * channels),\n channels: channels,\n sampleRate: ptrToInt32(sampleRatePtr),\n };\n\n Module._free(copiedBuf.byteOffset);\n Module._free(channelsPtr);\n Module._free(sampleRatePtr);\n Module._free(ptrToInt32(outputPtr));\n Module._free(outputPtr);\n return result;\n };\n})();\n")
|
20
audio/vorbis/internal/stb/generate.go
Normal file
20
audio/vorbis/internal/stb/generate.go
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2018 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 stb
|
||||
|
||||
//go:generate bash build.sh
|
||||
//go:generate file2byteslice -package=stb -input=decode.js -output=decodejs_file_js.go -var=decode_js
|
||||
//go:generate file2byteslice -package=stb -input=stbvorbis.js -output=stbvorbisjs_file_js.go -var=stbvorbis_js
|
||||
//go:generate file2byteslice -package=stb -input=wasm.js -output=wasmjs_file_js.go -var=wasm_js
|
43
audio/vorbis/internal/stb/genwasmjs.go
Normal file
43
audio/vorbis/internal/stb/genwasmjs.go
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright 2018 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 ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
func run() error {
|
||||
bin, err := ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("// Code generated by genwasmjs.go. DO NOT EDIT.")
|
||||
fmt.Println("")
|
||||
fmt.Println("var Module = typeof Module !== 'undefined' ? Module : {};")
|
||||
fmt.Printf("Module['wasmBinary'] = Uint8Array.from(atob(%q), c => c.charCodeAt(0));\n", base64.StdEncoding.EncodeToString(bin))
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
5464
audio/vorbis/internal/stb/stb_vorbis.c
Normal file
5464
audio/vorbis/internal/stb/stb_vorbis.c
Normal file
File diff suppressed because it is too large
Load Diff
2042
audio/vorbis/internal/stb/stbvorbis.js
Normal file
2042
audio/vorbis/internal/stb/stbvorbis.js
Normal file
File diff suppressed because it is too large
Load Diff
6
audio/vorbis/internal/stb/stbvorbisjs_file_js.go
Normal file
6
audio/vorbis/internal/stb/stbvorbisjs_file_js.go
Normal file
File diff suppressed because one or more lines are too long
4
audio/vorbis/internal/stb/wasm.js
Normal file
4
audio/vorbis/internal/stb/wasm.js
Normal file
File diff suppressed because one or more lines are too long
6
audio/vorbis/internal/stb/wasmjs_file_js.go
Normal file
6
audio/vorbis/internal/stb/wasmjs_file_js.go
Normal file
File diff suppressed because one or more lines are too long
@ -20,8 +20,6 @@ import (
|
||||
"io"
|
||||
"runtime"
|
||||
|
||||
"github.com/jfreymuth/oggvorbis"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/audio"
|
||||
"github.com/hajimehoshi/ebiten/audio/internal/convert"
|
||||
)
|
||||
@ -63,13 +61,20 @@ func (s *Stream) Size() int64 {
|
||||
return s.Length()
|
||||
}
|
||||
|
||||
type decoder interface {
|
||||
Read([]float32) (int, error)
|
||||
Length() int64
|
||||
Channels() int
|
||||
SampleRate() int
|
||||
}
|
||||
|
||||
type decoded struct {
|
||||
data []float32
|
||||
totalBytes int
|
||||
readBytes int
|
||||
posInBytes int
|
||||
source io.Closer
|
||||
decoder *oggvorbis.Reader
|
||||
decoder decoder
|
||||
}
|
||||
|
||||
func (d *decoded) readUntil(posInBytes int) error {
|
||||
@ -93,7 +98,6 @@ func (d *decoded) readUntil(posInBytes int) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
runtime.Gosched()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -157,7 +161,7 @@ func (d *decoded) Length() int64 {
|
||||
|
||||
// decode accepts an ogg stream and returns a decorded stream.
|
||||
func decode(in audio.ReadSeekCloser) (*decoded, int, int, error) {
|
||||
r, err := oggvorbis.NewReader(in)
|
||||
r, err := newDecoder(in)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
72
audio/vorbis/vorbis_js.go
Normal file
72
audio/vorbis/vorbis_js.go
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright 2018 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 vorbis
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/audio"
|
||||
"github.com/hajimehoshi/ebiten/audio/vorbis/internal/stb"
|
||||
)
|
||||
|
||||
type decoderImpl struct {
|
||||
data []int16
|
||||
channels int
|
||||
sampleRate int
|
||||
}
|
||||
|
||||
func (d *decoderImpl) Read(buf []float32) (int, error) {
|
||||
if len(d.data) == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
n := len(buf)
|
||||
if n > len(d.data) {
|
||||
n = len(d.data)
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
buf[i] = float32(d.data[i]) / 32768
|
||||
}
|
||||
d.data = d.data[n:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (d *decoderImpl) Length() int64 {
|
||||
return int64(len(d.data) / d.channels)
|
||||
}
|
||||
|
||||
func (d *decoderImpl) Channels() int {
|
||||
return d.channels
|
||||
}
|
||||
|
||||
func (d *decoderImpl) SampleRate() int {
|
||||
return d.sampleRate
|
||||
}
|
||||
|
||||
func newDecoder(in audio.ReadSeekCloser) (decoder, error) {
|
||||
buf, err := ioutil.ReadAll(in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, channels, sampleRate, err := stb.DecodeVorbis(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &decoderImpl{data, channels, sampleRate}, nil
|
||||
}
|
27
audio/vorbis/vorbis_notjs.go
Normal file
27
audio/vorbis/vorbis_notjs.go
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2018 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 vorbis
|
||||
|
||||
import (
|
||||
"github.com/jfreymuth/oggvorbis"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/audio"
|
||||
)
|
||||
|
||||
func newDecoder(in audio.ReadSeekCloser) (decoder, error) {
|
||||
return oggvorbis.NewReader(in)
|
||||
}
|
Loading…
Reference in New Issue
Block a user