main.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  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. "log"
  17. "math"
  18. "strings"
  19. "github.com/hajimehoshi/ebiten/v2"
  20. "github.com/hajimehoshi/ebiten/v2/audio"
  21. "github.com/hajimehoshi/ebiten/v2/ebitenutil"
  22. )
  23. const (
  24. screenWidth = 640
  25. screenHeight = 480
  26. sampleRate = 48000
  27. )
  28. const (
  29. freqA = 440.0
  30. freqAS = 466.2
  31. freqB = 493.9
  32. freqC = 523.3
  33. freqCS = 554.4
  34. freqD = 587.3
  35. freqDS = 622.3
  36. freqE = 659.3
  37. freqF = 698.5
  38. freqFS = 740.0
  39. freqG = 784.0
  40. freqGS = 830.6
  41. )
  42. // Twinkle, Twinkle, Little Star
  43. var score = strings.Replace(
  44. `CCGGAAGR FFEEDDCR GGFFEEDR GGFFEEDR CCGGAAGR FFEEDDCR`,
  45. " ", "", -1)
  46. // square fills out with square wave values with the specified volume, frequency and sequence.
  47. func square(out []float32, volume float32, freq float32, sequence float32) {
  48. if freq == 0 {
  49. for i := 0; i < len(out); i++ {
  50. out[i] = 0
  51. }
  52. return
  53. }
  54. length := int(sampleRate / freq)
  55. if length == 0 {
  56. panic("invalid freq")
  57. }
  58. for i := 0; i < len(out); i++ {
  59. a := volume
  60. if i%length < int(float32(length)*sequence) {
  61. a = -a
  62. }
  63. out[i] = a
  64. }
  65. }
  66. // toBytes returns the 2ch little endian 16bit byte sequence with the given left/right sequence.
  67. func toBytes(l, r []float32) []byte {
  68. if len(l) != len(r) {
  69. panic("len(l) must equal to len(r)")
  70. }
  71. b := make([]byte, len(l)*8)
  72. for i := range l {
  73. lv := math.Float32bits(l[i])
  74. rv := math.Float32bits(r[i])
  75. b[8*i] = byte(lv)
  76. b[8*i+1] = byte(lv >> 8)
  77. b[8*i+2] = byte(lv >> 16)
  78. b[8*i+3] = byte(lv >> 24)
  79. b[8*i+4] = byte(rv)
  80. b[8*i+5] = byte(rv >> 8)
  81. b[8*i+6] = byte(rv >> 16)
  82. b[8*i+7] = byte(rv >> 24)
  83. }
  84. return b
  85. }
  86. type Game struct {
  87. scoreIndex int
  88. frames int
  89. currentNote rune
  90. audioContext *audio.Context
  91. }
  92. func NewGame() *Game {
  93. return &Game{
  94. audioContext: audio.NewContext(sampleRate),
  95. }
  96. }
  97. // playNote plays the note at scoreIndex of the score.
  98. func (g *Game) playNote(scoreIndex int) rune {
  99. note := score[scoreIndex]
  100. // If the note is 'rest', play nothing.
  101. if note == 'R' {
  102. return rune(note)
  103. }
  104. freqs := []float32{freqC, freqD, freqE, freqF, freqG, freqA * 2, freqB * 2}
  105. var freq float32
  106. switch {
  107. case 'A' <= note && note <= 'B':
  108. freq = freqs[int(note)+len(freqs)-int('C')]
  109. case 'C' <= note && note <= 'G':
  110. freq = freqs[note-'C']
  111. default:
  112. panic("note out of range")
  113. }
  114. const vol = 1.0 / 16.0
  115. size := (ebiten.TPS()/2 - 2) * sampleRate / ebiten.TPS()
  116. l := make([]float32, size)
  117. r := make([]float32, size)
  118. square(l, vol, freq, 0.25)
  119. square(r, vol, freq, 0.25)
  120. p := g.audioContext.NewPlayerF32FromBytes(toBytes(l, r))
  121. p.Play()
  122. return rune(note)
  123. }
  124. func (g *Game) Update() error {
  125. // Play notes for each half second.
  126. if g.frames%30 == 0 && g.audioContext.IsReady() {
  127. g.currentNote = g.playNote(g.scoreIndex)
  128. g.scoreIndex++
  129. g.scoreIndex %= len(score)
  130. }
  131. g.frames++
  132. return nil
  133. }
  134. func (g *Game) Draw(screen *ebiten.Image) {
  135. msg := "Note: "
  136. if g.currentNote == 'R' || g.currentNote == 0 {
  137. msg += "-"
  138. } else {
  139. msg += string(g.currentNote)
  140. }
  141. if !g.audioContext.IsReady() {
  142. msg += "\n\n(If the audio doesn't start,\n click the screen or press keys)"
  143. }
  144. ebitenutil.DebugPrint(screen, msg)
  145. }
  146. func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
  147. return screenWidth, screenHeight
  148. }
  149. func main() {
  150. ebiten.SetWindowSize(screenWidth, screenHeight)
  151. ebiten.SetWindowTitle("PCM (Ebitengine Demo)")
  152. if err := ebiten.RunGame(NewGame()); err != nil {
  153. log.Fatal(err)
  154. }
  155. }