A musicbox

Description

This 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
 }

See also peterc's Tcl MP3 Alarm Clock.

TV (Nov 28 ' 08) I didn't know about this page, but I made an audio player based on tcl-cgi for use with a mobile internet device and a server which I think could pass as cool: Jukebox based on webserver with Tcl cgi scripts. If combined with Random worklist generator a correct shuffle should result, contrary to the above link, I think comparable to AK's version.