main.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. // Copyright 2015 Hajime Hoshi
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package main
  15. import (
  16. "bytes"
  17. "fmt"
  18. "image/color"
  19. "log"
  20. "math"
  21. "github.com/hajimehoshi/ebiten/v2"
  22. "github.com/hajimehoshi/ebiten/v2/audio"
  23. "github.com/hajimehoshi/ebiten/v2/ebitenutil"
  24. "github.com/hajimehoshi/ebiten/v2/examples/resources/fonts"
  25. "github.com/hajimehoshi/ebiten/v2/inpututil"
  26. "github.com/hajimehoshi/ebiten/v2/text/v2"
  27. "github.com/hajimehoshi/ebiten/v2/vector"
  28. )
  29. const (
  30. arcadeFontSize = 8
  31. )
  32. var (
  33. arcadeFaceSource *text.GoTextFaceSource
  34. )
  35. func init() {
  36. s, err := text.NewGoTextFaceSource(bytes.NewReader(fonts.PressStart2P_ttf))
  37. if err != nil {
  38. log.Fatal(err)
  39. }
  40. arcadeFaceSource = s
  41. }
  42. const (
  43. screenWidth = 320
  44. screenHeight = 240
  45. sampleRate = 48000
  46. baseFreq = 220
  47. )
  48. // pianoAt returns an i-th sample of piano with the given frequency.
  49. func pianoAt(i int, freq float32) float32 {
  50. // Create piano-like waves with multiple sin waves.
  51. amp := []float32{1.0, 0.8, 0.6, 0.4, 0.2}
  52. x := []float32{4.0, 2.0, 1.0, 0.5, 0.25}
  53. var v float32
  54. for j := 0; j < len(amp); j++ {
  55. // Decay
  56. a := amp[j] * float32(math.Exp(float64(-5*float32(i)*freq/baseFreq/(x[j]*sampleRate))))
  57. v += a * float32(math.Sin(2.0*math.Pi*float64(i)*float64(freq)*float64(j+1)/sampleRate))
  58. }
  59. return v / 5.0
  60. }
  61. // toBytes returns the 2ch little endian 16bit byte sequence with the given left/right sequence.
  62. func toBytes(l, r []float32) []byte {
  63. if len(l) != len(r) {
  64. panic("len(l) must equal to len(r)")
  65. }
  66. b := make([]byte, len(l)*8)
  67. for i := range l {
  68. lv := math.Float32bits(l[i])
  69. rv := math.Float32bits(r[i])
  70. b[8*i] = byte(lv)
  71. b[8*i+1] = byte(lv >> 8)
  72. b[8*i+2] = byte(lv >> 16)
  73. b[8*i+3] = byte(lv >> 24)
  74. b[8*i+4] = byte(rv)
  75. b[8*i+5] = byte(rv >> 8)
  76. b[8*i+6] = byte(rv >> 16)
  77. b[8*i+7] = byte(rv >> 24)
  78. }
  79. return b
  80. }
  81. var (
  82. pianoNoteSamples = map[int][]byte{}
  83. pianoNoteSamplesInited = false
  84. pianoNoteSamplesInitCh = make(chan struct{})
  85. )
  86. func init() {
  87. // Initialize piano data.
  88. // This takes a little long time (especially on browsers),
  89. // so run this asynchronously and notice the progress.
  90. go func() {
  91. // Create a reference data and use this for other frequency.
  92. const refFreq = 110
  93. length := 4 * sampleRate * baseFreq / refFreq
  94. refData := make([]float32, length)
  95. for i := 0; i < length; i++ {
  96. refData[i] = pianoAt(i, refFreq)
  97. }
  98. for i := range keys {
  99. freq := baseFreq * math.Exp2(float64(i-1)/12.0)
  100. // Calculate the wave data for the freq.
  101. length := 4 * sampleRate * baseFreq / int(freq)
  102. l := make([]float32, length)
  103. r := make([]float32, length)
  104. for i := 0; i < length; i++ {
  105. idx := int(float64(i) * freq / refFreq)
  106. if len(refData) <= idx {
  107. break
  108. }
  109. l[i] = refData[idx]
  110. }
  111. copy(r, l)
  112. n := toBytes(l, r)
  113. pianoNoteSamples[int(freq)] = n
  114. }
  115. close(pianoNoteSamplesInitCh)
  116. }()
  117. }
  118. var (
  119. pianoImage = ebiten.NewImage(screenWidth, screenHeight)
  120. )
  121. func init() {
  122. const (
  123. keyWidth = 24
  124. y = 48
  125. )
  126. whiteKeys := []string{"A", "S", "D", "F", "G", "H", "J", "K", "L"}
  127. for i, k := range whiteKeys {
  128. x := i*keyWidth + 36
  129. height := 112
  130. vector.DrawFilledRect(pianoImage, float32(x), float32(y), float32(keyWidth-1), float32(height), color.White, false)
  131. op := &text.DrawOptions{}
  132. op.GeoM.Translate(float64(x+keyWidth/2), float64(y+height-12))
  133. op.ColorScale.ScaleWithColor(color.Black)
  134. op.PrimaryAlign = text.AlignCenter
  135. text.Draw(pianoImage, k, &text.GoTextFace{
  136. Source: arcadeFaceSource,
  137. Size: arcadeFontSize,
  138. }, op)
  139. }
  140. blackKeys := []string{"Q", "W", "", "R", "T", "", "U", "I", "O"}
  141. for i, k := range blackKeys {
  142. if k == "" {
  143. continue
  144. }
  145. x := i*keyWidth + 24
  146. height := 64
  147. vector.DrawFilledRect(pianoImage, float32(x), float32(y), float32(keyWidth-1), float32(height), color.Black, false)
  148. op := &text.DrawOptions{}
  149. op.GeoM.Translate(float64(x+keyWidth/2), float64(y+height-12))
  150. op.ColorScale.ScaleWithColor(color.White)
  151. op.PrimaryAlign = text.AlignCenter
  152. text.Draw(pianoImage, k, &text.GoTextFace{
  153. Source: arcadeFaceSource,
  154. Size: arcadeFontSize,
  155. }, op)
  156. }
  157. }
  158. var (
  159. keys = []ebiten.Key{
  160. ebiten.KeyQ,
  161. ebiten.KeyA,
  162. ebiten.KeyW,
  163. ebiten.KeyS,
  164. ebiten.KeyD,
  165. ebiten.KeyR,
  166. ebiten.KeyF,
  167. ebiten.KeyT,
  168. ebiten.KeyG,
  169. ebiten.KeyH,
  170. ebiten.KeyU,
  171. ebiten.KeyJ,
  172. ebiten.KeyI,
  173. ebiten.KeyK,
  174. ebiten.KeyO,
  175. ebiten.KeyL,
  176. }
  177. )
  178. type Game struct {
  179. audioContext *audio.Context
  180. }
  181. func NewGame() *Game {
  182. return &Game{
  183. audioContext: audio.NewContext(sampleRate),
  184. }
  185. }
  186. func (g *Game) Update() error {
  187. // The piano data is still being initialized.
  188. // Get the progress if available.
  189. if !pianoNoteSamplesInited {
  190. select {
  191. case <-pianoNoteSamplesInitCh:
  192. pianoNoteSamplesInited = true
  193. default:
  194. }
  195. }
  196. if pianoNoteSamplesInited {
  197. for i, key := range keys {
  198. if !inpututil.IsKeyJustPressed(key) {
  199. continue
  200. }
  201. g.playNote(baseFreq * float32(math.Exp2(float64(i-1)/12.0)))
  202. }
  203. }
  204. return nil
  205. }
  206. // playNote plays piano sound with the given frequency.
  207. func (g *Game) playNote(freq float32) {
  208. f := int(freq)
  209. p := g.audioContext.NewPlayerF32FromBytes(pianoNoteSamples[f])
  210. p.Play()
  211. }
  212. func (g *Game) Draw(screen *ebiten.Image) {
  213. screen.Fill(color.RGBA{0x80, 0x80, 0xc0, 0xff})
  214. screen.DrawImage(pianoImage, nil)
  215. ebitenutil.DebugPrint(screen, fmt.Sprintf("TPS: %0.2f", ebiten.ActualTPS()))
  216. }
  217. func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
  218. return screenWidth, screenHeight
  219. }
  220. func main() {
  221. ebiten.SetWindowSize(screenWidth*2, screenHeight*2)
  222. ebiten.SetWindowTitle("Piano (Ebitengine Demo)")
  223. if err := ebiten.RunGame(NewGame()); err != nil {
  224. log.Fatal(err)
  225. }
  226. }