#!/usr/bin/perl -w
# 26GK6RK - mixr created by Pip Stuart <Pip@CPAN.Org>
# 3CT7asP - mixr renamed to pmix (counterpart 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 working mixer devices.
# 2do: 
#   add stereo balance mode as alternative to separate L/R sliders
#   add color file reading for:  item foreground background  ('-' = default)
#     item: (active|axis|handle|hotkey|menu|play|record|track)
#     colr: (black|red|green|yellow|blue|magenta|cyan|white|[brgyupcw])
#   add interactive recoloring (using Simp::CPik) which saves to .rc
#   handle term resize events  (using Simp)
#   handle mouse events        (using Simp)

use strict;
use Curses::Simp;
my $mixr = eval("use Audio::Mixer; 1") || 0; # `ximp` is better than Mixer.pm

my $mjvr = 1; my $mnvr = 0; my $ptvr = '427KaDe';
my $auth = 'Pip Stuart <Pip@CPAN.Org>';
my $name = $0; $name =~ s/.*\///;
my @dopt = ( 'vol', 'cd', 'pcm' ); # Dfalt mode     options
my @optz = @dopt;                  # initial device options
my %slut = ( '!' => 1, '@' => 2, '#' => 3, '$' => 4, '%' => 5,   # Shift Look
             '^' => 6, '&' => 7, '*' => 8, '(' => 9, ')' => 0 ); #   Up Table
my %valz = ('wich' => 0, 'mode' => 0, 'mute' => 0, 'dig1' => 1,  # pmix values
            'alll' => 1, 'only' => 0, 'ster' => 0, 'ltrt' => 0,
            'recf' => 1, 'titl' => 0, 'info' => 0);
my @bsbr = ( # base unselected && selected slider bar texts
"[-------'--------'--------'--------'(--%)---'-------'--------'--------'--------]",
'{======="========"========"========"(==%)==="======="========"========"========}');
#"[-------1--------2--------3--------4(--%)---5-------6--------7--------8--------]",
#'{=======!========@========#========$(==%)===%=======^========&========*========}');
my @bscl = ( # base unselected && selected slider bar colors
'gbwbwbwbwbwbwbwbrbwbwbwbwbwbwbwbwbrbwbwbwbwbwbwbwbwbrbwbwbwbwbwbwbwbwbrbwbwbwbwBwbwbwbwbrbwbwbwbwbwbwbwbrbwbwbwbwbwbwbwbwbrbwbwbwbwbwbwbwbwbrbwbwbwbwbwbwbwbwbgb$',
'GbWbWbWbWbWbWbWbUbWbWbWbWbWbWbWbWbUbWbWbWbWbWbWbWbWbUbWbWbWbWbWbWbWbWbUbGrGrGrGrGrWbWbWbUbWbWbWbWbWbWbWbUbWbWbWbWbWbWbWbWbUbWbWbWbWbWbWbWbWbUbWbWbWbWbWbWbWbWbGb$'
);
my @chrz = ( { # dynamic unselected && selected slider characters && colors
               'txcl' => 'Ou', # unsel text colorz (left)
               'txlt' => ' (left)',  # unsel text  (left)
               'tlcl' => 'wu', # unsel text colorz (left)
               'txrt' => ' (right)', # unsel text  (right)
               'trcl' => 'cu', # unsel text colorz (right)
               'sldc' => '|',  # unsel slider bar
               'sdcl' => 'Uu', # unsel slider bar colr
               'rplc' => '(',  # unsel recplay left char
               'rpcl' => 'up', # unsel recplay left colr
               'rprc' => ')',  # unsel recplay rite char
               'rpcr' => 'up', # unsel recplay rite colr
               'rpcc' => 'cp', # unsel recplay char colr
               'pccl' => 'wb',      # unsel muted colr
               'mutd' => '*muted*', # unsel muted chan
             },
             {
               'txcl' => 'Yp', # sel text colorz (left)
               'txlt' => ' [LEFT]',  # sel text  (left)
               'tlcl' => 'Pu', # sel text colorz (left)
               'txrt' => ' [RIGHT]', # sel text  (right)
               'trcl' => 'Cp', # sel text colorz (right)
               'sldc' => 'I',  # sel slider bar
               'sdcl' => 'Pp', # sel slider bar colr
               'rplc' => '[',  # sel recplay left char
               'rpcl' => 'Cg', # sel recplay left colr
               'rprc' => ']',  # sel recplay rite char
               'rpcr' => 'Cg', # sel recplay rite colr
               'rpcc' => 'Yg', # sel recplay char colr
               'pccl' => 'Gr',      # sel muted colr
               'mutd' => '@MUTED@', # sel muted chan
             }
           );
my @setz = (); my @bkst = (); my %sdvz = (); my %recp = (); my $simp;
my $chgr = ''; my $keey = ''; my $idle = 0;  my $oidl = 0;  my $dela = 0.1;

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

sub DrawBarz {
  my $sndx; my $ltxt; my $lclr;  $simp->Text('colr' => [ ], [ ]); # clean slate
  my $tmp0; my $tmp1; my $widt = $simp->Widt(); my $sopt; my $isst;
  if($valz{'titl'}) {
    $simp->Text('push' => " $name v$mjvr.$mnvr.$ptvr - by $auth");
    $simp->Colr('push' => '! ' . 'G' x length($name) . ' W' . 'Y' x length($mjvr) . 'W' . 'C' x length($mnvr) . 'WROYGCUP U WW CCC CCCCCC WGGGWYYYYWCCCWC');
  }
  if($valz{'info'}) {
    my $info = ''; $info = `$chgr :iv` if($chgr eq 'ximp');
    $info =~ s/(^Device:|\n.*$)//g;
    $simp->Text('push' => "CardInfo:$info");
    $simp->Colr('push' => '!UUUUUUUUWY');
  }
  foreach(@optz) { # build a text && color line for each device
    for(my $chan = 0; $chan < ($valz{'ster'} + 1); $chan++) {
      if((!$chan       || $sdvz{$_}) && # must be valid stereo device to loop
         ($_ ne 'dig1' || $valz{'dig1'})) { # && dig1 is messed up sometimes
        $setz[$chan]{$_} =  0 unless(exists($setz[$chan]{$_}) && 
                                    defined($setz[$chan]{$_}));
        $setz[$chan]{$_} = 99 if($setz[$chan]{$_} > 99);
        $sndx = 0; # selected index
        $sndx = 1 if(  $valz{'alll'} || 
                     ( $_ eq $optz[$valz{'wich'}] && 
                      (!$valz{'ster'} || $chan == $valz{'ltrt'})));
        $ltxt = $bsbr[$sndx];             # load bases
        $lclr = $bscl[$sndx];
        $sopt = $_;                      $isst = 0;
        if($valz{'ster'} && $sdvz{$_}) { $isst = 1;
          if($chan) { $_ .= $chrz[$sndx]{'txrt'}; }
          else      { $_ .= $chrz[$sndx]{'txlt'}; }
        }
        $tmp0 = length($_);               # left edge text
        substr($ltxt, 1, $tmp0  , $_);
        if   ($chan) { substr($lclr, 2, $tmp0*2,$chrz[$sndx]{'trcl'} x $tmp0);}
        elsif($isst) { substr($lclr, 2, $tmp0*2,$chrz[$sndx]{'tlcl'} x $tmp0);}
        else         { substr($lclr, 2, $tmp0*2,$chrz[$sndx]{'txcl'} x $tmp0);}
        if($widt > 7) {                   # only if wide enough
          $tmp1 = $widt - ($tmp0 + 1);    # rite edge text
          substr($ltxt, $tmp1  , $tmp0  , $_ );
          if     ($chan) {
            substr($lclr, $tmp1*2, $tmp0*2, $chrz[$sndx]{'trcl'} x $tmp0);
          } elsif($isst) {
            substr($lclr, $tmp1*2, $tmp0*2, $chrz[$sndx]{'tlcl'} x $tmp0);
          } else { 
            substr($lclr, $tmp1*2, $tmp0*2, $chrz[$sndx]{'txcl'} x $tmp0);
          }
          if($widt > 15) {                # centered stuff only if wide enough
            $_ = $sopt; $_ = sprintf("%02d", $setz[$chan]{$_}); $tmp0 = 0;
            if(($valz{'mute'}) || 
               ($valz{'only'} && $_ ne $optz[$valz{'wich'}])) {
              $_ = $chrz[$sndx]{'mutd'};  # supplant temp muted text
              $tmp0 = 1; # align muted with percent && record
            }
            $tmp0 += int((($widt - 3) - length($_))/2.0); 
            substr($ltxt, $tmp0  , length($_), $_);
            if($_ eq $chrz[$sndx]{'mutd'}) { 
              substr($lclr, $tmp0*2,   length($_)*2, 
                $chrz[$sndx]{'pccl'} x length($_));
              $tmp0 = -1; # flag the following record section not to draw
            }
            if($valz{'recf'}) {
              $_ = $sopt; # restore original text from before percent or muted
              if($tmp0 != -1 && $recp{$_}) {# for input devices
                $tmp0 += 4;                 # rite of cntr Record flag
                $tmp1 = $chrz[$sndx]{'rplc'} . $recp{$_};
                substr($ltxt, $tmp0,   3  , $tmp1 . $chrz[$sndx]{'rprc'});
                $tmp1 = $chrz[$sndx]{'rpcl'} . $chrz[$sndx]{'rpcc'};
                substr($lclr, $tmp0*2, 3*2, $tmp1 . $chrz[$sndx]{'rpcr'});
                if($recp{$_} eq 'R') {      # special bright R on unselected
                  $tmp1 = uc(substr($chrz[$sndx]{'rpcc'}, 0, 1));
                  substr($lclr, ($tmp0 + 1)*2, 1, $tmp1);
                }
              }
            }
          }
        }
        $_ = $sopt; # restore original optn in case stereo clobbered it
        $tmp0 = int(($setz[$chan]{$_}/100.0) * $widt); # position slider bar
        $tmp1 = length($chrz[$sndx]{'sldc'});
        substr($ltxt, $tmp0  , $tmp1  , $chrz[$sndx]{'sldc'});
        substr($lclr, $tmp0*2, $tmp1*2, $chrz[$sndx]{'sdcl'} x $tmp1);
        $simp->Text('push', $ltxt);
        $simp->Colr('push', $lclr);
      }
    }
  }
  $simp->Draw();
}

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

sub SaveSetz { 
  foreach(keys(%{$setz[0]})) { 
    my $parm = $setz[0]{$_};
    if($chgr eq 'mixr') { # Audio::Mixer stuff
      foreach(Audio::Mixer::get_mixer_params()) {
        Audio::Mixer::set_cval($_, $setz[0]{$_}, $setz[1]{$_});
      }
    } else {
      if     ($chgr eq 'ximp') {
        if($valz{'ster'} && $sdvz{$_}) {
          $parm .= " $setz[1]{$_}";
          $parm .= " $recp{$_}" if($valz{'recf'} && $recp{$_});
        }
      } elsif($chgr eq 'aumx') {
      }
      `$chgr $_ $parm`;
    }
  } 
}

sub LoadSetz {
  my $curr; my @linz = ();
  if($chgr eq 'mixr') { # Audio::Mixer stuff
    foreach(Audio::Mixer::get_mixer_params()) {
      ($setz[0]{$_}, $setz[1]{$_}) = Audio::Mixer::get_cval($_);
      $curr = Audio::Mixer::get_param_val($_);
      $sdvz{$_} = 0;
      $sdvz{$_} = 1 if($curr & 0x10000);
      $recp{$_} = ''; # don't know how to test recordability here
    }
  } else {
    if   ($chgr eq 'ximp')  { $curr = `$chgr all`; }
    elsif($chgr eq 'aumix') { $curr = `$chgr -q`; }
    @linz = split /\n/, $curr;
    foreach(@linz) {
#  `aumix -q`     #    `ximp -a`
# vol 38, 38      #      vol: 38% / 38%
# speaker 66, 66  #  speaker:    66%
# line 38, 38, P  #     line: 38% / 38% - P
# mic 0, 0, P     #      mic:    00%    - P
      if(/^\s*(\w+):?\s+(\d+)((,|%\s+\/)\s+(\d+))?((,|%\s+-)\s+([RP]))?/i) {
        $sdvz{$1}    = 0;
        $recp{$1}    = '';
        $setz[0]{$1} = $setz[1]{$1} = $2;
        if(defined($5)) {
          $sdvz{$1}    = 1;
          $setz[1]{$1} = $5;
          unless($valz{'ster'}) { 
            $setz[0]{$1} = $setz[1]{$1} = int( ($2 + $5) / 2.0 );
          }
        }
        $recp{$1} = $8 if(defined($8));
      }
    }
  }
}

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

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

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

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

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

sub ToglTitl { $valz{'titl'} ^= 1; ChngBarz(); }

sub ToglInfo { $valz{'info'} ^= 1; ChngBarz(); }

sub ToglRecf { $valz{'recf'} ^= 1; ChngBarz(); }

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

sub ToglSter { $valz{'ster'} ^= 1; $valz{'ltrt'} = 0; ChngBarz(); }

sub AlllBarz {
  foreach(@optz) {
    $setz[0]{$_} = $setz[1]{$_} = $setz[$valz{'ltrt'}]{$optz[$valz{'wich'}]};
  }
}

sub AsinReco {
  if($recp{$optz[$valz{'wich'}]}) {
    if   ($chgr eq 'mixr') { Audio::Mixer::set_source($optz[$valz{'wich'}]); }
    elsif($chgr eq 'ximp') { `$chgr :r$optz[$valz{'wich'}]`; }
    ChngBarz();
  }
}

sub AsinChan {
  my $chan = shift || return;
  for($valz{'wich'} = 0; $valz{'wich'} < @optz; $valz{'wich'}++) {
    last if($optz[$valz{'wich'}] eq $chan);
  }
}

sub ShowInfo {
  $simp->Mesg('type' => 'info',
" $name v$mjvr.$mnvr.$ptvr - by $auth
 
$name was inspired by aumix, mmix, && mmixer
 
 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 {
  $simp->Mesg('type' => 'help',
" $name v$mjvr.$mnvr.$ptvr - by $auth

                        Global Keys:                                
  h         - displays this Help screen                             
  b         - toggles Big mode (with all channel options)           
  S         - toggles Stereo mode (for all channels that support it)
  m         - toggles Muting                                        
  o         - toggles current channel as Only audible channel       
  a         - toggles locked slider bar across All channels         
  t         - toggles display of Title bar                          
  I         - toggles display of soundcard device Info              
  s         - Save all current channel values to ~/.${name}rc       
  l         - Load ~/.${name}rc into current channel values         
  r         - set current channel to Record mode if valid input     
  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 * 11)% of channel (must shift for jump up) 
 Home/End   - same as '0' && Shift-'9'                              

                        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
$simp = Curses::Simp->new('_flagbkgr' => 1, '_flagaudr' => 0);
DrawBarz();
while(!defined($keey) || ($keey !~ /^[xq]$/i && ord($keey) != 27)) {
  $keey = $simp->GetK($dela);
  LoadSetz();
  $oidl = $idle;
  if(defined($keey)) {
    if   (   $keey     eq ' ' ||
          lc($keey)    eq 'a') { ToglAlll(); }
    elsif(lc($keey)    eq 'b') { ToglMode(); }
    elsif(lc($keey)    eq 'm') { ToglMute(); }
    elsif(lc($keey)    eq 'o') { ToglOnly(); }
    elsif(lc($keey)    eq 't') { ToglTitl(); }
    elsif(   $keey     eq 'I') { ToglInfo(); }
    elsif(   $keey     eq 'R') { ToglRecf(); }
    elsif(   $keey     eq 'S') { ToglSter(); }
    elsif(   $keey     eq 's') { SaveValz(); }
    elsif(   $keey     eq 'l') { LoadValz(); }
    elsif(   $keey     eq 'L') { # L adds 'line' temporarily to small options
      $valz{'alll'} = 0; ChngBarz(); 
      $optz[@dopt] = 'line' unless($valz{'mode'});
      AsinChan('line'); }
    elsif(   $keey     eq 'i') { ShowInfo(); }
    elsif(   $keey     eq 'r') { AsinReco(); }
    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[$valz{'ltrt'}]{$optz[$valz{'wich'}]} > int($keey * 11)) {
        $setz[$valz{'ltrt'}]{$optz[$valz{'wich'}]} = int($keey * 11);
        ChngBarz();
      }
    } elsif(   $keey     =~ /^[!@#\$%^&*()]$/) { # handle shifted number keys
      $setz[$valz{'ltrt'}]{$optz[$valz{'wich'}]} = int($slut{$keey} * 11);
      ChngBarz();
    } elsif($keey eq 'KEY_LEFT')  { 
      $setz[$valz{'ltrt'}]{$optz[$valz{'wich'}]}-- if $setz[$valz{'ltrt'}]{$optz[$valz{'wich'}]};
      ChngBarz();
    } elsif($keey eq 'KEY_RIGHT') { 
      $setz[$valz{'ltrt'}]{$optz[$valz{'wich'}]}++ if $setz[$valz{'ltrt'}]{$optz[$valz{'wich'}]} < 99;
      ChngBarz();
    } elsif($keey eq 'KEY_HOME') { 
      $setz[$valz{'ltrt'}]{$optz[$valz{'wich'}]} = 0;
      ChngBarz();
    } elsif($keey eq 'KEY_END') { # END is dangerous to ears && speakers
      my $sure = $simp->Mesg(
        'titl' => '!*WARNING*!  END can be loud!  Are you SURE?',
        'pres' => 'Yes/No');
      if($sure =~ /y/i) {
        $setz[$valz{'ltrt'}]{$optz[$valz{'wich'}]} = 99;
        ChngBarz();
      }
    } elsif($keey eq 'KEY_UP' || lc($keey) eq 'k') { 
      if($valz{'ster'} && $sdvz{$optz[$valz{'wich'}]} && $valz{'ltrt'}) {
        $valz{'ltrt'} = 0;
      } else {
        $valz{'wich'} = @optz if $valz{'wich'} == 0; $valz{'wich'}--;
        $valz{'ltrt'} = 1 if($valz{'ster'} && $sdvz{$optz[$valz{'wich'}]});
      }
    } elsif($keey eq 'KEY_DOWN' || lc($keey) eq 'j')  { 
      if($valz{'ster'} && $sdvz{$optz[$valz{'wich'}]} && !$valz{'ltrt'}) {
        $valz{'ltrt'} = 1;
      } else {
        $valz{'wich'}++; $valz{'wich'} = 0 if $valz{'wich'} == @optz;
        $valz{'ltrt'} = 0;
      }
    } elsif(lc($keey) eq 'h' || lc($keey) eq '?' || $keey eq 'KEY_F1') {
      ShowHelp();
    } elsif($keey eq '-1') {
      if($idle < 32) { $dela = 0.1; }
      else           { $dela = 2;   }
      $idle += $dela;
    }
  }
  $idle = 0 if($idle == $oidl); # reset idle timer if something happened
  DrawBarz();
}
Audio::Mixer::close_mixer() if($chgr eq 'mixr');
