guess.js 5.2 KB

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