#!/usr/bin/perl -w
# 26GK6RK - mixr created by Pip@CPAN.Org
# 3CT7asP - mixr renamed to pmix (counter-part to ximp)
# Desc: A wrapper for ximp which together replace aumix.
#   pmix has two main modes: one which only has meters for PCM, Vol, && CD
#     && the other with meters for all detected
# 2do: 
#   make fully aumix compatible? (wrap ximp similarly)
#   add stereo toggle
#   study all other software mixers in .bak && imp good stuff
#   add interactive recoloring (once Simp::CPik is good) which saves to .rc
#   handle term resize events (once Simp does)
#   handle mouse events       (once Simp does)

use strict;
use Curses::Simp;

my $mjvr = 1; my $mnvr = 0; my $ptvr = '41O0AFb'; my $auth = 'Pip@CPAN.Org';
my $name = $0; $name =~ s/.*\///;
my @dopt = ( 'vol', 'cd', 'pcm' ); # Dfalt mode options
my %slut = ( '!' => 1, '@' => 2, '#' => 3, '$' => 4, '%' => 5,
             '^' => 6, '&' => 7, '*' => 8, '(' => 9, ')' => 0 );
my @chrz = ( {
               'legc' => '[',  # unsel left bar edge
               'lgcl' => 'gb', # unsel lt br edg col
               'barc' => '-',  # unselected bar char
               'brcl' => 'wb', # unsel bar colorz
               'regc' => ']',  # unsel rite bar edge
               'rgcl' => 'gb', # unsel rt br edg col
               'sldc' => '|',  # unsel slider bar
               'sdcl' => 'Uu', # unsel slider bar
               'txcl' => 'ou', # unsel text colorz
               'tikc' => '\'', # unsel tick char
               'ticl' => 'cb', # unsel tick col
               'pccl' => 'wB', # unsel percent col
               'mutd' => '*muted*', # unsel muted chan
             },
             {
               'legc' => '{',  # sel left bar edge
               'lgcl' => 'Gb', # sel lt br edg col
               'barc' => '=',  # selected bar char
               'brcl' => 'Wb', # sel bar colorz
               'regc' => '}',  # sel rite bar edge
               'rgcl' => 'Gb', # sel rt br edg col
               'sldc' => 'I',  # sel slider bar
               'sdcl' => 'Pp', # sel slider bar
               'txcl' => 'Yp', # sel text colorz
               'tikc' => '"',  # sel tick char
               'ticl' => 'Ub', # sel tick col
               'pccl' => 'Gr', # sel percent col
               'mutd' => '@muted@', # sel muted chan
             }
           );
my @optz = @dopt; my @scrn = (); my @colz = (); my $scrn;
my %setz = ();    my %bkst = (); my %knum = ();
my $chgr = "";    my $keey = ""; my $temp = "";
my $widt = 0;     my $hite = 0;  my $idle = 0;
my $wich = 0;     my $mode = 0;  my $oidl = 0;
my $mute = 0;     my $alll = 1;  my $only = 0;

sub FindChgr {
  my $test = "";
  my $chmx = `which ximp`; chomp($chmx);
  my $aumx = `which aumix`;    chomp($aumx);
  if   (-e $chmx) { $chgr = "ximp"; }
  elsif(-e $aumx) { $chgr = "aumix"; }
  else            { die "Need ximp in your path!!!\n"; }
  print "Testing for valid mixer device..."; # die if no /dev/mixer
  system("$chgr 2&>testkaka.txt");
  open KAKA, "<testkaka.txt"; $test = join '', <KAKA>; close KAKA;
  unlink("testkaka.txt");
  if   ($test =~ /unable to open/i) { die "No /dev/mixer found!!! =(\n"; }
  else { print "found!\n"; }
}

sub DrawBarz {
  my $sndx;

  %knum = $scrn->KNum(); $widt = $scrn->Widt(); $hite = $scrn->Hite();
  @scrn = ();     @colz = ();
  $scrn->Move(); # needed to reset cursor location for Draw bug
  foreach(@optz) {
    if($_ ne 'dig1') {
      $sndx = 0;
      if($alll || $_ eq $optz[$wich]) { $sndx = 1; }
      push(@scrn, $chrz[$sndx]{'barc'} x $widt);
      push(@colz, $chrz[$sndx]{'brcl'} x $widt);
      for $temp (1..8) { # draw ticks on 11 intervals
        substr($scrn[-1], int($widt / 100 * (11 * $temp)),     1, $chrz[$sndx]{'tikc'}); 
        substr($colz[-1], int($widt / 100 * (11 * $temp)) * 2, 2, $chrz[$sndx]{'ticl'}); 
      }
      substr($scrn[$#scrn],  0, length($_)+1, $chrz[$sndx]{'legc'} . $_);
      substr($colz[$#colz],  0, length($chrz[$sndx]{'legc'})*2, $chrz[$sndx]{'lgcl'} x length($chrz[$sndx]{'legc'}));
      substr($colz[$#colz],  length($chrz[$sndx]{'legc'})*2, length($_)*2, $chrz[$sndx]{'txcl'} x length($_));
      substr($scrn[$#scrn], -1,            1, $chrz[$sndx]{'regc'});
      substr($colz[$#colz], -1*(length($chrz[$sndx]{'regc'})*2), length($chrz[$sndx]{'regc'})*2, $chrz[$sndx]{'rgcl'} x length($chrz[$sndx]{'regc'}));
      if (length($scrn[$#scrn]) > 7) {
        substr($scrn[$#scrn],  length($scrn[$#scrn])-(length($_)+1), length($_), $_ );
        substr($colz[$#colz], (length($scrn[$#scrn])-(length($_)+1))*2, length($_)*2, $chrz[$sndx]{'txcl'} x length($_));
        if(length($scrn[$#scrn]) > 15) {
          if(($mute) || ($only && $_ ne $optz[$wich])) { $temp = $_; $_ = $chrz[$sndx]{'mutd'}; }
          $setz{$_} = 99 if($setz{$_} > 99);
          substr($scrn[$#scrn], int((length($scrn[$#scrn])-5)/2.0),   5,   '(' . sprintf("%02d", $setz{$_}) . '%)');
          substr($colz[$#colz], int((length($scrn[$#scrn])-5)/2.0)*2, 5*2, $chrz[$sndx]{'pccl'} x 5);
          if(($mute) || ($only && $_ ne $optz[$wich])) { $_ = $temp; }
        }
      }
      substr($scrn[$#scrn], int(($setz{$_} / 100.0) * $widt), 1, $chrz[$sndx]{'sldc'});
      substr($colz[$#colz], int(($setz{$_} / 100.0) * $widt) * 2, length($chrz[$sndx]{'sldc'})*2, $chrz[$sndx]{'sdcl'} x length($chrz[$sndx]{'sldc'}));
    }
  }
  $scrn->Text('asin' => \@scrn);
  $scrn->Colr('asin' => \@colz);
}

sub ChngBarz { AlllBarz() if $alll; SaveSetz(); }

sub SaveSetz { foreach(keys(%setz)) { `$chgr $_$setz{$_}`; } }

sub LoadSetz {
  my $curr = ""; my @linz = ();
  $curr = `$chgr -q`;
  @linz = split /\n/, $curr;
  foreach(@linz) {
    if(/^(\w+) (\d+), (\d+)/) {
      $setz{$1} = int( ($2 + $3) / 2.0 ); # avg. channels
    }
  }
}

sub SaveValz {
  open RCFL, ">$ENV{'HOME'}/.${name}rc";
  foreach(sort(keys(%setz))) { print RCFL "$_:$setz{$_}:$setz{$_}\n"; }
  close RCFL;
}

sub LoadValz {
  my @fldz = ();

  if(-e "$ENV{'HOME'}/.${name}rc") {
    open(RCFL, "<$ENV{'HOME'}/.${name}rc");
    foreach(<RCFL>) {
      @fldz = split /:/;
      $setz{$fldz[0]} = int(($fldz[1] + $fldz[2]) / 2);
    }
    close(RCFL);
    $mute = 0; $only = 0;
    SaveSetz();
  }
}

sub ToglMode {
  $mode ^= 1; $wich = 0;
  if($mode) { @optz = sort(keys(%setz)); } 
  else      { @optz = @dopt; }
}

sub ToglMute {
  $mute ^= 1;
  if($mute) {
    foreach (keys(%setz)) { $bkst{$_} = $setz{$_}; $setz{$_} = 0; }
  } else {
    foreach (keys(%bkst)) { $setz{$_} = $bkst{$_}; }
  }
  ChngBarz();
}

sub ToglAlll { $alll ^= 1; ChngBarz(); }

sub ToglOnly {
  $only ^= 1;
  foreach(keys(%setz)) { 
    if($_ ne $optz[$wich]) { 
      if($only) { $bkst{$_} = $setz{$_}; $setz{$_} = 0; $alll = 0; }
      else      { $setz{$_} = $bkst{$_}; }
    }
  }
  ChngBarz();
}

sub AlllBarz { foreach(@optz) { $setz{$_} = $setz{$optz[$wich]}; } }

sub AsinChan {
  my $chan = shift || return;

  for($wich = 0; $wich < @optz; $wich++) {
    last if($optz[$wich] eq $chan);
  }
}

sub ShowInfo {
  $scrn->Mesg(
'titl' => "$name Info Screen",
" $name v$mjvr.$mnvr.$ptvr - by $auth
 
$name was inspired by aumix
 
 Shout out to Keith && all the LBox.Org crew.  Thanks to Beppu-san for 
being a good friend.  I hope you find $name useful.  Please don't 
hesitate to let me know if you app-ree-see-ate it or if you'd like
me to add something for you.  I'd be glad to improve it given new 
suggestions.  Please support FSF.Org && EFF.Org.  Thanks.  TTFN.
 
                                                       -Pip
 
");
}

sub ShowHelp {
  $scrn->Mesg(
'titl' => "$name Help Screen",
"                        Global Keys:                           
                                                               
  h         - displays this Help screen                        
  b         - toggles Big mode (with all channel options)      
  m         - toggles Muting                                   
  o         - toggles current channel as Only audible channel  
  a         - locks slider bar in All channels                 
  s         - Save all current channel values to ~/.${name}rc  
  l         - Load ~/.${name}rc into current channel values    
  v         - jump to Volume channel                           
  c         - jump to Cd channel                               
  p         - jump to Pcm channel                              
 DownArrow  - go to next channel                               
 UpArrow    - go to previous channel                           
 LeftArrow  - lower channel level                              
 RightArrow - raise channel level                              
  0..9      - jump to n/9th of channel (must shift for jump up)
 
                        System Stuff:
       ?/H/F1  - Help  :  I - Info  :  x/q/Esc - eXit
");
}

FindChgr(); # verify that a valid /dev/mixer && changer can be found
LoadSetz(); #   before opening a new Curses screen
$scrn = Curses::Simp->new();#'_flagaudr' => 0);
$scrn->FlagBkgr('true');
DrawBarz();
while(!defined($keey) || (lc($keey) ne 'x' && lc($keey) ne 'q' && ord($keey) != 27)) {
  $keey = $scrn->GetK();
  LoadSetz();
  $oidl = $idle;
  if(defined($keey)) {
    if   (lc($keey)    eq " ") { ToglAlll(); }
    elsif(lc($keey)    eq "b") { ToglMode(); }
    elsif(lc($keey)    eq "m") { ToglMute(); }
    elsif(lc($keey)    eq "a") { ToglAlll(); }
    elsif(lc($keey)    eq "o") { ToglOnly(); }
    elsif(lc($keey)    eq "s") { SaveValz(); }
    elsif(   $keey     eq "l") { LoadValz(); }
    elsif(   $keey     eq "L") { 
      $alll = 0; ChngBarz(); 
      $optz[@dopt] = 'line' unless($mode);
      AsinChan('line'); }
    elsif(lc($keey)    eq "i") { ShowInfo(); }
    elsif(lc($keey)    eq "v") { AsinChan('vol'); }
    elsif(lc($keey)    eq "c") { AsinChan('cd'); }
    elsif(lc($keey)    eq "p") { AsinChan('pcm'); }
    elsif(   $keey     =~ /^\d$/) { # handle number keys
      if($setz{$optz[$wich]} > int($keey * 11)) {
        $setz{$optz[$wich]} = int($keey * 11);
        ChngBarz();
      }
    } elsif(   $keey     =~ /^[!@#\$%^&*()]$/) { # handle shifted number keys
      $setz{$optz[$wich]} = int($slut{$keey} * 11);
      ChngBarz();
    } elsif(exists $knum{$keey}) {
      if     ($knum{$keey} eq "KEY_LEFT")  { 
        $setz{$optz[$wich]}-- if $setz{$optz[$wich]};
        ChngBarz();
      } elsif($knum{$keey} eq "KEY_RIGHT") { 
        $setz{$optz[$wich]}++ if $setz{$optz[$wich]} < 99;
        ChngBarz();
      } elsif($knum{$keey} eq "KEY_HOME") { 
        $setz{$optz[$wich]} = 0;
        ChngBarz();
      } elsif($knum{$keey} eq "KEY_END") { 
#        $setz{$optz[$wich]} = 99;
#        ChngBarz();
      } elsif($knum{$keey} eq "KEY_UP")    { 
        $wich = @optz if $wich == 0; $wich--;
      } elsif($knum{$keey} eq "KEY_DOWN")  { 
        $wich++; $wich = 0 if $wich == @optz;
      } elsif($keey != -1)                 {
        ShowHelp();
      } else {
        if($idle < 32) { $scrn->Wait(0.25); $idle += 0.25; }
        else           { $scrn->Wait(2);    $idle += 2;    }
      }
    } elsif(lc($keey) ne 'x' && lc($keey) ne 'q' && ord($keey) != 27) {
#    } elsif(lc($keey) eq 'h' || lc($keey) eq '?') {
      ShowHelp();
    }
  }
  if($idle == $oidl) { $idle = 0; }
  DrawBarz();
}
