diff --git a/_docs/gen.go b/_docs/gen.go index b982fa9d7..b86d1cfc6 100644 --- a/_docs/gen.go +++ b/_docs/gen.go @@ -171,13 +171,18 @@ func versions() string { var examples = []example{ {Name: "alphablending"}, + {Name: "audio"}, + {Name: "font"}, + {Name: "hsv"}, {Name: "hue"}, {Name: "gamepad"}, {Name: "keyboard"}, + {Name: "masking"}, {Name: "mosaic"}, {Name: "noise"}, {Name: "paint"}, {Name: "perspective"}, + {Name: "piano"}, {Name: "rotate"}, {Name: "sprites"}, {Name: "blocks"}, diff --git a/_docs/public/examples/audio.content.html b/_docs/public/examples/audio.content.html new file mode 100644 index 000000000..48a40c929 --- /dev/null +++ b/_docs/public/examples/audio.content.html @@ -0,0 +1,39 @@ + + + diff --git a/_docs/public/examples/audio.html b/_docs/public/examples/audio.html new file mode 100644 index 000000000..d84654a94 --- /dev/null +++ b/_docs/public/examples/audio.html @@ -0,0 +1,326 @@ + + + + +Ebiten example - audio + + + +

Ebiten example - audio

+ +
package main
+
+import (
+    "bytes"
+    "fmt"
+    "image/color"
+    "log"
+    "time"
+
+    "github.com/hajimehoshi/ebiten"
+    "github.com/hajimehoshi/ebiten/ebitenutil"
+    "github.com/hajimehoshi/ebiten/exp/audio"
+    "github.com/hajimehoshi/ebiten/exp/audio/vorbis"
+    "github.com/hajimehoshi/ebiten/exp/audio/wav"
+)
+
+const (
+    screenWidth  = 320
+    screenHeight = 240
+)
+
+var (
+    playerBarImage     *ebiten.Image
+    playerCurrentImage *ebiten.Image
+)
+
+func init() {
+    var err error
+    playerBarImage, err = ebiten.NewImage(300, 4, ebiten.FilterNearest)
+    if err != nil {
+        log.Fatal(err)
+    }
+    playerBarImage.Fill(&color.RGBA{0x80, 0x80, 0x80, 0xff})
+
+    playerCurrentImage, err = ebiten.NewImage(4, 10, ebiten.FilterNearest)
+    if err != nil {
+        log.Fatal(err)
+    }
+    playerCurrentImage.Fill(&color.RGBA{0xff, 0xff, 0xff, 0xff})
+}
+
+type Player struct {
+    audioPlayer *audio.Player
+    total       time.Duration
+}
+
+var (
+    audioContext     *audio.Context
+    musicPlayer      *Player
+    seStream         *wav.Stream
+    sePlayer         *audio.Player
+    musicCh          = make(chan *Player)
+    seCh             = make(chan *wav.Stream)
+    mouseButtonState = map[ebiten.MouseButton]int{}
+    keyState         = map[ebiten.Key]int{}
+    volume128        = 128
+)
+
+func playerBarRect() (x, y, w, h int) {
+    w, h = playerBarImage.Size()
+    x = (screenWidth - w) / 2
+    y = screenHeight - h - 16
+    return
+}
+
+type SEStream struct {
+    *bytes.Reader
+}
+
+func (s *SEStream) Close() error {
+    return nil
+}
+
+func (p *Player) updateSE() error {
+    if seStream == nil {
+        return nil
+    }
+    if !ebiten.IsKeyPressed(ebiten.KeyP) {
+        keyState[ebiten.KeyP] = 0
+        return nil
+    }
+    keyState[ebiten.KeyP]++
+    if keyState[ebiten.KeyP] != 1 {
+        return nil
+    }
+    if sePlayer == nil {
+        var err error
+        sePlayer, err = audioContext.NewPlayer(seStream)
+        if err != nil {
+            return err
+        }
+    }
+    if sePlayer.IsPlaying() {
+        return nil
+    }
+    if err := sePlayer.Rewind(); err != nil {
+        return err
+    }
+    return sePlayer.Play()
+}
+
+func (p *Player) updateVolume() error {
+    if p.audioPlayer == nil {
+        return nil
+    }
+    if ebiten.IsKeyPressed(ebiten.KeyZ) {
+        volume128--
+    }
+    if ebiten.IsKeyPressed(ebiten.KeyX) {
+        volume128++
+    }
+    if volume128 < 0 {
+        volume128 = 0
+    }
+    if 128 < volume128 {
+        volume128 = 128
+    }
+    p.audioPlayer.SetVolume(float64(volume128) / 128)
+    return nil
+}
+
+func (p *Player) updatePlayPause() error {
+    if p.audioPlayer == nil {
+        return nil
+    }
+    if !ebiten.IsKeyPressed(ebiten.KeyS) {
+        keyState[ebiten.KeyS] = 0
+        return nil
+    }
+    keyState[ebiten.KeyS]++
+    if keyState[ebiten.KeyS] != 1 {
+        return nil
+    }
+    if p.audioPlayer.IsPlaying() {
+        return p.audioPlayer.Pause()
+    }
+    return p.audioPlayer.Play()
+}
+
+func (p *Player) updateBar() error {
+    if p.audioPlayer == nil {
+        return nil
+    }
+    if !ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
+        mouseButtonState[ebiten.MouseButtonLeft] = 0
+        return nil
+    }
+    mouseButtonState[ebiten.MouseButtonLeft]++
+    if mouseButtonState[ebiten.MouseButtonLeft] != 1 {
+        return nil
+    }
+    x, y := ebiten.CursorPosition()
+    bx, by, bw, bh := playerBarRect()
+    if y < by || by+bh <= y {
+        return nil
+    }
+    if x < bx || bx+bw <= x {
+        return nil
+    }
+    pos := time.Duration(x-bx) * p.total / time.Duration(bw)
+    return p.audioPlayer.Seek(pos)
+}
+
+func (p *Player) close() error {
+    return p.audioPlayer.Close()
+}
+
+func update(screen *ebiten.Image) error {
+    if err := audioContext.Update(); err != nil {
+        return err
+    }
+    if musicPlayer == nil {
+        select {
+        case musicPlayer = <-musicCh:
+        default:
+        }
+    }
+    if seStream == nil {
+        select {
+        case seStream = <-seCh:
+        default:
+        }
+    }
+    if musicPlayer != nil {
+        if err := musicPlayer.updateBar(); err != nil {
+            return err
+        }
+        if err := musicPlayer.updatePlayPause(); err != nil {
+            return err
+        }
+        if err := musicPlayer.updateSE(); err != nil {
+            return err
+        }
+        if err := musicPlayer.updateVolume(); err != nil {
+            return err
+        }
+    }
+
+    op := &ebiten.DrawImageOptions{}
+    x, y, w, h := playerBarRect()
+    op.GeoM.Translate(float64(x), float64(y))
+    screen.DrawImage(playerBarImage, op)
+    currentTimeStr := "00:00"
+    if musicPlayer != nil {
+        c := musicPlayer.audioPlayer.Current()
+
+        // Current Time
+        m := (c / time.Minute) % 100
+        s := (c / time.Second) % 60
+        currentTimeStr = fmt.Sprintf("%02d:%02d", m, s)
+
+        // Bar
+        cw, ch := playerCurrentImage.Size()
+        cx := int(time.Duration(w)*c/musicPlayer.total) + x - cw/2
+        cy := y - (ch-h)/2
+        op := &ebiten.DrawImageOptions{}
+        op.GeoM.Translate(float64(cx), float64(cy))
+        screen.DrawImage(playerCurrentImage, op)
+    }
+
+    msg := fmt.Sprintf(`FPS: %0.2f
+Press S to toggle Play/Pause
+Press P to play SE
+Press Z or X to change volume of the music
+%s`, ebiten.CurrentFPS(), currentTimeStr)
+    if musicPlayer == nil {
+        msg += "\nNow Loading..."
+    }
+    ebitenutil.DebugPrint(screen, msg)
+    return nil
+}
+
+func main() {
+    wavF, err := ebitenutil.OpenFile("_resources/audio/jab.wav")
+    if err != nil {
+        log.Fatal(err)
+    }
+    oggF, err := ebitenutil.OpenFile("_resources/audio/ragtime.ogg")
+    if err != nil {
+        log.Fatal(err)
+    }
+    const sampleRate = 22050
+    const bytesPerSample = 4 // TODO: This should be defined in audio package
+    audioContext, err = audio.NewContext(sampleRate)
+    if err != nil {
+        log.Fatal(err)
+    }
+    go func() {
+        s, err := wav.Decode(audioContext, wavF)
+        if err != nil {
+            log.Fatal(err)
+            return
+        }
+        seCh <- s
+        close(seCh)
+    }()
+    go func() {
+        s, err := vorbis.Decode(audioContext, oggF)
+        if err != nil {
+            log.Fatal(err)
+            return
+        }
+        p, err := audioContext.NewPlayer(s)
+        if err != nil {
+            log.Fatal(err)
+            return
+        }
+        musicCh <- &Player{
+            audioPlayer: p,
+            total:       time.Second * time.Duration(s.Size()) / bytesPerSample / sampleRate,
+        }
+        close(musicCh)
+        // TODO: Is this goroutine-safe?
+        p.Play()
+    }()
+    if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Audio (Ebiten Demo)"); err != nil {
+        log.Fatal(err)
+    }
+    if musicPlayer != nil {
+        if err := musicPlayer.close(); err != nil {
+            log.Fatal(err)
+        }
+    }
+}
+
+ + + diff --git a/_docs/public/examples/font.content.html b/_docs/public/examples/font.content.html new file mode 100644 index 000000000..a62e2236c --- /dev/null +++ b/_docs/public/examples/font.content.html @@ -0,0 +1,39 @@ + + + diff --git a/_docs/public/examples/font.html b/_docs/public/examples/font.html new file mode 100644 index 000000000..972506d60 --- /dev/null +++ b/_docs/public/examples/font.html @@ -0,0 +1,134 @@ + + + + +Ebiten example - font + + + +

Ebiten example - font

+ +
package main
+
+import (
+    "image"
+    "io/ioutil"
+    "log"
+
+    "github.com/golang/freetype/truetype"
+    "github.com/hajimehoshi/ebiten"
+    "github.com/hajimehoshi/ebiten/ebitenutil"
+    "golang.org/x/image/font"
+    "golang.org/x/image/math/fixed"
+)
+
+const (
+    screenWidth  = 640
+    screenHeight = 480
+)
+
+var (
+    textImage *ebiten.Image
+)
+
+var text = []string{
+    "The quick brown fox jumps over the lazy dog.",
+    "",
+    // A head part of a Japanese novel 山月記 (Sangetsuki)
+    // See http://www.aozora.gr.jp/cards/000119/files/624_14544.html.
+    "隴西の李徴は博学才穎、天宝の末年、",
+    "若くして名を虎榜に連ね、",
+    "ついで江南尉に補せられたが、",
+    "性、狷介、自ら恃むところ頗厚く、",
+    "賤吏に甘んずるを潔しとしなかった。",
+}
+
+func parseFont() error {
+    f, err := ebitenutil.OpenFile("_resources/fonts/mplus-1p-regular.ttf")
+    if err != nil {
+        return err
+    }
+    defer f.Close()
+    b, err := ioutil.ReadAll(f)
+    if err != nil {
+        return err
+    }
+    tt, err := truetype.Parse(b)
+    if err != nil {
+        return err
+    }
+    w, h := textImage.Size()
+    dst := image.NewRGBA(image.Rect(0, 0, w, h))
+    const size = 24
+    const dpi = 72
+    d := &font.Drawer{
+        Dst: dst,
+        Src: image.White,
+        Face: truetype.NewFace(tt, &truetype.Options{
+            Size:    size,
+            DPI:     dpi,
+            Hinting: font.HintingFull,
+        }),
+    }
+    dy := size * dpi / 72
+    y := dy
+    for _, s := range text {
+        d.Dot = fixed.P(0, y)
+        d.DrawString(s)
+        y += dy
+    }
+    return textImage.ReplacePixels(dst.Pix)
+}
+
+func update(screen *ebiten.Image) error {
+    if err := screen.DrawImage(textImage, &ebiten.DrawImageOptions{}); err != nil {
+        return err
+    }
+    return nil
+}
+
+func main() {
+    var err error
+    textImage, err = ebiten.NewImage(screenWidth, screenHeight, ebiten.FilterNearest)
+    if err != nil {
+        log.Fatal(err)
+    }
+    if err := parseFont(); err != nil {
+        log.Fatal(err)
+    }
+    if err := ebiten.Run(update, screenWidth, screenHeight, 1, "Font (Ebiten Demo)"); err != nil {
+        log.Fatal(err)
+    }
+}
+
+ + + diff --git a/_docs/public/examples/hsv.content.html b/_docs/public/examples/hsv.content.html new file mode 100644 index 000000000..31af5cbbd --- /dev/null +++ b/_docs/public/examples/hsv.content.html @@ -0,0 +1,39 @@ + + + diff --git a/_docs/public/examples/hsv.html b/_docs/public/examples/hsv.html new file mode 100644 index 000000000..9635ab0a6 --- /dev/null +++ b/_docs/public/examples/hsv.html @@ -0,0 +1,132 @@ + + + + +Ebiten example - hsv + + + +

Ebiten example - hsv

+ +
package main
+
+import (
+    "fmt"
+    _ "image/jpeg"
+    "log"
+    "math"
+
+    "github.com/hajimehoshi/ebiten"
+    "github.com/hajimehoshi/ebiten/ebitenutil"
+)
+
+const (
+    screenWidth  = 320
+    screenHeight = 240
+)
+
+var (
+    hueInt        = 0
+    saturationInt = 128
+    valueInt      = 128
+    gophersImage  *ebiten.Image
+)
+
+func clamp(v, min, max int) int {
+    if min > max {
+        panic("min must <= max")
+    }
+    if v < min {
+        return min
+    }
+    if max < v {
+        return max
+    }
+    return v
+}
+
+func update(screen *ebiten.Image) error {
+    if ebiten.IsKeyPressed(ebiten.KeyQ) {
+        hueInt--
+    }
+    if ebiten.IsKeyPressed(ebiten.KeyW) {
+        hueInt++
+    }
+    if ebiten.IsKeyPressed(ebiten.KeyA) {
+        saturationInt--
+    }
+    if ebiten.IsKeyPressed(ebiten.KeyS) {
+        saturationInt++
+    }
+    if ebiten.IsKeyPressed(ebiten.KeyZ) {
+        valueInt--
+    }
+    if ebiten.IsKeyPressed(ebiten.KeyX) {
+        valueInt++
+    }
+    hueInt = clamp(hueInt, -256, 256)
+    saturationInt = clamp(saturationInt, 0, 256)
+    valueInt = clamp(valueInt, 0, 256)
+
+    w, h := gophersImage.Size()
+    op := &ebiten.DrawImageOptions{}
+    op.GeoM.Translate(float64(screenWidth-w)/2, float64(screenHeight-h)/2)
+    hue := float64(hueInt) * 2 * math.Pi / 128
+    saturation := float64(saturationInt) / 128
+    value := float64(valueInt) / 128
+    op.ColorM.ChangeHSV(hue, saturation, value)
+    if err := screen.DrawImage(gophersImage, op); err != nil {
+        return err
+    }
+
+    msg := fmt.Sprintf(`Hue:        %0.2f [Q][W]
+Saturation: %0.2f [A][S]
+Value:      %0.2f [Z][X]`, hue, saturation, value)
+    if err := ebitenutil.DebugPrint(screen, msg); err != nil {
+        return err
+    }
+    return nil
+}
+
+func main() {
+    var err error
+    gophersImage, _, err = ebitenutil.NewImageFromFile("_resources/images/gophers.jpg", ebiten.FilterNearest)
+    if err != nil {
+        log.Fatal(err)
+    }
+    if err := ebiten.Run(update, screenWidth, screenHeight, 2, "HSV (Ebiten Demo)"); err != nil {
+        log.Fatal(err)
+    }
+}
+
+ + + diff --git a/_docs/public/examples/masking.content.html b/_docs/public/examples/masking.content.html new file mode 100644 index 000000000..542cc846a --- /dev/null +++ b/_docs/public/examples/masking.content.html @@ -0,0 +1,39 @@ + + + diff --git a/_docs/public/examples/masking.html b/_docs/public/examples/masking.html new file mode 100644 index 000000000..b49d4644e --- /dev/null +++ b/_docs/public/examples/masking.html @@ -0,0 +1,169 @@ + + + + +Ebiten example - masking + + + +

Ebiten example - masking

+ +
package main
+
+import (
+    "image"
+    "image/color"
+    _ "image/jpeg"
+    "log"
+    "math"
+
+    "github.com/hajimehoshi/ebiten"
+    "github.com/hajimehoshi/ebiten/ebitenutil"
+)
+
+const (
+    screenWidth  = 320
+    screenHeight = 240
+)
+
+var (
+    gophersImage   *ebiten.Image
+    fiveyearsImage *ebiten.Image
+    maskImage      *ebiten.Image
+    spotLightImage *ebiten.Image
+    spotLightX     = 0
+    spotLightY     = 0
+    spotLightVX    = 1
+    spotLightVY    = 1
+)
+
+func update(screen *ebiten.Image) error {
+    spotLightX += spotLightVX
+    spotLightY += spotLightVY
+    if spotLightX < 0 {
+        spotLightX = -spotLightX
+        spotLightVX = -spotLightVX
+    }
+    if spotLightY < 0 {
+        spotLightY = -spotLightY
+        spotLightVY = -spotLightVY
+    }
+    w, h := spotLightImage.Size()
+    maxX, maxY := screenWidth-w, screenHeight-h
+    if maxX < spotLightX {
+        spotLightX = -spotLightX + 2*maxX
+        spotLightVX = -spotLightVX
+    }
+    if maxY < spotLightY {
+        spotLightY = -spotLightY + 2*maxY
+        spotLightVY = -spotLightVY
+    }
+
+    if err := maskImage.Clear(); err != nil {
+        return err
+    }
+
+    op := &ebiten.DrawImageOptions{}
+    op.GeoM.Translate(float64(spotLightX), float64(spotLightY))
+    if err := maskImage.DrawImage(spotLightImage, op); err != nil {
+        return err
+    }
+
+    op = &ebiten.DrawImageOptions{}
+    op.CompositeMode = ebiten.CompositeModeSourceOut
+    if err := maskImage.DrawImage(fiveyearsImage, op); err != nil {
+        return err
+    }
+
+    if err := screen.Fill(color.RGBA{0x00, 0x00, 0x80, 0xff}); err != nil {
+        return err
+    }
+    if err := screen.DrawImage(gophersImage, &ebiten.DrawImageOptions{}); err != nil {
+        return err
+    }
+    if err := screen.DrawImage(maskImage, &ebiten.DrawImageOptions{}); err != nil {
+        return err
+    }
+
+    return nil
+}
+
+func max(a, b int) int {
+    if a < b {
+        return b
+    }
+    return a
+}
+
+func min(a, b int) int {
+    if a < b {
+        return a
+    }
+    return b
+}
+
+func main() {
+    var err error
+    gophersImage, _, err = ebitenutil.NewImageFromFile("_resources/images/gophers.jpg", ebiten.FilterNearest)
+    if err != nil {
+        log.Fatal(err)
+    }
+    fiveyearsImage, _, err = ebitenutil.NewImageFromFile("_resources/images/fiveyears.jpg", ebiten.FilterNearest)
+    if err != nil {
+        log.Fatal(err)
+    }
+    maskImage, err = ebiten.NewImage(screenWidth, screenHeight, ebiten.FilterNearest)
+    if err != nil {
+        log.Fatal(err)
+    }
+
+    as := image.Point{128, 128}
+    a := image.NewAlpha(image.Rectangle{image.ZP, as})
+    for j := 0; j < as.Y; j++ {
+        for i := 0; i < as.X; i++ {
+            r := as.X / 2
+            d := math.Sqrt(float64((i-r)*(i-r) + (j-r)*(j-r)))
+            b := uint8(max(0, min(0xff, 3*0xff-int(d*3*0xff)/r)))
+            a.SetAlpha(i, j, color.Alpha{b})
+        }
+    }
+    spotLightImage, err = ebiten.NewImageFromImage(a, ebiten.FilterNearest)
+    if err != nil {
+        log.Fatal(err)
+    }
+    if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Masking (Ebiten Demo)"); err != nil {
+        log.Fatal(err)
+    }
+}
+
+ + + diff --git a/_docs/public/examples/piano.content.html b/_docs/public/examples/piano.content.html new file mode 100644 index 000000000..1e7d154d8 --- /dev/null +++ b/_docs/public/examples/piano.content.html @@ -0,0 +1,39 @@ + + + diff --git a/_docs/public/examples/piano.html b/_docs/public/examples/piano.html new file mode 100644 index 000000000..7790cfaec --- /dev/null +++ b/_docs/public/examples/piano.html @@ -0,0 +1,261 @@ + + + + +Ebiten example - piano + + + +

Ebiten example - piano

+ +
package main
+
+import (
+    "bytes"
+    "fmt"
+    "image/color"
+    "log"
+    "math"
+
+    "github.com/hajimehoshi/ebiten"
+    "github.com/hajimehoshi/ebiten/ebitenutil"
+    "github.com/hajimehoshi/ebiten/examples/common"
+    "github.com/hajimehoshi/ebiten/exp/audio"
+)
+
+const (
+    screenWidth  = 320
+    screenHeight = 240
+    sampleRate   = 44100
+)
+
+var audioContext *audio.Context
+
+func init() {
+    var err error
+    audioContext, err = audio.NewContext(sampleRate)
+    if err != nil {
+        log.Fatal(err)
+    }
+}
+
+var pcm = make([]float64, 4*sampleRate)
+
+const baseFreq = 220
+
+func init() {
+    s := float64(sampleRate)
+    amp := []float64{1.0, 0.8, 0.6, 0.4, 0.2}
+    x := []float64{4.0, 2.0, 1.0, 0.5, 0.25}
+    for i := 0; i < len(pcm); i++ {
+        v := 0.0
+        twoPiF := 2.0 * math.Pi * baseFreq
+        for j := 0; j < len(amp); j++ {
+            a := amp[j] * math.Exp(-5*float64(i)/(x[j]*s))
+            v += a * math.Sin(float64(i)*twoPiF*float64(j+1)/s)
+        }
+        pcm[i] = v / 5.0
+    }
+}
+
+var (
+    noteCache = map[int][]byte{}
+)
+
+func toBytes(l, r []int16) []byte {
+    if len(l) != len(r) {
+        panic("len(l) must equal to len(r)")
+    }
+    b := make([]byte, len(l)*4)
+    for i, _ := range l {
+        b[4*i] = byte(l[i])
+        b[4*i+1] = byte(l[i] >> 8)
+        b[4*i+2] = byte(r[i])
+        b[4*i+3] = byte(r[i] >> 8)
+    }
+    return b
+}
+
+type stream struct {
+    *bytes.Reader
+}
+
+func (s *stream) Close() error {
+    s.Reader = nil
+    return nil
+}
+
+func addNote(freq float64, vol float64) error {
+    f := int(freq)
+    if n, ok := noteCache[f]; ok {
+        p, err := audioContext.NewPlayer(&stream{bytes.NewReader(n)})
+        if err != nil {
+            return err
+        }
+        p.Play()
+        return nil
+    }
+    length := len(pcm) * baseFreq / f
+    l := make([]int16, length)
+    r := make([]int16, length)
+    j := 0
+    jj := 0
+    for i := 0; i < len(l); i++ {
+        p := pcm[j]
+        l[i] = int16(p * vol * math.MaxInt16)
+        r[i] = l[i]
+        jj += f
+        j = jj / baseFreq
+    }
+    n := toBytes(l, r)
+    noteCache[f] = n
+    p, err := audioContext.NewPlayer(&stream{bytes.NewReader(n)})
+    if err != nil {
+        return err
+    }
+    p.Play()
+    return nil
+}
+
+var keys = []ebiten.Key{
+    ebiten.KeyQ,
+    ebiten.KeyA,
+    ebiten.KeyW,
+    ebiten.KeyS,
+    ebiten.KeyD,
+    ebiten.KeyR,
+    ebiten.KeyF,
+    ebiten.KeyT,
+    ebiten.KeyG,
+    ebiten.KeyH,
+    ebiten.KeyU,
+    ebiten.KeyJ,
+    ebiten.KeyI,
+    ebiten.KeyK,
+    ebiten.KeyO,
+    ebiten.KeyL,
+}
+
+var keyStates = map[ebiten.Key]int{}
+
+func init() {
+    for _, key := range keys {
+        keyStates[key] = 0
+    }
+}
+
+func updateInput() {
+    for _, key := range keys {
+        if !ebiten.IsKeyPressed(key) {
+            keyStates[key] = 0
+            continue
+        }
+        keyStates[key]++
+    }
+}
+
+var (
+    imagePiano *ebiten.Image
+)
+
+func init() {
+    var err error
+    imageEmpty, err := ebiten.NewImage(16, 16, ebiten.FilterNearest)
+    if err != nil {
+        panic(err)
+    }
+    imageEmpty.Fill(color.White)
+    imagePiano, err = ebiten.NewImage(screenWidth, screenHeight, ebiten.FilterNearest)
+    if err != nil {
+        panic(err)
+    }
+    whiteKeys := []string{"A", "S", "D", "F", "G", "H", "J", "K", "L"}
+    width := 24
+    y := 48
+    for i, k := range whiteKeys {
+        x := i*width + 36
+        height := 112
+        op := &ebiten.DrawImageOptions{}
+        w, h := imageEmpty.Size()
+        op.GeoM.Scale(float64(width-1)/float64(w), float64(height)/float64(h))
+        op.GeoM.Translate(float64(x), float64(y))
+        op.ColorM.Scale(1, 1, 1, 1)
+        imagePiano.DrawImage(imageEmpty, op)
+        common.ArcadeFont.DrawText(imagePiano, k, x+8, y+height-16, 1, color.Black)
+    }
+
+    blackKeys := []string{"Q", "W", "", "R", "T", "", "U", "I", "O"}
+    for i, k := range blackKeys {
+        if k == "" {
+            continue
+        }
+        x := i*width + 24
+        height := 64
+        op := &ebiten.DrawImageOptions{}
+        w, h := imageEmpty.Size()
+        op.GeoM.Scale(float64(width-1)/float64(w), float64(height)/float64(h))
+        op.GeoM.Translate(float64(x), float64(y))
+        op.ColorM.Scale(0, 0, 0, 1)
+        imagePiano.DrawImage(imageEmpty, op)
+        common.ArcadeFont.DrawText(imagePiano, k, x+8, y+height-16, 1, color.White)
+    }
+}
+
+func update(screen *ebiten.Image) error {
+    if err := audioContext.Update(); err != nil {
+        return err
+    }
+    updateInput()
+    for i, key := range keys {
+        if keyStates[key] != 1 {
+            continue
+        }
+        if err := addNote(220*math.Exp2(float64(i-1)/12.0), 1.0); err != nil {
+            return err
+        }
+    }
+
+    screen.Fill(color.RGBA{0x80, 0x80, 0xc0, 0xff})
+    screen.DrawImage(imagePiano, nil)
+
+    ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %0.2f", ebiten.CurrentFPS()))
+    return nil
+}
+
+func main() {
+    if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Piano (Ebiten Demo)"); err != nil {
+        log.Fatal(err)
+    }
+}
+
+ + + diff --git a/_docs/public/index.html b/_docs/public/index.html index b7d95fefd..c01df519a 100644 --- a/_docs/public/index.html +++ b/_docs/public/index.html @@ -63,7 +63,7 @@ pre {

Example

- Ebiten example: alphablendingEbiten example: hueEbiten example: gamepadEbiten example: keyboardEbiten example: mosaicEbiten example: noiseEbiten example: paintEbiten example: perspectiveEbiten example: rotateEbiten example: spritesEbiten example: blocks + Ebiten example: alphablendingEbiten example: audioEbiten example: fontEbiten example: hsvEbiten example: hueEbiten example: gamepadEbiten example: keyboardEbiten example: maskingEbiten example: mosaicEbiten example: noiseEbiten example: paintEbiten example: perspectiveEbiten example: pianoEbiten example: rotateEbiten example: spritesEbiten example: blocks

Install on Mac OS X or Windows