« get me outta code hell

Completely re-implement pickers - http-music - Command-line music player + utils (not a server!)
about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFlorrie <towerofnix@gmail.com>2017-08-11 10:06:04 -0300
committerFlorrie <towerofnix@gmail.com>2017-08-11 10:06:04 -0300
commit6d063af2b57841dce30ddb07bf60ce828d737697 (patch)
tree766a51d636dd43c5894c5bb28667fb607bb557fe
parent9fa3021cd8424f2e82a6b48aaf85db6fd5d27659 (diff)
Completely re-implement pickers
-rw-r--r--src/pickers.js154
-rwxr-xr-xsrc/play.js40
-rw-r--r--todo.txt1
3 files changed, 109 insertions, 86 deletions
diff --git a/src/pickers.js b/src/pickers.js
index ec4d629..6a30c37 100644
--- a/src/pickers.js
+++ b/src/pickers.js
@@ -2,71 +2,100 @@
 
 const { flattenGrouplike } = require('./playlist-utils')
 
-function makeLoopingOrderedPlaylistPicker(grouplike) {
-  // Looping ordered playlist picker - this plays all the tracks in a group
-  // in order, while looping the same order forever.
+function makePicker(grouplike, sort, loop) {
+  // Options to take into consideration:
+  // - How should the top-level be sorted?
+  //   (e.g. "order", "shuffle", "shuffle-groups")
+  // - How should looping be handled?
+  //   (e.g. "loop", "no-loop")
+  // - Also keep in mind aliases for all of the above.
+  //   (e.g. "ordered", "shuffled", "noloop")
+  //
+  // What about a shuffle-mode that should simply pick a random track every
+  // time?
+  //
+  // What about a shuffle-mode that re-shuffles the list every time a loop
+  // happens?
+  //
+  // Both of those options could probably be handled via the 'loop' option.
+
+  const topLevel = {items: []}
+
+  let generateTopLevel = () => {
+    if (sort === 'order') {
+      topLevel.items = flattenGrouplike(grouplike).items
+    }
 
-  const flatGroup = flattenGrouplike(grouplike)
-  let index = 0
+    if (sort === 'shuffle') {
+      topLevel.items = shuffleArray(flattenGrouplike(grouplike).items)
+    }
 
-  return function() {
-    if (index >= flatGroup.items.length) {
-      index = 0
+    if (sort === 'shuffle-top-level' || sort === 'shuffle-groups') {
+      topLevel.items = flattenGrouplike(shuffleArray(grouplike.items))
     }
 
-    const picked = flatGroup.items[index]
-    index++
-    return picked
+    console.log(topLevel.items.map(require('./playlist-utils').getItemPathString).join('\n'))
   }
-}
 
-function makeNonLoopingOrderedPlaylistPicker(grouplike) {
-  // Ordered playlist picker - this plays all the tracks in a group in
-  // order, after flattening it.
+  generateTopLevel()
 
-  const flatGroup = flattenGrouplike(grouplike)
   let index = 0
 
   return function() {
-    if (index < flatGroup.items.length) {
-      const picked = flatGroup.items[index]
-      index++
-      return picked
-    } else {
-      return null
+    if (index === topLevel.items.length) {
+      if (loop === 'loop-same-order' || loop === 'loop') {
+        index = 0
+      }
+
+      if (loop === 'loop-regenerate') {
+        generateTopLevel()
+        index = 0
+      }
+
+      if (loop === 'no-loop' || loop === 'no') {
+        // Returning null means the picker is done picking.
+        // (In theory, we could use an ES2015 generator intead, but this works
+        // well enough.)
+        return null
+      }
     }
-  }
-}
-
-function makeLoopingShufflePlaylistPicker(grouplike) {
-  // Shuffle playlist picker - this selects a random track at any index in
-  // the playlist, after flattening it.
-
-  const flatGroup = flattenGrouplike(grouplike)
 
-  return function() {
-    if (flatGroup.items.length) {
-      const index = Math.floor(Math.random() * flatGroup.items.length)
-      return flatGroup.items[index]
-    } else {
-      return null
+    if (index > topLevel.items.length) {
+      throw new Error(
+        "Picker index is greater than total item count?" +
+        `(${index} > ${topLevel.items.length}`
+      )
     }
-  }
-}
-
-function makeNonLoopingShufflePlaylistPicker(grouplike) {
-  // No-loop shuffle playlist picker - this takes a playlist and randomly
-  // shuffles the order of the items in it, then uses that as an "ordered"
-  // playlist (i.e. it plays all the items in it then stops).
-
-  const flatGroup = flattenGrouplike(grouplike)
-  const items = shuffleArray(flatGroup.items)
 
-  return function() {
-    if (items.length) {
-      return items.splice(0, 1)[0]
-    } else {
-      return null
+    if (index < topLevel.items.length) {
+      // Pick-random is a special exception - in this case we don't actually
+      // care about the value of the index variable; instead we just pick a
+      // random track from the generated top level.
+      //
+      // Loop=pick-random is different from sort=shuffle. Sort=shuffle always
+      // ensures the same song doesn't play twice in a single shuffle. It's
+      // like how when you shuffle a deck of cards, you'll still never pick
+      // the same card twice, until you go all the way through the deck and
+      // re-shuffle the deck!
+      //
+      // Loop=pick-random instead picks a random track every time the picker
+      // is called. It's more like you reshuffle the complete deck every time
+      // you pick something.
+      //
+      // Now, how should pick-random work when dealing with groups, such as
+      // when using sort=shuffle-groups? (If I can't find a solution, I'd say
+      // that's alright.)
+      if (loop === 'pick-random') {
+        const pickedIndex = Math.floor(Math.random() * topLevel.items.length)
+        return topLevel.items[pickedIndex]
+      }
+
+      // If we're using any other mode, we just want to get the current item
+      // in the playlist, and increment the index variable by one (note i++
+      // and not ++i; i++ increments AFTER getting i so it gets us the range
+      // 0..length-1, whereas ++i increments BEFORE, which gets us the range
+      // 1..length.
+      return topLevel.items[index++]
     }
   }
 }
@@ -93,24 +122,5 @@ function shuffleArray(array) {
   return workingArray
 }
 
-module.exports = {
-  makeLoopingOrderedPlaylistPicker,
-  makeNonLoopingOrderedPlaylistPicker,
-  makeLoopingShufflePlaylistPicker,
-  makeNonLoopingShufflePlaylistPicker,
-
-  byName: {
-    'order':           makeNonLoopingOrderedPlaylistPicker,
-    'ordered':         makeNonLoopingOrderedPlaylistPicker,
-    'order-loop':      makeLoopingOrderedPlaylistPicker,
-    'ordered-loop':    makeLoopingOrderedPlaylistPicker,
-    'order-noloop':    makeNonLoopingOrderedPlaylistPicker,
-    'ordered-noloop':  makeNonLoopingOrderedPlaylistPicker,
-    'order-no-loop':   makeNonLoopingOrderedPlaylistPicker,
-    'ordered-no-loop': makeNonLoopingOrderedPlaylistPicker,
-    'shuffle':         makeLoopingShufflePlaylistPicker,
-    'shuffle-loop':    makeLoopingShufflePlaylistPicker,
-    'shuffle-noloop':  makeNonLoopingShufflePlaylistPicker,
-    'shuffle-no-loop': makeNonLoopingShufflePlaylistPicker,
-  }
-}
+
+module.exports = makePicker
diff --git a/src/play.js b/src/play.js
index 50be20e..408b79c 100755
--- a/src/play.js
+++ b/src/play.js
@@ -7,7 +7,7 @@ const clone = require('clone')
 const fs = require('fs')
 const fetch = require('node-fetch')
 const commandExists = require('./command-exists')
-const { byName: pickersByName } = require('./pickers')
+const makePicker = require('./pickers')
 const startLoopPlay = require('./loop-play')
 const processArgv = require('./process-argv')
 const processSmartPlaylist = require('./smart-playlist')
@@ -55,7 +55,8 @@ async function main(args) {
   let sourcePlaylist = null
   let activePlaylist = null
 
-  let pickerType = 'shuffle'
+  let pickerSortMode = 'shuffle'
+  let pickerLoopMode = 'loop-regenerate'
   let playerCommand = await determineDefaultPlayer()
   let playOpts = []
 
@@ -276,15 +277,31 @@ async function main(args) {
 
     'np': util => util.alias('-no-play'),
 
-    '-picker': function(util) {
-      // --picker <picker type>  (alias: --selector)
-      // Selects the mode that the song to play is picked.
+    '-sort-mode': function(util) {
+      // --sort-mode <mode>  (alias: --sort)
+      // Sets the mode by which the playback order list is sorted.
+      // Valid options include 'order', 'shuffle', and 'shuffle-top-level'
+      // (which has an alias 'shuffle-groups').
       // See pickers.js.
 
-      pickerType = util.nextArg()
+      pickerSortMode = util.nextArg()
     },
 
-    '-selector': util => util.alias('-picker'),
+    '-sort': util => util.alias('-sort-mode'),
+
+    '-loop-mode': function(util) {
+      // --loop-mode <mode>  (alias: --loop)
+      // Sets the mode by which the playback order list is looped (typically,
+      // what happens when the picker's index counter gets to the end of the
+      // list).
+      // Valid options include 'no-loop', 'loop-same-order' (or 'loop'),
+      // 'loop-regenerate', and 'pick-random'.
+      // See pickers.js.
+
+      pickerLoopMode = util.nextArg()
+    },
+
+    '-loop': util => util.alias('-loop-mode'),
 
     '-player': function(util) {
       // --player <player>
@@ -307,14 +324,9 @@ async function main(args) {
   }
 
   if (willPlay || (willPlay === null && shouldPlay)) {
-    if (!Object.keys(pickersByName).includes(pickerType)) {
-      console.error("Invalid picker type: " + pickerType)
-      return
-    }
-
-    console.log(`Using ${pickerType} picker.`)
+    console.log(`Using sort: ${pickerSortMode} and loop: ${pickerLoopMode}.`)
 
-    const picker = pickersByName[pickerType](activePlaylist)
+    const picker = makePicker(activePlaylist, pickerSortMode, pickerLoopMode)
 
     console.log(`Using ${playerCommand} player.`)
 
diff --git a/todo.txt b/todo.txt
index 5501926..e943b79 100644
--- a/todo.txt
+++ b/todo.txt
@@ -304,3 +304,4 @@ TODO: safeUnlink probably shouldn't throw/reject if it fails to delete its
 TODO: A shuffle-groups picker would be fun! (That is, it would take a
       grouplike, then shuffle all the items on the TOP level; so it would
       play random (top-level) groups at a time.)
+      (Done!)