« get me outta code hell

mtui - Music Text User Interface - user-friendly command line music player
about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--guess.js192
1 files changed, 192 insertions, 0 deletions
diff --git a/guess.js b/guess.js
new file mode 100644
index 0000000..deceb01
--- /dev/null
+++ b/guess.js
@@ -0,0 +1,192 @@
+'use strict'
+
+const Backend = require('./backend')
+const processSmartPlaylist = require('./smart-playlist')
+
+const {
+  flattenGrouplike,
+  parentSymbol,
+  searchForItem
+} = require('./playlist-utils')
+
+const {
+  util: {
+    ansi,
+    telchars: telc
+  }
+} = require('./tui-lib')
+
+function untilEvent(object, event) {
+  return new Promise(resolve => {
+    object.once(event, resolve)
+  })
+}
+
+const resetLine = '\r\x1b[0J'
+const dim = ansi.resetAttributes() + ansi.setAttributes([ansi.A_DIM])
+const write = data => process.stdout.write(data)
+
+async function game() {
+  const backend = new Backend()
+
+  const result = await backend.setup()
+  if (result.error) {
+    console.error(result.error)
+    process.exit(1)
+  }
+
+  // TODO: nah
+  backend.setVolume(60)
+
+  process.stdin.setRawMode(true)
+  process.stdin.on('data', async data => {
+    if (data[0] === 0x03) {
+      await backend.stopPlaying()
+      process.exit(0)
+    }
+  })
+
+  const sourcePath = process.argv[2] || process.env.HOME + '/Music'
+  let grouplike = {source: ['crawl-local', sourcePath]}
+  grouplike = await processSmartPlaylist(grouplike)
+
+  // TODO: Actually let the user choose this..!
+  const group = grouplike.items.find(item => item.name === 'library')
+  const allTracks = flattenGrouplike(grouplike).items
+
+  const displayTrack = (track, shouldLimit) => {
+    const parent = track[parentSymbol]
+    const limit = (shouldLimit
+      ? text => text.length > 30 ? '…' + text.slice(-30) : text
+      : text => text)
+    process.stdout.write(limit(track.name))
+    if (parent.name) {
+      process.stdout.write(' (From ' + limit(parent.name) + ')')
+    }
+  }
+
+  while (allTracks.length) {
+    const track = allTracks[Math.floor(Math.random() * allTracks.length)]
+    backend.play(track)
+    await untilEvent(backend, 'playing')
+    console.log('-- Listen! Then press space to pause and make a guess. --')
+
+    let startTime = Date.now()
+    let playTime = 0
+    let gaveUp = false
+    let giveUpNext = false
+
+    let trackMatch = null
+    let input = ''
+    const displayInput = () => {
+      write(resetLine)
+      write(' >> ' + ansi.setAttributes([ansi.A_BRIGHT]))
+      write(input)
+      write(dim + ' :: ' + ansi.resetAttributes())
+      if (trackMatch) {
+        displayTrack(trackMatch, true)
+      } else {
+        write(ansi.setForeground(ansi.C_RED))
+        write('(None)')
+        write(ansi.resetAttributes())
+      }
+      write(`\r\x1b[${4 + input.length}C`)
+    }
+
+    const echoFn = () => {
+      let t = (playTime + Date.now() - startTime) / 1000
+      t = Math.floor(t * 10) / 10
+      if (t % 1 === 0) {
+        t = t + '.0'
+      }
+      write(resetLine + t + 's')
+    }
+
+    while (true) {
+      let echo
+      if (!backend.player.isPaused) {
+        echo = setInterval(echoFn, 50)
+      }
+      const key = await untilEvent(process.stdin, 'data')
+      clearInterval(echo)
+      if (key[0] === 0x10 || (key[0] === 0x20 && !backend.player.isPaused)) {
+        if (backend.player.isPaused) {
+          startTime = Date.now()
+          console.log(resetLine + dim + '<Unpaused.>')
+          write(ansi.resetAttributes())
+        } else {
+          playTime += Date.now() - startTime
+          console.log(resetLine + dim + '<Paused. Type the track\'s name below! ^P to resume.>')
+          write(ansi.resetAttributes())
+          echoFn()
+          displayInput()
+        }
+        backend.togglePause()
+      /*
+      } else if (key[0] === 0x3f && (!key.length || !backend.player.isPaused)) {
+        backend.setPause(false)
+        gaveUp = true
+        break
+      */
+      } else if (backend.player.isPaused) {
+        if (telc.isBackspace(key)) {
+          input = input.slice(0, -1)
+          giveUpNext = false
+        } else if (telc.isEnter(key)) {
+          if (trackMatch) {
+            write('\n')
+            try {
+              if (trackMatch === track) {
+                write(ansi.setAttributes([ansi.A_BRIGHT, ansi.C_GREEN]))
+                write('-- You got it! --\n')
+                displayTrack(trackMatch, false)
+                break
+              } else {
+                write(ansi.setAttributes([ansi.A_BRIGHT, ansi.C_RED]))
+                write('-- Not this one! --\n')
+                displayTrack(trackMatch, false)
+              }
+            } finally {
+              write(ansi.resetAttributes())
+              write('\n')
+            }
+          } else {
+            if (giveUpNext) {
+              backend.setPause(false)
+              gaveUp = true
+              break
+            } else {
+              write(resetLine + '(Press enter twice in a row to reveal the answer.)\n')
+              giveUpNext = true
+            }
+          }
+        } else {
+          input += key.toString()
+          giveUpNext = false
+        }
+        trackMatch = searchForItem({items: allTracks}, input)
+        displayInput()
+      }
+    }
+
+    process.stdout.write(resetLine)
+
+    if (gaveUp) {
+      console.log('-- You chose to reveal the track that played. --')
+      displayTrack(track, false)
+      console.log('')
+    }
+
+    console.log('-- Press space to continue! ^C to quit. --')
+    while (true) {
+      const key = await untilEvent(process.stdin, 'data')
+      if (key[0] === 0x20) {
+        break
+      }
+    }
+    console.log('')
+  }
+}
+
+game().catch(err => console.error(err))
+