#!/usr/bin/perl
# Filename:	vol
# Author:	David Ljung Madison <DaveSource.com>
# See License:	http://MarginalHacks.com/License
# Description:	Set main soundcard volume
# Requires:	aumix (to get/set volume settings)
#		  http://freshmeat.net/projects/aumix/
# Optional:	osd_cat (for cool on-screen settings!)
#		  http://www.ignavus.net/software.html
use strict;

##################################################
# Setup the variables
##################################################
my $PROGNAME = $0;
$PROGNAME =~ s|.*/||;

my $AUMIX	= "aumix";

my $SAVE_VOL	= "/tmp/$PROGNAME.saved";

# osd (on-screen display) if available (otherwise undef)
my $OSD		= "osd_cat";
my $OSD_ARGS	= "-d 2 -f '-*-courier-*-r-*-*-50-*-*-*-*-*-*-*' -c red -t -o 400";

my $DAY_VOL	= 90;
my $NIGHT_VOL	= 55;
my $MUTE_VOL	= 35;

my $DAY_STARTS	= 700;		# 7am
my $DAY_ENDS	= 2230;		# 10:30pm

my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
my $miltime = sprintf("%0.2d%0.2d",$hour,$min);

my $NOW_VOL	= ($miltime<$DAY_STARTS || $miltime>=$DAY_ENDS) ? $NIGHT_VOL : $DAY_VOL;

##################################################
# Usage
##################################################
sub show_time {
  my ($miltime) = @_;
  $miltime = sprintf("%0.4d",$miltime);
  my $h = substr($miltime,0,2);
  my $m = substr($miltime,2,2);
  return "$h:${m}am" if ($h<=12);
  $h-=12;
  "$h:${m}pm";
}
sub usage {
  my $msg;
  foreach $msg (@_) { print "ERROR:  $msg\n"; }
  print "\n";
  print "Usage:\t$PROGNAME [-d] [[+-]<num>|day|night|mute|unmute]\n";
  print "\tSet soundcard volume levels\n";
  print "\n";
  print "\t<num>       Specify a numeric volume setting\n";
  print "\t[+-]<num>   Increase/decrease volume\n";
  print "\tday         Daytime volume, full level [$DAY_VOL]\n";
  print "\tnight       Nighttime volume, quieter [$NIGHT_VOL]\n";
  print "\tmute        Save volume and mute (for phone calls, ..) [$MUTE_VOL]\n";
  print "\tunmute      Restore volume from last mute\n";
  print "\n";
  print "\tIf no option, $PROGNAME will toggle between normal vol and mute:\n";
  print "\t  Between ",show_time($DAY_STARTS)," and ",show_time($DAY_ENDS),
        ", uses daytime volume [$DAY_VOL]\n";
  print "\t  Otherwise uses night volume [$NIGHT_VOL]\n";
  print "\n";
  exit -1;
}

sub parse_args {
  my $action;
  while ($#ARGV>=0) {
    my $arg=shift(@ARGV);
    if ($arg =~ /^-h$/) { usage(); }
    if ($arg =~ /^-d$/) { $MAIN::DEBUG=1; next; }
    usage("Too many actions specified [$arg and $action]") if (defined($action));
    $action=$arg;
    usage("Unknown action: $action")
      unless $action =~ /^[-+]?\d+$/ || grep($action eq $_,qw(day night mute unmute));
  }
  $action = $action || "now";

  $action="mute" if ($action eq "now" && get_vol()==$NOW_VOL);

  $action;
}

##################################################
# Vol code
##################################################
sub get_vol {
  open(GET,"$AUMIX -v query|") || usage("Couldn't run aumix! [$AUMIX]");
  my ($l,$r);
  while (<GET>) {
    ($l,$r) = ($1,$2) if (/vol (\d+),\s*(\d+),/);
  }
  close(GET);
  usage("Error running aumix! [$AUMIX: $?]") if $?;
  usage("Didn't get volume settings from [$AUMIX]??")
    unless defined $l && defined $r;
  ($l,$r);
}

sub set_vol {
  my ($vol) = @_;
  system("$AUMIX -v $vol");
  usage("Error running aumix! [$AUMIX: $?]") if $?;

  my $x = int($vol/4);
  my $o = 25-$x;
  my $str = "Volume=$vol   [".(">"x$x).(" "x$o)."]\n";

  $ENV{DISPLAY} = ":0.0" unless ($ENV{DISPLAY});	# cron doesn't set
  if ($OSD && open(OSD,"|$OSD $OSD_ARGS")) {
    print OSD " "x5,$str;
    close(OSD);
    return unless $?;
  }
  print $str;
}

sub set_restored_vol {
  my ($l,$r) = @_;

  print STDERR "Volume levels are unbalanced - saving average of $l,$r\n"
    if ($l != $r);
  $l = ($l+$r)/2;
  open(SAVE,">$SAVE_VOL") || usage("Can't write [$SAVE_VOL]");
  print SAVE "$l\n";
  close(SAVE);
}

sub get_restored_vol {
  open(SAVE,"<$SAVE_VOL") || usage("Can't read [$SAVE_VOL]");
  $_ = <SAVE>;
  close(SAVE);
  usage("save_vol file corrupted [$SAVE_VOL]") unless /^(\d+)$/;
  $1;
}

##################################################
# Main code
##################################################
sub main {
  my $action = parse_args();

  if ($action =~ /^(\d+)$/) {
    set_vol($1);
  } elsif ($action =~ /^([+-]\d+)$/) {
    my $add = $1;
    set_vol(get_vol()+$add);
  } elsif ($action eq "day") {
    set_vol($DAY_VOL);
  } elsif ($action eq "night") {
    set_vol($NIGHT_VOL);
  } elsif ($action eq "mute") {
    my ($l,$r) = get_vol();
    if ($l != $MUTE_VOL || $r != $MUTE_VOL) {
      set_restored_vol($l,$r);
      set_vol($MUTE_VOL);
    }
  } elsif ($action eq "unmute") {
    set_vol(get_restored_vol());
  } elsif ($action eq "now") {
    set_vol($NOW_VOL);
  }
}
main();
