mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 03:08:54 +01:00
Add Experimental Audio API
This commit is contained in:
parent
9d5ab644a4
commit
355da1bcbc
@ -31,7 +31,7 @@ window.addEventListener('load', function() {
|
|||||||
var s = document.createElement('script');
|
var s = document.createElement('script');
|
||||||
var src = 'blocks.js';
|
var src = 'blocks.js';
|
||||||
if (isProduction()) {
|
if (isProduction()) {
|
||||||
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/master/' + src;
|
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/audio/' + src;
|
||||||
}
|
}
|
||||||
s.src = src;
|
s.src = src;
|
||||||
document.body.appendChild(s);
|
document.body.appendChild(s);
|
||||||
|
@ -31,7 +31,7 @@ window.addEventListener('load', function() {
|
|||||||
var s = document.createElement('script');
|
var s = document.createElement('script');
|
||||||
var src = 'hue.js';
|
var src = 'hue.js';
|
||||||
if (isProduction()) {
|
if (isProduction()) {
|
||||||
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/master/' + src;
|
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/audio/' + src;
|
||||||
}
|
}
|
||||||
s.src = src;
|
s.src = src;
|
||||||
document.body.appendChild(s);
|
document.body.appendChild(s);
|
||||||
|
@ -31,7 +31,7 @@ window.addEventListener('load', function() {
|
|||||||
var s = document.createElement('script');
|
var s = document.createElement('script');
|
||||||
var src = 'keyboard.js';
|
var src = 'keyboard.js';
|
||||||
if (isProduction()) {
|
if (isProduction()) {
|
||||||
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/master/' + src;
|
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/audio/' + src;
|
||||||
}
|
}
|
||||||
s.src = src;
|
s.src = src;
|
||||||
document.body.appendChild(s);
|
document.body.appendChild(s);
|
||||||
|
@ -31,7 +31,7 @@ window.addEventListener('load', function() {
|
|||||||
var s = document.createElement('script');
|
var s = document.createElement('script');
|
||||||
var src = 'mosaic.js';
|
var src = 'mosaic.js';
|
||||||
if (isProduction()) {
|
if (isProduction()) {
|
||||||
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/master/' + src;
|
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/audio/' + src;
|
||||||
}
|
}
|
||||||
s.src = src;
|
s.src = src;
|
||||||
document.body.appendChild(s);
|
document.body.appendChild(s);
|
||||||
|
@ -31,7 +31,7 @@ window.addEventListener('load', function() {
|
|||||||
var s = document.createElement('script');
|
var s = document.createElement('script');
|
||||||
var src = 'paint.js';
|
var src = 'paint.js';
|
||||||
if (isProduction()) {
|
if (isProduction()) {
|
||||||
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/master/' + src;
|
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/audio/' + src;
|
||||||
}
|
}
|
||||||
s.src = src;
|
s.src = src;
|
||||||
document.body.appendChild(s);
|
document.body.appendChild(s);
|
||||||
|
@ -31,7 +31,7 @@ window.addEventListener('load', function() {
|
|||||||
var s = document.createElement('script');
|
var s = document.createElement('script');
|
||||||
var src = 'perspective.js';
|
var src = 'perspective.js';
|
||||||
if (isProduction()) {
|
if (isProduction()) {
|
||||||
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/master/' + src;
|
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/audio/' + src;
|
||||||
}
|
}
|
||||||
s.src = src;
|
s.src = src;
|
||||||
document.body.appendChild(s);
|
document.body.appendChild(s);
|
||||||
|
@ -31,7 +31,7 @@ window.addEventListener('load', function() {
|
|||||||
var s = document.createElement('script');
|
var s = document.createElement('script');
|
||||||
var src = 'rotate.js';
|
var src = 'rotate.js';
|
||||||
if (isProduction()) {
|
if (isProduction()) {
|
||||||
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/master/' + src;
|
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/audio/' + src;
|
||||||
}
|
}
|
||||||
s.src = src;
|
s.src = src;
|
||||||
document.body.appendChild(s);
|
document.body.appendChild(s);
|
||||||
|
23
audio.go
Normal file
23
audio.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package ebiten
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hajimehoshi/ebiten/internal/audio"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AppendAudioBuffer(l []float32, r []float32) {
|
||||||
|
audio.Append(l, r)
|
||||||
|
}
|
87
example/audio/main.go
Normal file
87
example/audio/main.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/hajimehoshi/ebiten"
|
||||||
|
"github.com/hajimehoshi/ebiten/ebitenutil"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
screenWidth = 320
|
||||||
|
screenHeight = 240
|
||||||
|
)
|
||||||
|
|
||||||
|
var frames = 0
|
||||||
|
|
||||||
|
const (
|
||||||
|
hzA = 440.0
|
||||||
|
hzAS = 466.2
|
||||||
|
hzB = 493.9
|
||||||
|
hzC = 523.3
|
||||||
|
hzCS = 554.4
|
||||||
|
hzD = 587.3
|
||||||
|
hzDS = 622.3
|
||||||
|
hzE = 659.3
|
||||||
|
hzF = 698.5
|
||||||
|
hzFS = 740.0
|
||||||
|
hzG = 784.0
|
||||||
|
hzGS = 830.6
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: Need API to get sample rate?
|
||||||
|
const sampleRate = 44100
|
||||||
|
|
||||||
|
func square(out []float32, hz float64, sequence float64) {
|
||||||
|
length := int(sampleRate / hz)
|
||||||
|
if length == 0 {
|
||||||
|
panic("invalid hz")
|
||||||
|
}
|
||||||
|
for i := 0; i < len(out); i++ {
|
||||||
|
a := float32(1.0)
|
||||||
|
if i%length < int(float64(length)*sequence) {
|
||||||
|
a = 0
|
||||||
|
}
|
||||||
|
out[i] = a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(screen *ebiten.Image) error {
|
||||||
|
defer func() {
|
||||||
|
frames++
|
||||||
|
}()
|
||||||
|
|
||||||
|
const size = sampleRate / 60 // 3600 BPM
|
||||||
|
notes := []float64{hzA, hzB, hzC, hzD, hzE, hzF, hzG, hzA * 2}
|
||||||
|
if frames%30 == 0 {
|
||||||
|
l := make([]float32, size*30)
|
||||||
|
r := make([]float32, size*30)
|
||||||
|
note := notes[rand.Intn(len(notes))]
|
||||||
|
square(l, note, 0.5)
|
||||||
|
square(r, note, 0.5)
|
||||||
|
ebiten.AppendAudioBuffer(l, r)
|
||||||
|
}
|
||||||
|
ebitenutil.DebugPrint(screen, fmt.Sprintf("%0.2f", ebiten.CurrentFPS()))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Rotate (Ebiten Demo)"); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
@ -61,7 +61,7 @@ func createJSIfNeeded(name string) (string, error) {
|
|||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if (err != nil && os.IsNotExist(err)) || time.Now().Sub(stat.ModTime()) > 10*time.Second {
|
if (err != nil && os.IsNotExist(err)) || time.Now().Sub(stat.ModTime()) > 5*time.Second {
|
||||||
target := "github.com/hajimehoshi/ebiten/example/" + name
|
target := "github.com/hajimehoshi/ebiten/example/" + name
|
||||||
out, err := exec.Command("gopherjs", "build", "-o", out, target).CombinedOutput()
|
out, err := exec.Command("gopherjs", "build", "-o", out, target).CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
29
internal/audio/audio.go
Normal file
29
internal/audio/audio.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// +build !js
|
||||||
|
|
||||||
|
package audio
|
||||||
|
|
||||||
|
func Init() {
|
||||||
|
// TODO: Implement
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start() {
|
||||||
|
// TODO: Implement
|
||||||
|
}
|
||||||
|
|
||||||
|
func Append(l []float32, r []float32) {
|
||||||
|
// TODO: Implement
|
||||||
|
}
|
78
internal/audio/audio_js.go
Normal file
78
internal/audio/audio_js.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// +build js
|
||||||
|
|
||||||
|
package audio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gopherjs/gopherjs/js"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Keep this so as not to be destroyed by GC.
|
||||||
|
var node js.Object
|
||||||
|
var context js.Object
|
||||||
|
|
||||||
|
const bufferSize = 1024
|
||||||
|
|
||||||
|
var (
|
||||||
|
bufferL = make([]float32, 0)
|
||||||
|
bufferR = make([]float32, 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
func min(a, b int) int {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init() {
|
||||||
|
context = js.Global.Get("AudioContext").New()
|
||||||
|
// TODO: ScriptProcessorNode will be replaced Audio WebWorker.
|
||||||
|
// https://developer.mozilla.org/ja/docs/Web/API/ScriptProcessorNode
|
||||||
|
const bufLen = 1024
|
||||||
|
node = context.Call("createScriptProcessor", bufLen, 0, 2)
|
||||||
|
node.Call("addEventListener", "audioprocess", func(e js.Object) {
|
||||||
|
l := e.Get("outputBuffer").Call("getChannelData", 0)
|
||||||
|
r := e.Get("outputBuffer").Call("getChannelData", 1)
|
||||||
|
for i := 0; i < bufLen; i++ {
|
||||||
|
// TODO: Use copyFromChannel?
|
||||||
|
if len(bufferL) <= i {
|
||||||
|
l.SetIndex(i, 0)
|
||||||
|
r.SetIndex(i, 0)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
l.SetIndex(i, bufferL[i])
|
||||||
|
r.SetIndex(i, bufferR[i])
|
||||||
|
}
|
||||||
|
// TODO: Will the array heads be released properly on GopherJS?
|
||||||
|
usedLen := min(bufLen, len(bufferL))
|
||||||
|
bufferL = bufferL[usedLen:]
|
||||||
|
bufferR = bufferR[usedLen:]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start() {
|
||||||
|
// TODO: For iOS, node should be connected with a buffer node.
|
||||||
|
node.Call("connect", context.Get("destination"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Append(l []float32, r []float32) {
|
||||||
|
if len(l) != len(r) {
|
||||||
|
panic("len(l) must equal to len(r)")
|
||||||
|
}
|
||||||
|
bufferL = append(bufferL, l...)
|
||||||
|
bufferR = append(bufferR, r...)
|
||||||
|
}
|
@ -19,6 +19,7 @@ package ui
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
glfw "github.com/go-gl/glfw3"
|
glfw "github.com/go-gl/glfw3"
|
||||||
|
"github.com/hajimehoshi/ebiten/internal/audio"
|
||||||
"github.com/hajimehoshi/ebiten/internal/opengl"
|
"github.com/hajimehoshi/ebiten/internal/opengl"
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
@ -81,6 +82,9 @@ func init() {
|
|||||||
f()
|
f()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
audio.Init()
|
||||||
|
|
||||||
current = u
|
current = u
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,6 +138,8 @@ func Start(width, height, scale int, title string) (actualScale int, err error)
|
|||||||
windowWidth, _ := window.GetFramebufferSize()
|
windowWidth, _ := window.GetFramebufferSize()
|
||||||
actualScale = windowWidth / width
|
actualScale = windowWidth / width
|
||||||
|
|
||||||
|
audio.Start()
|
||||||
|
|
||||||
return actualScale, nil
|
return actualScale, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ package ui
|
|||||||
import (
|
import (
|
||||||
"github.com/gopherjs/gopherjs/js"
|
"github.com/gopherjs/gopherjs/js"
|
||||||
"github.com/gopherjs/webgl"
|
"github.com/gopherjs/webgl"
|
||||||
|
"github.com/hajimehoshi/ebiten/internal/audio"
|
||||||
"github.com/hajimehoshi/ebiten/internal/opengl"
|
"github.com/hajimehoshi/ebiten/internal/opengl"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
@ -26,6 +27,8 @@ import (
|
|||||||
var canvas js.Object
|
var canvas js.Object
|
||||||
var context *opengl.Context
|
var context *opengl.Context
|
||||||
|
|
||||||
|
// TODO: This returns true even when the browser is not active.
|
||||||
|
// The current behavior causes sound noise...
|
||||||
func shown() bool {
|
func shown() bool {
|
||||||
return !js.Global.Get("document").Get("hidden").Bool()
|
return !js.Global.Get("document").Get("hidden").Bool()
|
||||||
}
|
}
|
||||||
@ -36,6 +39,8 @@ func Use(f func(*opengl.Context)) {
|
|||||||
|
|
||||||
func vsync() {
|
func vsync() {
|
||||||
ch := make(chan struct{})
|
ch := make(chan struct{})
|
||||||
|
// TODO: In iOS8, this is called at every 1/30[sec] frame.
|
||||||
|
// Can we use DOMHighResTimeStamp?
|
||||||
js.Global.Get("window").Call("requestAnimationFrame", func() {
|
js.Global.Get("window").Call("requestAnimationFrame", func() {
|
||||||
close(ch)
|
close(ch)
|
||||||
})
|
})
|
||||||
@ -149,6 +154,8 @@ func init() {
|
|||||||
canvas.Call("addEventListener", "contextmenu", func(e js.Object) bool {
|
canvas.Call("addEventListener", "contextmenu", func(e js.Object) bool {
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
audio.Init()
|
||||||
}
|
}
|
||||||
|
|
||||||
func devicePixelRatio() int {
|
func devicePixelRatio() int {
|
||||||
@ -185,5 +192,7 @@ func Start(width, height, scale int, title string) (actualScale int, err error)
|
|||||||
})
|
})
|
||||||
canvas.Call("focus")
|
canvas.Call("focus")
|
||||||
|
|
||||||
|
audio.Start()
|
||||||
|
|
||||||
return actualScale, nil
|
return actualScale, nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user