Version 19 of A musicbox

Updated 2007-03-05 16:56:47

Description

a musicbox is a small round-the-clock random shuffle player for ogg-files. Switching to mp3 is trivial.


In the beginning (after my real-world alarm clock broke) I (= Andreas Kupries) used cron and a wakeup script using wish and the bell to wake me up.

Then I switched to

  find . -name '*.ogg' -print | xargs /usr/local/bin/ogg123 -z

for random play now that I ogg'ified quite a number of my CDs.

The deficiency of this command pipe: I can play about 220-230 songs in a day, but there are over 1000 in my catalog now. This means that the find/xargs combo will only reach about a quarter of all songs as random play is only in the groups created by xargs and not over the whole set.

I finally decided to tackle this and created the script below. It features random shuffle over all songs (using code from the wiki) and a persistent storage containing which songs were already played, so that they are excluded the next time the musicbox starts, thus giving every song a fair chance to be played.

It should be trivial to switch this over to a commandline mp3 player (for example mpg123), or snack. I hope that snack will support Ogg Vorbis soon, so that I can remove the dependency on an external application. snack now (Aug 2002) supports Ogg


  #!/usr/local/bin/tclsh
  # Musicbox application, playing round the clock, all of the music I
  # have in certain directories. No arguments. Use ~/.musicboxrc to store
  # persistent information.
  #
  # Internal information:
  #
  # 1. Array containing the names of all the files which were already
  #    played as keys. The values are not relevant.
  #
  # 2. List of files to play. Filled via 'find', but only with files not
  #    already played. Shuffled in random order.
  #
  # Only (1) is persistent.
  #
  # *future* Rewrite to use the snack extension (when it incorporates
  # *ogg support).

  proc main {} {
      global    played
      array set played {}

      # Get persistent playing information.
      catch {source ~/.musicboxrc}

      cd ~/.mydata/CDs

      # Forever
      while {1} {
          # Search the music directories for unplayed files and shuffle them
          set playlist [shuffle [unplayed_files]]

          # If there were no new files then use the files which were
          # already played as list of songs to play and reset the 'played'
          # information.

          if {[llength $playlist] == 0} {
              set playlist [shuffle [lsort [array names played]]]
              clear_played
          }

          play $playlist
          #exit
      }
  }

  proc unplayed_files {} {
      set playlist [list]

      set p [open "|find . -name *.ogg" r]
      while {![eof $p]} {
          if {[gets $p line] < 0} {continue}
          set line [string trim $line]
          if {$line == {}} {continue}
          if {[isplayed $line]} {continue}
          lappend playlist $line
      }

      return $playlist
  }


  proc isplayed {file} {
      global played
      return [info exists played($file)]
  }


  proc clear_played {} {
      global    played
      unset     played
      array set played {}
      save_played
      return
  }


  proc K {x y} {return $x}

  proc shuffle {list} {
      set n [llength $list]
      set slist [list]
      while {$n>0} {
          set j [expr {int(rand()*$n)}]
          lappend slist [lindex $list $j]
          incr n -1
          set temp [lindex $list $n]
          set list [lreplace [K $list [set list {}]] $j $j $temp]
      }
      return $slist
  }


  proc play {list} {
      global played

      foreach file $list {
          puts stdout "Playing $file ..."
          catch {exec /usr/local/bin/ogg123 $file > /dev/null}
          set played($file) .
          save_played
      }

      return
  }


  proc save_played {} {
      global played

      set    f [open ~/.musicboxrc.new w]
      puts  $f "array set played \{[array get played]\}"
      close $f

      catch {file copy -force ~/.musicboxrc ~/.musicboxrc.old}
      file copy -force ~/.musicboxrc.new ~/.musicboxrc
      return
  }


  # ... go
  main

Have fun.


And now a little shell function giving me a command line interface to the musicbox (partially linux and site specific).

 function music () {
        case $1 in
        on)        if [ 0 -eq `ps auxw | grep musicbox | grep -v grep | wc -l` ]
                then
                        #No musicbox running, start it.
                        musicbox > "$HOME/.mydata/CDs/db/collections/played.`date`" &
                else
                        echo "Musicbox already running, ignoring command"
                fi
                ;;
        off)        killall musicbox ogg123
                ;;
        pause)        killall -STOP ogg123 musicbox
                ;;
        cont)        killall -CONT ogg123 musicbox
                ;;
        stat)        here=`pwd`
                cd $HOME/.mydata/CDs
                find . -name '*.m3u' -print | wc -l | xargs echo '#Albums = '
                find . -name '*.ogg' -print | wc -l | xargs echo '#Songs  = '
                cd $here
                ;;
        played)        if [ -r $HOME/.musicboxrc ]
                then
                        echo 'source ~/.musicboxrc ;puts "Played: [array size played]"; exit' | tclsh
                else
                        echo "Nothing played"
                fi
                ;;
        vol)        rexima -v | grep pcm
                ;;
        clear)        rm -f $HOME/.musicboxrc
                rm -f $HOME/.musicboxrc.new
                rm -f $HOME/.musicboxrc.old
                ;;
        *)        # Assume a number and use it to control the mixer
                rexima pcm $1
                ;;
        esac
 }

---> For other audio applications in Tcl-Tk, see: Audio Applications in Tcl-Tk

---> For other categories of applications in Tcl-Tk, see: Applications in Tcl-Tk, Index


Category Application | Category Multimedia | Category Music | Category File | Category Sound | Category Signal Processing