#!/usr/local/bin/wish8.4 -f
# -*- tcl -*-

#  Copyright (C) 1994 by Jean-Marc Zucconi (jmz@cabri.obs-besancon.fr)
#  Everyone is granted permission to copy, modify and redistribute.
#  This notice must be preserved on all copies or derivates.

set cdplayer  "/usr/local/bin/cdplayer";
set bitmapdir "/usr/local/lib/xcd";

set size  [lindex $argv 0];

#color/geometry variables:
if {$size == "-small"} {
  set bd 2; 		#border width for buttons
} else {
  set bd 3; 		#border width for buttons
}
set bg gray;		#background color
set fg black;		#foreground color
set active firebrick;	#active color

#font for track#/time display:
if {$size == "-small"} {
  set digitfont "-Adobe-Helvetica-Bold-R-Normal--*-120-*";
  set smallfont "-Adobe-Helvetica-Bold-R-Normal--*-100-*";
} else {
  set digitfont "-Adobe-Helvetica-Medium-R-Normal--*-180-*";
}

#the cd status. May be playing, ejected, stopped.
set stat('playing') 1;
set stat('ejected') 2;
set stat('stopped') 3;
set stat('none')   -1;
set status $stat('none');
# the paused variable is set to 1 if the disk is paused
set paused 0;

#display variables:
set track  "00";
set time   "00:00";

#volume level
set volume -1;

#+@@@
# these variables are set in setup: toc ntracks total_time
# toc should not exists until setup is called
set ntracks 0;
set total_time 0;

#when skipping ff_pressed or rewind_pressed will be set to the current track
# number
set ff_pressed 0;
set rewind_pressed 0;

#track_pressed is set to 1 to display the total # of tracks
set track_pressed 0;


#the command used to start playing. May be play_all, play_tracks, play_resume
# or play_msf
set play "play_all";

#first track to play (in proc play_tracks). Set by the next/prev buttons
set first_track 0;
#-@@@

#the list of buttons
set btns [list play stop pause eject prev next rewind ff];   

#tables for volume conversion
for { set i  0} { $i <= 255} { incr i} {
  set a [expr int(exp($i/55.253) -1)];
  set v1($i) $a;
  set v2($a) $i;
  for {set j [expr $a - 1]} {($j >= 0) && ![info exists v2($j)]} {incr j -1} {
    set v2($j) $i;
  }  
}

#widgets
frame .top;
frame .middle;
frame .bottom;
frame .topright
frame .right
frame .left

foreach i $btns {
  if {$size == "-small"} {
    button .$i -relief groove  -bitmap @$bitmapdir/${i}_s.xbm -bg $bg -bd $bd;
  } else {
    button .$i -relief groove  -bitmap @$bitmapdir/$i.xbm -bg $bg -bd $bd;
  }
  bind .$i <1> ${i}_button;
}

bind .rewind  <ButtonRelease-1> rewind_release;
bind .ff      <ButtonRelease-1> ff_release;

button .off -text quit -bd $bd -pady 0 -command exit -relief groove;

#label .track -text track;
if {$size == "-small" } {
  button .track -text track -relief groove -font $smallfont;
} else {
  button .track -text track -relief groove;
}
bind .track <1> "set track_pressed 1";
bind .track <ButtonRelease-1> "set track_pressed 0";
bind .track <2> "tk_popup .pop %X %Y";
#label .time -text time;
set time_format 1
if {$size == "-small" } {
  button .time -text time -relief groove -font $smallfont;
} else {
  button .time -text time -relief groove;
}
bind .time <1> "incr time_format";
bind .time <ButtonRelease-1> "incr time_format";

label .trackn -textvariable track -relief sunken -width 3 -font $digitfont;
label .timec -textvariable time -relief sunken -width 5 -font $digitfont;

if {$size == "-small" } {
  scale .volume -from 0 -to 100 -orient horizontal -length 150 \
      -command set_volume -font $smallfont;
} else {
  scale .volume -label volume -from 0 -to 100 -orient horizontal -length 170 \
      -command set_volume;
}

pack .track .trackn .time .timec -in .topright -side left -fill none;
pack .play .pause .stop .eject  -in .top	-side left;
pack .prev .next .rewind .ff -in .middle -side left;
pack  .off -in .bottom  -fill x;
pack .top .middle .bottom -side top -pady 0 -fill both -in .left;
pack .topright .volume -side top -in .right
pack .left .right -side left

wm resizable . 0 0

############################################################################
#start the player
set player [open |$cdplayer r+];

#event loop
after 100 do_status;

#############################################################################
# do_status returns the CD state: <status> <track> <min> <sec> <frame>
proc do_status {} {
  global player;
  global ff_pressed rewind_pressed;

  puts $player "status"; flush $player;
  gets $player str;
  set s [lindex $str 0]; # the status code
  if { $s == -1} {
    set_ejected;
  } else {
    switch $s {
      17 {play_in_progress $str} \
      18 {play_paused $str} \
      19 {play_completed} \
      20 {play_error} \
      21 {play_no_status} \
    }
  }
  # check if ff or rewind are pressed
  if {$ff_pressed || $rewind_pressed} {
    skip_msf $str;
  }
  after 100 do_status;
}

proc play_in_progress {a} {
  global track time total_time;
  global status stat time_format;
  global active;
  global toc;
  global ntracks track_pressed;

  if { !$track_pressed} {
    set track [lindex $a 1];
  } else {
    set track [format "%02d" $ntracks];
    set time [format "%02d:%02d" [expr int($total_time/60)] [ expr $total_time%60]];
  }
  if {$track_pressed == 0} {
    if {$time_format & 1} {
      set time  [format "%02d:%02d" [lindex $a 2] [lindex $a 3]];
    } else {
      scan $track "%d" curt;
      set time [time_str $curt];
    }
  }
  if {$status != $stat('playing')} {
    .play configure -relief sunken -fg $active;
    setup;
    set status $stat('playing');
  }
}
proc play_paused {a} {
  global status stat track time paused total_time;
  global active;
  global ntracks track_pressed;
  global toc time_format;
  
  if { !$track_pressed} {
    set track [lindex $a 1];
  } else {
    set track [format "%02d" $ntracks];
    set time [format "%02d:%02d" [expr int($total_time/60)] [ expr $total_time%60]];
  }
  if {$time_format & 1} {
    if {[info exists ptime]} {
      set time $ptime;
    } else {
      if {$track_pressed == 0} {
	set time  [format "%02d:%02d" [lindex $a 2] [lindex $a 3]];
      }
    }
  } else {    
    if {$track_pressed == 0} {
      scan $track "%d" curt;
      set n [expr $curt + 1];
      set t [expr [lindex $toc($n) 1]*60 - [lindex $toc($curt) 1]*60 + \
		 [lindex $toc($n) 2] - [lindex $toc($curt) 2] + [lindex $toc($n) 3]/75 - \
		 [lindex $toc($n) 3]/75];
      set m [expr int($t/60)];
      set s [expr $t%60];
      set time  [format "%02d:%02d" $m $s];
    }
  }
  if {$paused == 0} {
    .pause configure -relief sunken -fg $active;
    .play  configure -relief sunken -fg $active;
    set paused 1;
    set status $stat('playing');
    set track [lindex $a 1];
    if {$track_pressed == 0} {
      set time  [format "%02d:%02d" [lindex $a 2] [lindex $a 3]];
    }
    set ptime $time
    if { ![info exists toc] } {
      set p 1;
    } else {
      set p 0;
    }
    setup;
    # if paused at startup, set play to play_msf
    if { $p } {
      global play toc;
      global m1 s1 f1;
      scan $time "%d:%d" m s;
      scan $track "%d" curt;
      set t [expr $m*60 + $s + [lindex $toc($curt) 1]*60 + [lindex $toc($curt) 2]];
      set m1 [expr int($t/60)];
      set s1 [expr $t%60];
      set f1 [lindex $a 3];
      set play "play_msf";
    }
  }
}
proc play_completed {} {
  play_no_status;
}
proc play_error {} {
}
proc play_no_status {} {
  global status stat track time;
  global btns fg active;
  global total_time ntracks;
  if { $status != $stat('stopped')} {
    foreach i $btns {
      .$i configure -relief groove -fg $fg ;
    }
    .stop configure -relief sunken -fg $active;
    setup;
    set track [format "%02d" $ntracks];
    set time [format "%02d:%02d" [expr int($total_time/60)] [ expr $total_time%60]];
    set status $stat('stopped');
  }
}

# read the table of contents of the disk and set the volume level
proc setup {} {
  global player;

  if { ![info exists toc] } {
    global toc ntracks total_time;

    puts $player "tocentry";
    flush $player;
    set i 1;
    set ntracks 0;
    if {[winfo exists .pop]} {destroy .pop}
    menu .pop
    while { $i > 0} {
      gets $player str;
      set toc($i) $str;
      if { [lindex $str 0] == 255} {
	set i 0;
	set total_time [expr int([lindex $str 1] * 60 + [lindex $str 2] + [lindex $str 3] /75.)];
      } else {
        .pop add command -label $i -command "gototrack $i"
	incr ntracks;
	incr i;
      }
    }
    # if volume has not been set, get it from the player
    global volume v1;
    if {$volume == -1} {
      puts $player "getvol";
      flush $player;
      gets $player str;
      set volume 0;
      if {[lindex $str 0] != -1} {
        set vol [lindex $str 0];
        set volume $v1([lindex $str 0]);
      }
    }
    .volume set $volume;
  }
}
proc set_ejected {} {
  global player status stat track time;
  global btns active fg;
  if {$status != $stat('ejected')} {
    foreach i $btns {
      .$i configure -relief groove -fg $fg;
    }
    .eject configure -relief sunken -fg $active;
    set track "00";
    set time "00:00";
    set status $stat('ejected');
    if { [ info exists toc ] } {
      unset toc;
    }
  }
}
proc play_msf {} {
  global player ntracks toc m1 s1 f1;
  set t [expr $ntracks + 1];
  set m2 [lindex $toc($t) 1];
  set s2 [lindex $toc($t) 2];
  set f2 [expr [lindex $toc($t) 3] - 1];
  set t [expr $m2*4500 + $s2*75 +$f2 -1];
  set m2 [expr $t/4500];
  set s2 [expr ($t-$m2*4500)/75];
  set f2 [expr $t-$m2*4500-$s2*75];
  puts $player "msfplay $m1 $s1 $f1 $m2 $s2 $f2";
  flush $player;
}
proc play_all {} {
  global toc m1 s1 f1;
  set m1 [lindex $toc(1) 1];
  set s1 [lindex $toc(1) 2];
  set f1 [lindex $toc(1) 3];
  play_msf;
}
proc play_tracks {} {
  global toc m1 s1 f1 first_track;
  set m1 [lindex $toc($first_track) 1];
  set s1 [lindex $toc($first_track) 2];
  set f1 [lindex $toc($first_track) 3];
  play_msf;
  set first_track 0;
}
proc play_resume {} {
  global player;
  puts $player "resume";
  flush $player;
}
  
############################################################################
# buttons
#########
proc set_volume {a} {
  global player volume v2;

  set volume $a;
  puts $player "setvol $v2($a) $v2($a)";
  flush $player;
}

proc play_button {} {
  global status stat play;
  global active fg;
  if { $status == $stat('stopped') } {
    .stop configure -fg $fg -relief groove;
    .play configure -relief sunken -fg $active;
    $play;
  }
}
proc pause_button {} {
  global status stat paused player play;
  global active fg;
  global first_track ntracks;
  if { $status == $stat('playing')} {
    if { $paused == 0 } {
      .pause configure -relief sunken -fg $active;
      puts $player "pause";
      set paused 1;
      set play "play_resume";
    } else {
      .pause configure -relief groove -fg $fg;
      $play;
      set paused 0;
    }
    flush $player;
  }
}
proc stop_button {} {
  global status stat player play;
  puts $player "stop";
  flush $player;
  set status $stat('none');
  set play "play_all";
}
proc eject_button {} {
  global status stat player play;
  puts $player "eject";
  flush $player;
  set_ejected;
  set status $stat('none');
  set play "play_all";
}

proc next_button {} {
  global player status stat paused play;
  global ntracks track first_track time;
  global active fg;
  if {$status == $stat('ejected')} {
    return;
  }
  if {$first_track != $ntracks} {
    # the last track is not reached
    scan $track "%d" curt;
    if {$ntracks != $curt} {
      if {$first_track == 0} {
	set first_track [expr $curt + 1];
      } else {
	incr first_track;
      }
      .next configure -relief sunken -fg $active
    } else {
      set first_track 1;
    }
    set track [format "%02d" $first_track];
    set time "00:00";
    set play "play_tracks";
    if {$status == $stat('playing') && $paused != 1} {
      play_tracks;
    }
    .next configure -fg $fg -relief groove;
  }
}
proc prev_button {} {
  global player status stat paused play;
  global ntracks track first_track time;
  global active fg;
  if {$status == $stat('ejected')} {
    return;
  }
  scan $track "%d" curt;
  if {$first_track == 0 && $curt == 1} {
    set track "01";
    set time "00:00";
    set first_track 1;
    if {$status == $stat('playing') && $paused != 1} {
      play_tracks;
    }
  }
  if {$first_track != 1 && $curt > 1} {
    if {$first_track != 0} {
      incr first_track -1;
    } else {
      # if not at the beginning of a track, only go at the beginning
      if {$time == "00:00"} {
	set first_track [expr $curt - 1];
      } else {
	set first_track $curt;
      }
    }
    .prev configure -relief sunken -fg $active;
    set track [format "%02d" $first_track];
    set time "00:00";
    set play "play_tracks";
    if {$status == $stat('playing') && $paused != 1} {
      play_tracks;
    }
    .prev configure -fg $fg -relief groove;
  }
}
proc ff_button {} {
  global ff_pressed active;
  global status stat track;
  if {$status == $stat('playing')} {
    set ff_pressed 1;
    .ff configure -relief sunken -fg $active;
  }
}
proc ff_release {} {
  global ff_pressed fg;
  set ff_pressed 0;
  .ff configure -relief groove -fg $fg;
}
proc skip_msf {s} {
  global time track m1 s1 toc;
  global status paused play;
  scan $track "%d" curt;
  new_msf $curt $time [lindex $s 3];
  #global f1; puts "$s m/s/f= $m1 $s1 $f1";
  if {$paused == 1} {
    set play "play_msf";
    set t [expr $m1*60 + $s1 - [lindex $toc($curt) 1]*60 - [lindex $toc($curt) 2]];
    set m [expr int($t/60)];
    set s [expr $t%60];
    set time [format "%02d:%02d" $m $s];
  } else {
    play_msf;
  }
}

proc rewind_button {} {
  global rewind_pressed active;
  global status stat track;
  if {$status == $stat('playing')} {
    scan $track "%d" rewind_pressed;
    .rewind configure -relief sunken -fg $active;
  }
}
proc rewind_release {} {
  global rewind_pressed fg;
  set rewind_pressed 0;
  .rewind configure -relief groove -fg $fg;
}  
proc new_msf {trk ms f} {
  global m1 s1 f1;
  global toc ff_pressed rewind_pressed;

  scan $ms "%d:%d" m1 s1;
  if {$ff_pressed} {
    set delta 10;
    set i [expr $trk + 1];
  } else {
    set delta -10;
    set i $trk;
  }
  set t [expr $m1*60 + $s1 + $f/75. + [lindex $toc($trk) 1]*60 + \
         [lindex $toc($trk) 2] + [lindex $toc($trk) 3]/75. + $delta];
  set t2 [expr [lindex $toc($i) 1]*60 + [lindex $toc($i) 2] + [lindex $toc($i) 3]/75.];

  if {($ff_pressed && ($t >= $t2)) || ($rewind_pressed && ($t <= $t2))} {
    #start/end of track reached. stop
    set m1 [lindex $toc($i) 1];
    set s1 [lindex $toc($i) 2];
    set f1 [lindex $toc($i) 3];
    set ff_pressed 0;
    set rewind_pressed 0;
  } else {
    set m1 [expr int($t/60)];
    set s1 [expr int($t)%60];
    set f1 [expr int($t*75 - $m1*4500 - $s1*75)];
  }
}
proc time_str {curt} {
  global toc
  set n [expr $curt + 1];
  set t [expr [lindex $toc($n) 1]*60 - [lindex $toc($curt) 1]*60 + \
	     [lindex $toc($n) 2] - [lindex $toc($curt) 2] + [lindex $toc($n) 3]/75 - \
	     [lindex $toc($n) 3]/75];
  set m [expr int($t/60)];
  set s [expr $t%60];
  return  [format "%02d:%02d" $m $s];
}
proc gototrack {t} {
  global first_track time track play fg;
  set first_track $t;
  set track [format "%02d" $first_track];
  set time "00:00";
  set play "play_tracks";
  .stop configure -relief groove -fg $fg ;
  play_tracks;
}
