#! /bin/zsh

# This file is maintained at http://git.mdcc.cx/draai

# draai - Joost van Baal's music playing stuff

# see also
#   joostvb@kovalevskaya:~% grep mpc .zshrc_local

# copyright:
COPYRIGHT='Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Joost van Baal-Ilić'

# This program is freely distributable per the following license:
LICENSE="
Draai is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License, as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.  This program is distributed WITHOUT ANY WARRANTY.  You should have
received a copy of the GNU General Public License along with draai.  If not,
see <http://www.gnu.org/licenses/>."

VERSION='20110603'

prog=`basename $0`

setopt extendedglob

# FIXME: default, rc, env, commandline
test -r $HOME/.draai/rc && . $HOME/.draai/rc

debug=${DR_DEBUG:-false}
shuffle=${DR_SHUFFLE:-true}
timestamp=${DR_TIMESTAMP:-true}
raw=${DR_RAW:-false}
sloppy=${DR_SLOPPY:-false}
watch=${DR_WATCH:-dr_watch}
# watch="watch --no-title"

# mpd.conf:
# metadata_to_use        "artist,album,title,track,name,genre,date,composer,performer,disc,comment"
rawformat="%file%: [[%artist% - ]%album% - %title% ][(%comment%)]([%name% - ][%genre% - ][%date% - ][%composer% - ][%performer% - ][%disc% - ][%track% ]%time%)"
format="[%position%) ][[[%artist% - ]%album% - %title%]|[%file%]] ([%name% - ]%time%)"

crossfade=${DR_CROSSFADE:-4}
peek=${DR_PEEK:-30}
seek=${DR_SEEK:-20}

volume_step=${DR_VOLUME_STEP:-10}

fadeout_step=${DR_FADEOUT_STEP:-10}
fadeout_repeat=${DR_FADEOUT_REPEAT:-10}
fadeout_sleep=${DR_FADEOUT_SLEEP:-0.2}

syslog_file=${DR_SYSLOG_FILE:-/var/log/syslog}
syslog_facility=${DR_SYSLOG_FACILITY:-user}
syslog_level=${DR_SYSLOG_LEVEL:-info}

usage()
{

cat <<EOT
Usage: $prog [options] draai|init|next|guestlist|unguestlist|nice|insert|tail|logtail|list|peek|syslog|skip|osjittisstil|delete|fastforward|crescendo|diminuendo|quit [audiofile [audiofile...]]
Play audio tracks, using mpc(1).

  -V, --version         show program's version number and exit
  -h, --help            show this help message and exit
  -d, --debug           be very verbose
  -S, --noshuffle       don't shuffle tracks and leave random mode untouched
                          (default is: do shuffle and disable random mode)
  -p PLAYLIST, --playlist=PLAYLIST
                        playlist file, -p can be supplied more than once
  -n N, --number=N      number of tracks FIXME: not yet implemented
  -t T, --time=T        if combined with quit or draai: time at which to quit
                          or start
  -T, --notime          if combined with tail: do not add timestamps
                          FIXME: not yet implemented
  -r, --raw             if combined with tail, peek or list: print raw stuff, suitable
                          for postprocessing
  -s, --sloppy          don't try hard to make everything sound smooth.  if
                          combined with skip: risk a squeak on old hardware.
  -l, --license         show license and exit

crescendo:
   Play louder.

delete:
   Delete track(s) from current playlist. Last arguments should be _positions_ of
   tracks to be removed, not filename.  If no position is given, deletes upcoming
   track from playlist.

diminuendo:
   Play softer.

draai:
   Play music.

fastforward:
   Seek forward in current track.

guestlist:
   Reschedule listed tracknumbers as upcoming tracks. Last arguments should be
   the positions of tracks to be put on the guestlist, not filenames.

harder:
   Play louder.

init:
   Start a bunch of X terminals setting up some draai jobs: tail, logtail, peek,
   syslog, ...

insert:
   Add file to playlist and schedule it as upcoming track.

list:
   List current playlist.

logtail:
   Run tail(1) on system log file.

osjittisstil:
   Run this if unwanted silence pops up during a playing session (for now, it runs
   draai --sloppy skip).

peek:
   Show status of current song, and show upcoming $peek FIXME tracks.

quit:
   quit

skip:
   Skip this track, start playing the next one now.

syslog:
   Send raw information from tail to syslog.  You likely want to run "draai syslog"
   in the background.

tail:
   Print information about track when it starts playing, similar to
   tail -f on a logfile.

unguestlist:
   Reschedule listed tracknumbers to end of playlist.

zachter:
   Play softer.


EOT

}

# help2man ./draai | man -l -

fadeout()
{
    # make it fade out (mpc does _not_ support that out of the box!)
    # we could add a "silent" song as last and use crossfade
    # we however use mpc's volume control.

    # as a sideeffect, sets $volume to original volume

    # % mpc volume
    # volume: 87%
    volume=$(mpc volume)
    volume=${volume#volume: }
    volume=${volume%\%*}

    step=$(( $volume / $fadeout_step ))

    ## repeat 20
    repeat $fadeout_repeat
    do
       # -5
       mpc volume -$step > /dev/null
       # GNU coreutils
       sleep $fadeout_sleep
    done

##  exponential fadeout:
##  don't do that, it's way too fast
#
##        # 1024 = 2^10
##        step=$(( $vol / 1024 ))
##        mpc volume -$step > /dev/null
##        sleep 0.2
##        repeat 8
##        do
##          mpc volume -$step > /dev/null
##          step=$(( step * 2 ))
##          sleep 0.2
##        done
}

# calculate position of current track
position()
{
    for l in $(mpc | grep playing)
    do
        # zshexpn(1) "FILENAME GENERATION"
        if [[ $l == (#b)\#(*)/* ]]
        then
            position=$match
        fi
    done
    $debug && echo >&2 $prog: set position to \'$position\'
}

# see zsh-betabuiltins(1) for getopts help
# see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=384014 for manpage
# http://zsh.dotsrc.org/Doc/Release/zsh_16.html#SEC89
# getopt(1) understands long options. use that one?

# dpigs(1) has nice getopt(1) example

OPTS=$(getopt -o VhdSp:n:t:Trsl --long version,help,debug,noshuffle,playlist:,number:,time:,notime,raw,sloppy,license -- "$@")
eval set -- "$OPTS"

# define playlists to be an (ordinary) array
typeset -a playlists
playlists=()

while true; do
    case "$1" in
      -V|--version)
        echo $prog $VERSION
        exit 0
        ;;
      -h|--help)
        usage
        exit 0
        ;;
      -q|--quiet)
        quiet=true
        shift
        ;;
      -d|--debug)
        debug=true
        shift
        ;;
      -S|--noshuffle)
        shuffle=false
        shift
        ;;
      -p|--playlist)
        playlists=($playlists "$2")
        shift 2
        ;;
      -n|--number)
        n="$2"
        shift 2
        ;;
      -t|--time)
        time="$2"
        shift 2
        ;;
      -T|--notime)
        timestamp=false
        shift
        ;;
      -r|--raw)
        raw=true
        shift
        ;;
      -s|--sloppy)
        sloppy=true
        shift
        ;;
      -l|--license)
        echo $COPYRIGHT
        echo $LICENSE
        exit 0
        ;;
      --)
        shift
        break
        ;;
      *)
        usage
        exit 1
        ;;
    esac
done

shift $(($OPTIND - 1))

# define args to be an associative array
typeset -A args

command=$1
shift

case $command in
  draai)
    # no other args:
    # drDefault(plfiles=options.playlists, n=options.n, shuffle=options.shuffle)
    # some files present:
    # drDraai(args, n=options.n, shuffle=options.shuffle)
    set -A files "$@"
    if test -n "$time"
    then
        opts=
        $debug && opts="--debug"
        $shuffle || opts="$opts --noshuffle"
        test -n "$n" && opts="$opts --number $n"
        if test -n "$playlists"
        then
            for playlist in $playlists
            do
                opts="$opts --playlist $playlist"
            done
        fi
        if [[ $time = (#b)+(<->) ]]
        then
            # fixme: more strict match
            echo draai $opts draai $files | at now + $match minutes
        else
            echo draai $opts draai $files | at $time
        fi
    else
        # mpc clear > /dev/null
        mpc crop > /dev/null
        $debug && echo >&2 "$prog: gonna add $files"
        for f in $files
        do
            mpc add "$f"
            $debug && echo >&2 $prog: adding \'$f\'
        done
        # for some reason, piping to mpc with  ... echo $f; done | mpc add ...  fails

        for playlist in $playlists
        do
            mpc load "$playlist" | grep -v volume
        done
        $shuffle && mpc shuffle > /dev/null && mpc random off > /dev/null
        mpc crossfade $crossfade > /dev/null
        mpc play
    fi
    ;;
  init|genesis)
    # user should be permitted to write on tty12. on Debian systems, this
    # means being a member of group tty
    draai tail >/dev/tty12 &
    draai syslog &

    if test -n "$DISPLAY"
    then
       x-terminal-emulator -e "$watch draai peek" &
       x-terminal-emulator -e "draai logtail" &
       draai tail
    else
      # use "screen improved" by some debian guy

      # screen draai tail
      screen -S draai -U dr_watch draai peek

      # "create"
      # exec

      # DR_PEEK=10 screen -S draai -U dr_watch draai peek 
      # screen -p draai -X c
      # screen -R  (debian-specific)
      # -d -m
    fi

    ;;
  next|volgende)
    # drNext(plfile=drDefaultNextPLFile, n=options.n)
    # next($args)
    echo not yet implemented
    ;;
  guestlist)
    # FIXME we assume all args are >> current position
    if test $# -gt 0
    then
        position=0
        position
        shift=0
        for p in "$@"
        do
            echo $p
        done | sort --numeric-sort --reverse | while read p
        do
            position=$(($position + 1 ))
            p=$(( $p + $shift ))
            $debug && echo >&2 "$prog: gonna move $p to $position"
            mpc move $p $position
            shift=$(( $shift + 1 ))
        done
    else
        echo "$prog: command guestlist needs tracknumber(s)"
        exit 1
    fi
    ;;
  unguestlist|unfriend|ontvriend)
    if test $# -gt 0
    then
        position=$( mpc playlist | tail -1 | cut -d\) -f1 )
        for p in "$@"
        do
            echo $p
        done | sort --numeric-sort --reverse | while read p
        do
            $debug && echo >&2 "$prog: gonna move $p to $position"
            mpc move $p $position
        done
    else
        echo "$prog: command unguestlist needs tracknumber(s)"
        exit 1
    fi
    ;;
  insert|voegin)
    set -A files "$@"
    for f in $files
    do
        mpc add "$f"
        position=$( mpc playlist | tail -1 | cut -d\) -f1 )
        draai guestlist $position
    done
    ;;
  nice)
    # drNice(args[1:], plfiles=options.playlists, n=options.n, shuffle=options.shuffle)
    echo not yet implemented
    ;;
  skip|slaover)
    # skip current song
    $sloppy || fadeout
    mpc next

    # FIXME: is NOT sleeping really "sloppy"!?
    $sloppy || sleep $fadeout_sleep

    # restore original volume
    $sloppy || mpc volume $volume >/dev/null
    ;;
  osjittisstil)
    draai --sloppy skip
    ;;
  delete)
    if test $# -gt 0
    then
        for p in "$@"
        do
            echo $p
        done | sort --numeric-sort --reverse | while read p
        do
            mpc del $p
        done
    else
        position=0
        position
        mpc del $(( $position + 1 ))
    fi
    ;;
  fastforward|voorwaarts)
    mpc seek +$seek%
   ;;
  crescendo|harder|louder)
    mpc volume +$volume_step
   ;;
  diminuendo|zachter|softer)
    mpc volume -$volume_step
   ;;
  tail|staart)
    # tail: like tail -f

    # FIXME use variables for 2 and 74 e.a.
    old=""
    while sleep 2; do
        current=$(mpc --format $format | head -1)
        $raw && string=$(mpc --format $rawformat | head -1)
        if test "$old" != "$current"
        then
            if $raw
            then
                echo $string
            else
                echo -n $(date +%H:%M)
                # 80 - 5 = 75 columns left: 1 for space, 3 for dots, 41 + 30 for rest
                # see zsh-misc PROMPT EXPANSION

                # c With ${#name}, count the total number of characters in an array,  as  if
                #   the elements were concatenated with spaces between them.

                # Sun 18 17:09 < Fruit> $#foo
                # Sun 18 17:10 < Fruit> maar dat is stiekem stuk
                # Sun 18 17:10 < Fruit> want dat geeft je het aantal bytes
                # Sun 18 17:10 < Fruit> ipv het aantal karakters

                # only truncate if $current > 74 chars.
                # if <= 74 chars: line out to left
                if [[ $#current  -gt 74 ]]
                then
                    print -P " %41>>$current%>>...%30<<$current"
                else
                    # parse current: Guitar - Mind The Gap Volume 61 - Tokyo Memory (5:19)
                    # split off timespec: rightadjust
                    # time is "12:16)"
                    time=$(echo $current | sed 's/.*(//')
                    track=$(echo $current | sed 's/^\(.*\)(.*$/\1/')

                    # we assume time is not wider than (234:67)
                    # AFX - Analord 08 - Backdoor.Spyboter.A         (125:06)
                    #     -66-                                        ( -7-
                    printf " %-66.66s%8.8s\n" "$track" "($time"
                fi
            fi
            old=$current
        fi
    done
    # alternatively, one might like to run
    #   watch --no-title 'mpc --format "[[[%artist% - ]%album% - %title%]|[%file%]] ([%name% - ][%track% ]%time%)"'
    ;;
  logtail)
    tail -F $syslog_file | grep ' draai\['
    ;;
  syslog)
    draai --raw tail | logger -i -p $syslog_facility.$syslog_level -t draai
    ;;
  list)
    # list current playlist
    $raw && format=$rawformat
    mpc --format $format playlist
    ;;
  peek|spiek)
    # show status of current song
    mpc | grep playing
    # show upcoming $peek tracks
    position=0
    position
    total=$(mpc playlist | wc -l)
    $raw && format=$rawformat
    mpc --format $format playlist | tail -$(( $total - $position + 1 )) | head -$peek | \
      while read current
    do
        time=$(echo $current | sed 's/.*(//')
        track=$(echo $current | sed 's/^\(.*\)(.*$/\1/')
        if [[ $#track -gt 74 ]]
        then
            if [[ $track == \>* ]]
            then
                print -nP "%47>>$track%>>...%22<<$track"
            else
                print -nP " %46>>$track%>>...%22<<$track"
            fi
        else
            if [[ $track == \>* ]]
            then
                printf "%-72.72s" "$track"
            else
                printf " %-71.71s" "$track"
            fi
       fi
       # we assume time is not wider than (234:67)
       printf "%8s\n" "($time"
    done
    ;;
  play|unpause|resume|ga)
    if test -n "$time"
    then
        if [[ $time = (#b)+(<->) ]]
        then
            # fixme: more strict match
            echo draai play | at now + $match minutes
        else
            echo draai play | at $time
        fi
    else
        mpc play
    fi
    ;;
  quit|exit|stop|pause|ho)
    if test -n "$time"
    then
        if [[ $time = (#b)+(<->) ]]
        then
            # fixme: more strict match
            echo draai quit | at now + $match minutes
        else
            echo draai quit | at $time
        fi
    else
        fadeout
        if [[ "$command" == pause ]]
        then
            mpc pause
        else
            mpc stop > /dev/null
        fi
        # restore original volume
        mpc volume $volume > /dev/null
    fi
    ;;
  *)
    usage
    exit 1
    ;;
esac

