main.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. // Copyright 2016 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. // This is an example to implement an audio player.
  15. // See examples/wav for a simpler example to play a sound file.
  16. package main
  17. import (
  18. "bytes"
  19. "fmt"
  20. "image"
  21. "image/color"
  22. _ "image/png"
  23. "io"
  24. "log"
  25. "time"
  26. "github.com/hajimehoshi/ebiten/v2"
  27. "github.com/hajimehoshi/ebiten/v2/audio"
  28. "github.com/hajimehoshi/ebiten/v2/audio/mp3"
  29. "github.com/hajimehoshi/ebiten/v2/audio/vorbis"
  30. "github.com/hajimehoshi/ebiten/v2/audio/wav"
  31. "github.com/hajimehoshi/ebiten/v2/ebitenutil"
  32. raudio "github.com/hajimehoshi/ebiten/v2/examples/resources/audio"
  33. riaudio "github.com/hajimehoshi/ebiten/v2/examples/resources/images/audio"
  34. "github.com/hajimehoshi/ebiten/v2/inpututil"
  35. "github.com/hajimehoshi/ebiten/v2/vector"
  36. )
  37. const (
  38. screenWidth = 640
  39. screenHeight = 480
  40. sampleRate = 48000
  41. )
  42. var (
  43. playerBarColor = color.RGBA{0x80, 0x80, 0x80, 0xff}
  44. playerCurrentColor = color.RGBA{0xff, 0xff, 0xff, 0xff}
  45. )
  46. var (
  47. playButtonImage *ebiten.Image
  48. pauseButtonImage *ebiten.Image
  49. alertButtonImage *ebiten.Image
  50. )
  51. func init() {
  52. img, _, err := image.Decode(bytes.NewReader(riaudio.Play_png))
  53. if err != nil {
  54. panic(err)
  55. }
  56. playButtonImage = ebiten.NewImageFromImage(img)
  57. img, _, err = image.Decode(bytes.NewReader(riaudio.Pause_png))
  58. if err != nil {
  59. panic(err)
  60. }
  61. pauseButtonImage = ebiten.NewImageFromImage(img)
  62. img, _, err = image.Decode(bytes.NewReader(riaudio.Alert_png))
  63. if err != nil {
  64. panic(err)
  65. }
  66. alertButtonImage = ebiten.NewImageFromImage(img)
  67. }
  68. type musicType int
  69. const (
  70. typeOgg musicType = iota
  71. typeMP3
  72. )
  73. func (t musicType) String() string {
  74. switch t {
  75. case typeOgg:
  76. return "Ogg"
  77. case typeMP3:
  78. return "MP3"
  79. default:
  80. panic("not reached")
  81. }
  82. }
  83. // Player represents the current audio state.
  84. type Player struct {
  85. game *Game
  86. audioContext *audio.Context
  87. audioPlayer *audio.Player
  88. current time.Duration
  89. total time.Duration
  90. seBytes []byte
  91. seCh chan []byte
  92. volume128 int
  93. musicType musicType
  94. playButtonPosition image.Point
  95. alertButtonPosition image.Point
  96. }
  97. func playerBarRect() (x, y, w, h int) {
  98. w, h = 600, 8
  99. x = (screenWidth - w) / 2
  100. y = screenHeight - h - 16
  101. return
  102. }
  103. func NewPlayer(game *Game, audioContext *audio.Context, musicType musicType) (*Player, error) {
  104. type audioStream interface {
  105. io.ReadSeeker
  106. Length() int64
  107. }
  108. // bytesPerSample is the byte size for one sample (8 [bytes] = 2 [channels] * 4 [bytes] (32bit float)).
  109. // TODO: This should be defined in audio package.
  110. const bytesPerSample = 8
  111. var s audioStream
  112. switch musicType {
  113. case typeOgg:
  114. var err error
  115. s, err = vorbis.DecodeF32(bytes.NewReader(raudio.Ragtime_ogg))
  116. if err != nil {
  117. return nil, err
  118. }
  119. case typeMP3:
  120. var err error
  121. s, err = mp3.DecodeF32(bytes.NewReader(raudio.Ragtime_mp3))
  122. if err != nil {
  123. return nil, err
  124. }
  125. default:
  126. panic("not reached")
  127. }
  128. p, err := audioContext.NewPlayerF32(s)
  129. if err != nil {
  130. return nil, err
  131. }
  132. player := &Player{
  133. game: game,
  134. audioContext: audioContext,
  135. audioPlayer: p,
  136. total: time.Second * time.Duration(s.Length()) / bytesPerSample / sampleRate,
  137. volume128: 128,
  138. seCh: make(chan []byte),
  139. musicType: musicType,
  140. }
  141. if player.total == 0 {
  142. player.total = 1
  143. }
  144. const buttonPadding = 16
  145. w := playButtonImage.Bounds().Dx()
  146. player.playButtonPosition.X = (screenWidth - w*2 + buttonPadding*1) / 2
  147. player.playButtonPosition.Y = screenHeight - 160
  148. player.alertButtonPosition.X = player.playButtonPosition.X + w + buttonPadding
  149. player.alertButtonPosition.Y = player.playButtonPosition.Y
  150. player.audioPlayer.Play()
  151. go func() {
  152. s, err := wav.DecodeF32(bytes.NewReader(raudio.Jab_wav))
  153. if err != nil {
  154. log.Fatal(err)
  155. return
  156. }
  157. b, err := io.ReadAll(s)
  158. if err != nil {
  159. log.Fatal(err)
  160. return
  161. }
  162. player.seCh <- b
  163. }()
  164. return player, nil
  165. }
  166. func (p *Player) Close() error {
  167. return p.audioPlayer.Close()
  168. }
  169. func (p *Player) update() error {
  170. select {
  171. case p.seBytes = <-p.seCh:
  172. close(p.seCh)
  173. p.seCh = nil
  174. default:
  175. }
  176. if p.audioPlayer.IsPlaying() {
  177. p.current = p.audioPlayer.Position()
  178. }
  179. if err := p.seekBarIfNeeded(); err != nil {
  180. return err
  181. }
  182. p.switchPlayStateIfNeeded()
  183. p.playSEIfNeeded()
  184. p.updateVolumeIfNeeded()
  185. if inpututil.IsKeyJustPressed(ebiten.KeyU) {
  186. b := ebiten.IsRunnableOnUnfocused()
  187. ebiten.SetRunnableOnUnfocused(!b)
  188. }
  189. return nil
  190. }
  191. func (p *Player) shouldPlaySE() bool {
  192. if p.seBytes == nil {
  193. // Bytes for the SE is not loaded yet.
  194. return false
  195. }
  196. if inpututil.IsKeyJustPressed(ebiten.KeyP) {
  197. return true
  198. }
  199. r := image.Rectangle{
  200. Min: p.alertButtonPosition,
  201. Max: p.alertButtonPosition.Add(alertButtonImage.Bounds().Size()),
  202. }
  203. if image.Pt(ebiten.CursorPosition()).In(r) {
  204. if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
  205. return true
  206. }
  207. }
  208. for _, id := range p.game.justPressedTouchIDs {
  209. if image.Pt(ebiten.TouchPosition(id)).In(r) {
  210. return true
  211. }
  212. }
  213. return false
  214. }
  215. func (p *Player) playSEIfNeeded() {
  216. if !p.shouldPlaySE() {
  217. return
  218. }
  219. sePlayer := p.audioContext.NewPlayerF32FromBytes(p.seBytes)
  220. sePlayer.Play()
  221. }
  222. func (p *Player) updateVolumeIfNeeded() {
  223. if ebiten.IsKeyPressed(ebiten.KeyZ) {
  224. p.volume128--
  225. }
  226. if ebiten.IsKeyPressed(ebiten.KeyX) {
  227. p.volume128++
  228. }
  229. if p.volume128 < 0 {
  230. p.volume128 = 0
  231. }
  232. if 128 < p.volume128 {
  233. p.volume128 = 128
  234. }
  235. p.audioPlayer.SetVolume(float64(p.volume128) / 128)
  236. }
  237. func (p *Player) shouldSwitchPlayStateIfNeeded() bool {
  238. if inpututil.IsKeyJustPressed(ebiten.KeyS) {
  239. return true
  240. }
  241. r := image.Rectangle{
  242. Min: p.playButtonPosition,
  243. Max: p.playButtonPosition.Add(playButtonImage.Bounds().Size()),
  244. }
  245. if image.Pt(ebiten.CursorPosition()).In(r) {
  246. if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
  247. return true
  248. }
  249. }
  250. for _, id := range p.game.justPressedTouchIDs {
  251. if image.Pt(ebiten.TouchPosition(id)).In(r) {
  252. return true
  253. }
  254. }
  255. return false
  256. }
  257. func (p *Player) switchPlayStateIfNeeded() {
  258. if !p.shouldSwitchPlayStateIfNeeded() {
  259. return
  260. }
  261. if p.audioPlayer.IsPlaying() {
  262. p.audioPlayer.Pause()
  263. return
  264. }
  265. p.audioPlayer.Play()
  266. }
  267. func (p *Player) justPressedPosition() (int, int, bool) {
  268. if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
  269. x, y := ebiten.CursorPosition()
  270. return x, y, true
  271. }
  272. if len(p.game.justPressedTouchIDs) > 0 {
  273. x, y := ebiten.TouchPosition(p.game.justPressedTouchIDs[0])
  274. return x, y, true
  275. }
  276. return 0, 0, false
  277. }
  278. func (p *Player) seekBarIfNeeded() error {
  279. // Calculate the next seeking position from the current cursor position.
  280. x, y, ok := p.justPressedPosition()
  281. if !ok {
  282. return nil
  283. }
  284. bx, by, bw, bh := playerBarRect()
  285. const padding = 4
  286. if y < by-padding || by+bh+padding <= y {
  287. return nil
  288. }
  289. if x < bx || bx+bw <= x {
  290. return nil
  291. }
  292. pos := time.Duration(x-bx) * p.total / time.Duration(bw)
  293. p.current = pos
  294. if err := p.audioPlayer.SetPosition(pos); err != nil {
  295. return err
  296. }
  297. return nil
  298. }
  299. func (p *Player) draw(screen *ebiten.Image) {
  300. // Draw the bar.
  301. x, y, w, h := playerBarRect()
  302. vector.DrawFilledRect(screen, float32(x), float32(y), float32(w), float32(h), playerBarColor, true)
  303. // Draw the cursor on the bar.
  304. c := p.current
  305. cx := float32(x) + float32(w)*float32(p.current)/float32(p.total)
  306. cy := float32(y) + float32(h)/2
  307. vector.DrawFilledCircle(screen, cx, cy, 12, playerCurrentColor, true)
  308. // Compose the current time text.
  309. m := (c / time.Minute) % 100
  310. s := (c / time.Second) % 60
  311. ms := (c / time.Millisecond) % 1000
  312. currentTimeStr := fmt.Sprintf("%02d:%02d.%03d", m, s, ms)
  313. // Draw buttons
  314. op := &ebiten.DrawImageOptions{}
  315. op.GeoM.Translate(float64(p.playButtonPosition.X), float64(p.playButtonPosition.Y))
  316. if p.audioPlayer.IsPlaying() {
  317. screen.DrawImage(pauseButtonImage, op)
  318. } else {
  319. screen.DrawImage(playButtonImage, op)
  320. }
  321. op.GeoM.Reset()
  322. op.GeoM.Translate(float64(p.alertButtonPosition.X), float64(p.alertButtonPosition.Y))
  323. screen.DrawImage(alertButtonImage, op)
  324. // Draw the debug message.
  325. msg := fmt.Sprintf(`TPS: %0.2f
  326. Press S to toggle Play/Pause
  327. Press P to play SE
  328. Press Z or X to change volume of the music
  329. Press U to switch the runnable-on-unfocused state
  330. Press A to switch Ogg and MP3 (Current: %s)
  331. Current Time: %s
  332. Current Volume: %d/128
  333. Type: %s`, ebiten.ActualTPS(), p.musicType,
  334. currentTimeStr, int(p.audioPlayer.Volume()*128), p.musicType)
  335. ebitenutil.DebugPrint(screen, msg)
  336. }
  337. type Game struct {
  338. musicPlayer *Player
  339. musicPlayerCh chan *Player
  340. errCh chan error
  341. justPressedTouchIDs []ebiten.TouchID
  342. }
  343. func NewGame() (*Game, error) {
  344. audioContext := audio.NewContext(sampleRate)
  345. g := &Game{
  346. musicPlayerCh: make(chan *Player),
  347. errCh: make(chan error),
  348. }
  349. m, err := NewPlayer(g, audioContext, typeOgg)
  350. if err != nil {
  351. return nil, err
  352. }
  353. g.musicPlayer = m
  354. return g, nil
  355. }
  356. func (g *Game) Update() error {
  357. select {
  358. case p := <-g.musicPlayerCh:
  359. g.musicPlayer = p
  360. case err := <-g.errCh:
  361. return err
  362. default:
  363. }
  364. g.justPressedTouchIDs = inpututil.AppendJustPressedTouchIDs(g.justPressedTouchIDs[:0])
  365. if g.musicPlayer != nil && inpututil.IsKeyJustPressed(ebiten.KeyA) {
  366. var t musicType
  367. switch g.musicPlayer.musicType {
  368. case typeOgg:
  369. t = typeMP3
  370. case typeMP3:
  371. t = typeOgg
  372. default:
  373. panic("not reached")
  374. }
  375. if err := g.musicPlayer.Close(); err != nil {
  376. return err
  377. }
  378. g.musicPlayer = nil
  379. go func() {
  380. p, err := NewPlayer(g, audio.CurrentContext(), t)
  381. if err != nil {
  382. g.errCh <- err
  383. return
  384. }
  385. g.musicPlayerCh <- p
  386. }()
  387. }
  388. if g.musicPlayer != nil {
  389. if err := g.musicPlayer.update(); err != nil {
  390. return err
  391. }
  392. }
  393. return nil
  394. }
  395. func (g *Game) Draw(screen *ebiten.Image) {
  396. if g.musicPlayer != nil {
  397. g.musicPlayer.draw(screen)
  398. }
  399. }
  400. func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
  401. return screenWidth, screenHeight
  402. }
  403. func main() {
  404. ebiten.SetWindowSize(screenWidth, screenHeight)
  405. ebiten.SetWindowTitle("Audio (Ebitengine Demo)")
  406. g, err := NewGame()
  407. if err != nil {
  408. log.Fatal(err)
  409. }
  410. if err := ebiten.RunGame(g); err != nil {
  411. log.Fatal(err)
  412. }
  413. }