mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-24 18:58:54 +01:00
Merge branch 'audio'
This commit is contained in:
commit
8a5c6d132e
@ -31,7 +31,7 @@ window.addEventListener('load', function() {
|
||||
var s = document.createElement('script');
|
||||
var src = 'blocks.js';
|
||||
if (isProduction()) {
|
||||
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/master/' + src;
|
||||
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/audio/' + src;
|
||||
}
|
||||
s.src = src;
|
||||
document.body.appendChild(s);
|
||||
|
@ -31,7 +31,7 @@ window.addEventListener('load', function() {
|
||||
var s = document.createElement('script');
|
||||
var src = 'hue.js';
|
||||
if (isProduction()) {
|
||||
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/master/' + src;
|
||||
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/audio/' + src;
|
||||
}
|
||||
s.src = src;
|
||||
document.body.appendChild(s);
|
||||
|
@ -31,7 +31,7 @@ window.addEventListener('load', function() {
|
||||
var s = document.createElement('script');
|
||||
var src = 'keyboard.js';
|
||||
if (isProduction()) {
|
||||
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/master/' + src;
|
||||
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/audio/' + src;
|
||||
}
|
||||
s.src = src;
|
||||
document.body.appendChild(s);
|
||||
|
@ -31,7 +31,7 @@ window.addEventListener('load', function() {
|
||||
var s = document.createElement('script');
|
||||
var src = 'mosaic.js';
|
||||
if (isProduction()) {
|
||||
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/master/' + src;
|
||||
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/audio/' + src;
|
||||
}
|
||||
s.src = src;
|
||||
document.body.appendChild(s);
|
||||
|
@ -31,7 +31,7 @@ window.addEventListener('load', function() {
|
||||
var s = document.createElement('script');
|
||||
var src = 'paint.js';
|
||||
if (isProduction()) {
|
||||
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/master/' + src;
|
||||
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/audio/' + src;
|
||||
}
|
||||
s.src = src;
|
||||
document.body.appendChild(s);
|
||||
|
@ -31,7 +31,7 @@ window.addEventListener('load', function() {
|
||||
var s = document.createElement('script');
|
||||
var src = 'perspective.js';
|
||||
if (isProduction()) {
|
||||
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/master/' + src;
|
||||
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/audio/' + src;
|
||||
}
|
||||
s.src = src;
|
||||
document.body.appendChild(s);
|
||||
|
@ -31,7 +31,7 @@ window.addEventListener('load', function() {
|
||||
var s = document.createElement('script');
|
||||
var src = 'rotate.js';
|
||||
if (isProduction()) {
|
||||
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/master/' + src;
|
||||
src = 'http://hajimehoshi.github.io/ebiten.pagestorage/audio/' + src;
|
||||
}
|
||||
s.src = src;
|
||||
document.body.appendChild(s);
|
||||
|
@ -61,7 +61,7 @@ func createJSIfNeeded(name string) (string, error) {
|
||||
if err != nil && !os.IsNotExist(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
|
||||
out, err := exec.Command("gopherjs", "build", "-o", out, target).CombinedOutput()
|
||||
if err != nil {
|
||||
|
117
example/audio/main.go
Normal file
117
example/audio/main.go
Normal file
@ -0,0 +1,117 @@
|
||||
// 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"
|
||||
"github.com/hajimehoshi/ebiten/exp/audio"
|
||||
"log"
|
||||
)
|
||||
|
||||
const (
|
||||
screenWidth = 320
|
||||
screenHeight = 240
|
||||
)
|
||||
|
||||
var frames = 0
|
||||
|
||||
const (
|
||||
freqA = 440.0
|
||||
freqAS = 466.2
|
||||
freqB = 493.9
|
||||
freqC = 523.3
|
||||
freqCS = 554.4
|
||||
freqD = 587.3
|
||||
freqDS = 622.3
|
||||
freqE = 659.3
|
||||
freqF = 698.5
|
||||
freqFS = 740.0
|
||||
freqG = 784.0
|
||||
freqGS = 830.6
|
||||
)
|
||||
|
||||
const score = `CCGGAAGR FFEEDDCR GGFFEEDR GGFFEEDR CCGGAAGR FFEEDDCR`
|
||||
|
||||
var scoreIndex = 0
|
||||
|
||||
func square(out []float32, volume float64, freq float64, sequence float64) {
|
||||
if freq == 0 {
|
||||
for i := 0; i < len(out); i++ {
|
||||
out[i] = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
length := int(float64(audio.SampleRate()) / freq)
|
||||
if length == 0 {
|
||||
panic("invalid freq")
|
||||
}
|
||||
for i := 0; i < len(out); i++ {
|
||||
a := float32(volume)
|
||||
if i%length < int(float64(length)*sequence) {
|
||||
a = 0
|
||||
}
|
||||
out[i] = a
|
||||
}
|
||||
}
|
||||
|
||||
func addNote() {
|
||||
size := audio.SampleRate() / 60
|
||||
notes := []float64{freqC, freqD, freqE, freqF, freqG, freqA * 2, freqB * 2}
|
||||
|
||||
defer func() {
|
||||
scoreIndex++
|
||||
scoreIndex %= len(score)
|
||||
}()
|
||||
l := make([]float32, size*30)
|
||||
r := make([]float32, size*30)
|
||||
note := score[scoreIndex]
|
||||
for note == ' ' {
|
||||
scoreIndex++
|
||||
scoreIndex %= len(score)
|
||||
note = score[scoreIndex]
|
||||
}
|
||||
freq := 0.0
|
||||
switch {
|
||||
case note == 'R':
|
||||
freq = 0
|
||||
case note <= 'B':
|
||||
freq = notes[int(note)+len(notes)-int('C')]
|
||||
default:
|
||||
freq = notes[note-'C']
|
||||
}
|
||||
vol := 1.0 / 16.0
|
||||
square(l, vol, freq, 0.25)
|
||||
square(r, vol, freq, 0.25)
|
||||
audio.AppendToBuffer(0, l, r)
|
||||
}
|
||||
|
||||
func update(screen *ebiten.Image) error {
|
||||
defer func() {
|
||||
frames++
|
||||
}()
|
||||
if frames%30 == 0 {
|
||||
addNote()
|
||||
}
|
||||
ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %0.2f", ebiten.CurrentFPS()))
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Rotate (Ebiten Demo)"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
32
exp/audio/audio.go
Normal file
32
exp/audio/audio.go
Normal file
@ -0,0 +1,32 @@
|
||||
// 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 audio
|
||||
|
||||
import (
|
||||
"github.com/hajimehoshi/ebiten/internal/audio"
|
||||
)
|
||||
|
||||
func SampleRate() int {
|
||||
return audio.SampleRate
|
||||
}
|
||||
|
||||
func AppendToBuffer(channel int, l []float32, r []float32) bool {
|
||||
return audio.Append(channel, l, r)
|
||||
}
|
||||
|
||||
// TODO: better name
|
||||
func CurrentTime() int {
|
||||
return audio.CurrentBytes()
|
||||
}
|
41
internal/audio/audio.go
Normal file
41
internal/audio/audio.go
Normal file
@ -0,0 +1,41 @@
|
||||
// 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
|
||||
|
||||
const SampleRate = 44100
|
||||
|
||||
func Init() {
|
||||
// TODO: Implement
|
||||
}
|
||||
|
||||
func Start() {
|
||||
// TODO: Implement
|
||||
}
|
||||
|
||||
func Append(channel int, l []float32, r []float32) bool {
|
||||
// TODO: Implement
|
||||
return false
|
||||
}
|
||||
|
||||
func CurrentBytes() int {
|
||||
// TODO: Implement
|
||||
return 0
|
||||
}
|
||||
|
||||
func Update() {
|
||||
// TODO: Implement
|
||||
}
|
142
internal/audio/audio_js.go
Normal file
142
internal/audio/audio_js.go
Normal file
@ -0,0 +1,142 @@
|
||||
// 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
|
||||
const SampleRate = 44100
|
||||
|
||||
var nextInsertion = 0
|
||||
|
||||
type channel struct {
|
||||
l []float32
|
||||
r []float32
|
||||
}
|
||||
|
||||
var channels = make([]*channel, 16)
|
||||
|
||||
func init() {
|
||||
for i, _ := range channels {
|
||||
channels[i] = &channel{
|
||||
l: []float32{},
|
||||
r: []float32{},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
var currentBytes = 0
|
||||
|
||||
func CurrentBytes() int {
|
||||
return currentBytes
|
||||
}
|
||||
|
||||
func Init() {
|
||||
context = js.Global.Get("AudioContext").New()
|
||||
// TODO: ScriptProcessorNode will be replaced with Audio WebWorker.
|
||||
// https://developer.mozilla.org/ja/docs/Web/API/ScriptProcessorNode
|
||||
node = context.Call("createScriptProcessor", bufferSize, 0, 2)
|
||||
node.Call("addEventListener", "audioprocess", func(e js.Object) {
|
||||
defer func() {
|
||||
currentBytes += bufferSize
|
||||
}()
|
||||
|
||||
l := e.Get("outputBuffer").Call("getChannelData", 0)
|
||||
r := e.Get("outputBuffer").Call("getChannelData", 1)
|
||||
inputL := make([]float32, bufferSize)
|
||||
inputR := make([]float32, bufferSize)
|
||||
for _, ch := range channels {
|
||||
if len(ch.l) == 0 {
|
||||
continue
|
||||
}
|
||||
l := min(len(ch.l), bufferSize)
|
||||
for i := 0; i < l; i++ {
|
||||
inputL[i] += ch.l[i]
|
||||
inputR[i] += ch.r[i]
|
||||
}
|
||||
// TODO: Use copyFromChannel?
|
||||
usedLen := min(bufferSize, len(ch.l))
|
||||
ch.l = ch.l[usedLen:]
|
||||
ch.r = ch.r[usedLen:]
|
||||
}
|
||||
nextInsertion -= min(bufferSize, nextInsertion)
|
||||
for i := 0; i < bufferSize; i++ {
|
||||
// TODO: Use copyFromChannel?
|
||||
if len(inputL) <= i {
|
||||
l.SetIndex(i, 0)
|
||||
r.SetIndex(i, 0)
|
||||
continue
|
||||
}
|
||||
l.SetIndex(i, inputL[i])
|
||||
r.SetIndex(i, inputR[i])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Update() {
|
||||
nextInsertion += SampleRate / 60
|
||||
}
|
||||
|
||||
func Start() {
|
||||
// TODO: For iOS, node should be connected with a buffer node.
|
||||
node.Call("connect", context.Get("destination"))
|
||||
}
|
||||
|
||||
func channelAt(i int) *channel {
|
||||
if i == -1 {
|
||||
for _, ch := range channels {
|
||||
if len(ch.l) <= nextInsertion {
|
||||
return ch
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
ch := channels[i]
|
||||
if len(ch.l) <= nextInsertion {
|
||||
return ch
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Append(i int, l []float32, r []float32) bool {
|
||||
// TODO: Mutex (especially for OpenAL)
|
||||
if len(l) != len(r) {
|
||||
panic("len(l) must equal to len(r)")
|
||||
}
|
||||
ch := channelAt(i)
|
||||
if ch == nil {
|
||||
return false
|
||||
}
|
||||
ch.l = append(ch.l, make([]float32, nextInsertion-len(ch.l))...)
|
||||
ch.r = append(ch.r, make([]float32, nextInsertion-len(ch.r))...)
|
||||
ch.l = append(ch.l, l...)
|
||||
ch.r = append(ch.r, r...)
|
||||
return true
|
||||
}
|
@ -19,6 +19,7 @@ package ui
|
||||
import (
|
||||
"fmt"
|
||||
glfw "github.com/go-gl/glfw3"
|
||||
"github.com/hajimehoshi/ebiten/internal/audio"
|
||||
"github.com/hajimehoshi/ebiten/internal/opengl"
|
||||
"runtime"
|
||||
"time"
|
||||
@ -81,6 +82,9 @@ func init() {
|
||||
f()
|
||||
}
|
||||
}()
|
||||
|
||||
audio.Init()
|
||||
|
||||
current = u
|
||||
}
|
||||
|
||||
@ -133,6 +137,8 @@ func Start(width, height, scale int, title string) (actualScale int, err error)
|
||||
windowWidth, _ := window.GetFramebufferSize()
|
||||
actualScale = windowWidth / width
|
||||
|
||||
audio.Start()
|
||||
|
||||
return actualScale, nil
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ package ui
|
||||
import (
|
||||
"github.com/gopherjs/gopherjs/js"
|
||||
"github.com/gopherjs/webgl"
|
||||
"github.com/hajimehoshi/ebiten/internal/audio"
|
||||
"github.com/hajimehoshi/ebiten/internal/opengl"
|
||||
"strconv"
|
||||
)
|
||||
@ -26,6 +27,8 @@ import (
|
||||
var canvas js.Object
|
||||
var context *opengl.Context
|
||||
|
||||
// TODO: This returns true even when the browser is not active.
|
||||
// The current behavior causes sound noise...
|
||||
func shown() bool {
|
||||
return !js.Global.Get("document").Get("hidden").Bool()
|
||||
}
|
||||
@ -36,6 +39,8 @@ func Use(f func(*opengl.Context)) {
|
||||
|
||||
func vsync() {
|
||||
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() {
|
||||
close(ch)
|
||||
})
|
||||
@ -178,6 +183,8 @@ func init() {
|
||||
window.Call("addEventListener", "gamepadconnected", func(e js.Object) {
|
||||
// Do nothing.
|
||||
})
|
||||
|
||||
audio.Init()
|
||||
}
|
||||
|
||||
func setMouseCursorFromEvent(e js.Object) {
|
||||
@ -217,5 +224,7 @@ func Start(width, height, scale int, title string) (actualScale int, err error)
|
||||
|
||||
canvas.Call("focus")
|
||||
|
||||
audio.Start()
|
||||
|
||||
return actualScale, nil
|
||||
}
|
||||
|
3
run.go
3
run.go
@ -15,6 +15,7 @@
|
||||
package ebiten
|
||||
|
||||
import (
|
||||
"github.com/hajimehoshi/ebiten/internal/audio"
|
||||
"github.com/hajimehoshi/ebiten/internal/opengl"
|
||||
"github.com/hajimehoshi/ebiten/internal/ui"
|
||||
"time"
|
||||
@ -69,6 +70,8 @@ func Run(f func(*Image) error, width, height, scale int, title string) error {
|
||||
if err := graphicsContext.postUpdate(); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: I'm not sure this is 'Update'. Is 'Tick' better?
|
||||
audio.Update()
|
||||
ui.SwapBuffers()
|
||||
if err != nil {
|
||||
return err
|
||||
|
Loading…
Reference in New Issue
Block a user