guess.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. 'use strict'
  2. const Backend = require('./backend')
  3. const os = require('os')
  4. const processSmartPlaylist = require('./smart-playlist')
  5. const {
  6. flattenGrouplike,
  7. parentSymbol,
  8. searchForItem
  9. } = require('./playlist-utils')
  10. const {
  11. util: {
  12. ansi,
  13. telchars: telc
  14. }
  15. } = require('tui-lib')
  16. function untilEvent(object, event) {
  17. return new Promise(resolve => {
  18. object.once(event, resolve)
  19. })
  20. }
  21. const resetLine = '\r\x1b[0J'
  22. const dim = ansi.resetAttributes() + ansi.setAttributes([ansi.A_DIM])
  23. const write = data => process.stdout.write(data)
  24. async function game() {
  25. const backend = new Backend()
  26. const result = await backend.setup()
  27. if (result.error) {
  28. console.error(result.error)
  29. process.exit(1)
  30. }
  31. const QP = await backend.addQueuePlayer()
  32. // TODO: nah
  33. QP.setVolume(60)
  34. process.stdin.setRawMode(true)
  35. process.stdin.on('data', async data => {
  36. if (data[0] === 0x03) {
  37. await QP.stopPlaying()
  38. process.exit(0)
  39. }
  40. })
  41. const sourcePath = process.argv[2] || os.homedir() + '/Music'
  42. let grouplike = {source: ['crawl-local', sourcePath]}
  43. grouplike = await processSmartPlaylist(grouplike)
  44. const allTracks = flattenGrouplike(grouplike).items
  45. const displayTrack = (track, shouldLimit) => {
  46. const parent = track[parentSymbol]
  47. const limit = (shouldLimit
  48. ? text => text.length > 30 ? '…' + text.slice(-30) : text
  49. : text => text)
  50. process.stdout.write(limit(track.name))
  51. if (parent.name) {
  52. process.stdout.write(' (From ' + limit(parent.name) + ')')
  53. }
  54. }
  55. while (allTracks.length) {
  56. const track = allTracks[Math.floor(Math.random() * allTracks.length)]
  57. QP.setPause(false)
  58. const promise = untilEvent(QP, 'playing')
  59. QP.play(track)
  60. await promise
  61. console.log('-- Listen! Then press space to pause and make a guess. --')
  62. let startTime = Date.now()
  63. let playTime = 0
  64. let gaveUp = false
  65. let giveUpNext = false
  66. let trackMatch = null
  67. let input = ''
  68. const displayInput = () => {
  69. write(resetLine)
  70. write(' >> ' + ansi.setAttributes([ansi.A_BRIGHT]))
  71. write(input)
  72. write(dim + ' :: ' + ansi.resetAttributes())
  73. if (trackMatch) {
  74. displayTrack(trackMatch, true)
  75. } else {
  76. write(ansi.setForeground(ansi.C_RED))
  77. write('(None)')
  78. write(ansi.resetAttributes())
  79. }
  80. write(`\r\x1b[${4 + input.length}C`)
  81. }
  82. const fmtTime = () => {
  83. let t = (playTime + Date.now() - startTime) / 1000
  84. t = Math.floor(t * 10) / 10
  85. if (t % 1 === 0) {
  86. t = t + '.0'
  87. }
  88. return t + 's'
  89. }
  90. const echoFn = () => {
  91. write(resetLine + fmtTime())
  92. }
  93. while (true) {
  94. let echo
  95. if (!QP.player.isPaused) {
  96. echo = setInterval(echoFn, 50)
  97. }
  98. const key = await untilEvent(process.stdin, 'data')
  99. clearInterval(echo)
  100. if (key[0] === 0x10 || (key[0] === 0x20 && !QP.player.isPaused)) {
  101. if (QP.player.isPaused) {
  102. startTime = Date.now()
  103. console.log(resetLine + dim + '<Unpaused.>')
  104. write(ansi.resetAttributes())
  105. } else {
  106. console.log(resetLine + dim + `<Paused @ ${fmtTime()}. Type the track's name below! ^P to resume.>`)
  107. playTime += Date.now() - startTime
  108. write(ansi.resetAttributes())
  109. echoFn()
  110. displayInput()
  111. }
  112. QP.togglePause()
  113. /*
  114. } else if (key[0] === 0x3f && (!key.length || !QP.player.isPaused)) {
  115. QP.setPause(false)
  116. gaveUp = true
  117. break
  118. */
  119. } else if (QP.player.isPaused) {
  120. if (telc.isBackspace(key)) {
  121. input = input.slice(0, -1)
  122. giveUpNext = false
  123. } else if (telc.isEnter(key)) {
  124. if (trackMatch) {
  125. write('\n')
  126. try {
  127. if (trackMatch === track) {
  128. write(ansi.setAttributes([ansi.A_BRIGHT, ansi.C_GREEN]))
  129. write('-- You got it! --\n')
  130. displayTrack(trackMatch, false)
  131. break
  132. } else {
  133. write(ansi.setAttributes([ansi.A_BRIGHT, ansi.C_RED]))
  134. write('-- Not this one! --\n')
  135. displayTrack(trackMatch, false)
  136. }
  137. } finally {
  138. write(ansi.resetAttributes())
  139. write('\n')
  140. }
  141. } else {
  142. if (giveUpNext) {
  143. QP.setPause(false)
  144. gaveUp = true
  145. break
  146. } else {
  147. write(resetLine + '(Press enter twice in a row to reveal the answer.)\n')
  148. giveUpNext = true
  149. }
  150. }
  151. } else {
  152. input += key.toString()
  153. giveUpNext = false
  154. }
  155. trackMatch = searchForItem({items: allTracks}, input)
  156. displayInput()
  157. }
  158. }
  159. process.stdout.write(resetLine)
  160. if (gaveUp) {
  161. console.log('-- You chose to reveal the track that played. --')
  162. displayTrack(track, false)
  163. console.log('')
  164. }
  165. console.log('-- Press space to continue! ^C to quit. --')
  166. while (true) {
  167. const key = await untilEvent(process.stdin, 'data')
  168. if (key[0] === 0x20) {
  169. break
  170. }
  171. }
  172. console.log('')
  173. }
  174. }
  175. game().catch(err => console.error(err))