/*
 * mixer control program
 *
 * woody & patb@corel.ca
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/errno.h>
#include <fcntl.h>
#include <linux/soundcard.h>

#define	VNC_MUTE_INTERNAL_SPKR	0x01	//the sw mute on/off control bit
#define	VNC_MUTE_LINE_OUT	0x40	//the sw mute of line-out bit
#define VNC_PHONE_DETECT        0x20    // the phone is offhook
#define VNC_HANDSET_DETECT      0x10    // a handset is plugged in
#define	VNC_NO_AUTOSELECT	0x80	//software in total control of handset!

#define MIXER_PRIVATE3_READ    0x53570001

int mixer_fd = -1;
unsigned supported;
int source;
char **p;			/* parameters */

#define ALL_OPT "-all"
#define MASTER_OPT "-master"
#define DSP_OPT "-dsp"
#define MIC_OPT "-mic"
#define FM_OPT "-fm"
#define SOURCE_OPT "-source"
#define LINEOUT_OPT "-lineout"
#define PHONE_OPT "-phone"	//LINE1 is our analog phone
//#define SPKR_OPT "-spkr"
#define RECLEV_OPT "-reclev"
#define HELP_OPT "-help"
#define RECMON_OPT "-recmon"
#define PRIVATE_OPT "-private"
#define CAPS_OPT "-caps"
#define REGS_OPT "-regs"

#define NOT_CMP 0
#define GET -1
#define SET 1

#define LEFT  0x0001
#define RIGHT 0x0100
#define LEFT_MASK 0x00ff
#define RIGHT_MASK 0xff00

/* fetch english titles from soundcard.h */
char* snd_names[] = SOUND_DEVICE_LABELS;

int all_chk;

/* look for patterns like:
 -param
 -param=nn
 -param=(nn,mm)

 return GET (-1) for the first case
 */
int
str_cmp (char *pattern, char *s)
{
  int i, j, k, l;

  all_chk = all_chk ^ 1;
  if (!all_chk)
    if (str_cmp (ALL_OPT, s))
      return GET;

  for (i = 0; (pattern[i]) && (s[i]) && (pattern[i] == s[i]); i++);

  if (pattern[i])
    return 0;			/* not this one */

  if (!s[i])
    return GET;

  if (!s[i + 1])
    return GET;

  if (s[i + 1] == '(')		/* we got stereo */
    {
      for (k = i + 1; s[k] && (s[k] != ','); k++);

      if (!s[k])
	return GET;

      s[k] = 0;

      for (l = k + 1; s[k] && (s[k] != ')'); k++);

      s[k] = 0;
      j = atoi (s + i + 2);
      l = atoi (s + k + 1);

      return LEFT * j + RIGHT * l + SET;
    }
  else
    {
      j = atoi (s + i + 1);
      return (LEFT + RIGHT) * j + SET;
    }
}

void
set_source (int id)
{
    int source;
    if (ioctl (mixer_fd, SOUND_MIXER_READ_RECSRC, &source) == -1)
        perror ("Error reading mixer recording source");
    printf("set source: old source %x, new source %x\n",source,id);

    source = 1<<id;
    if (ioctl (mixer_fd, SOUND_MIXER_WRITE_RECSRC, &id) < 0)
        perror ("Mixer.set_source()");
}


int
check_source (char *s)
{
    int k;
    unsigned int source;

    k = str_cmp (SOURCE_OPT, s);

    switch (k)

    {
    case GET:
        source = 0;
        if (ioctl (mixer_fd, SOUND_MIXER_READ_RECSRC, &source) == -1)
            perror ("Error reading mixer recording source");

        printf ("Record input:\t%s\n",
                source & SOUND_MASK_PHONEIN ? "PhoneIn" :
                (source & SOUND_MASK_LINE ? "LineIn" :
                 (source & SOUND_MASK_LINE1 ? "Handset" :
                  (source & SOUND_MASK_MIC ? "Internal mic" :
                   "not connected"))));
        return 1;

    case NOT_CMP:
      return 0;

    default:
        set_source ((k - 1) & 0x0F);	//convert from "stereo"+1 to a number

        return 1;
    }

}

void
set_volume (int mixer_id, int volume)
{
  if (ioctl (mixer_fd, MIXER_WRITE (mixer_id), &volume) < 0)
    perror ("Mixer.set_mixer()");
}

int
get_volume (int mixer_id)
{
  int volume;

  if (ioctl (mixer_fd, MIXER_READ (mixer_id), &volume) < 0)
    {
      perror ("Mixer.get_volume()");
      return 0;
    }
  return volume;
}

char *
sget_volume (int mixer_id)
{
  int v;
  char *s = calloc (100, sizeof (char));

  if (s == NULL)
    {
      perror ("Mixer.sget_volume()");
      return "ERROR";
    }

  v = get_volume (mixer_id);
  if ((v & LEFT_MASK) == (v & RIGHT_MASK) >> 8)
    sprintf (s, "\t%d", (v & LEFT_MASK));
  else
    sprintf (s, "\t(L=%d R=%d)", (v & LEFT_MASK), (v & RIGHT_MASK) >> 8);
  return s;
}

int
check_master (char *s)
{
  int k;

  if ((supported & SOUND_MASK_VOLUME) == 0)
    return 0;
  k = str_cmp (MASTER_OPT, s);

  switch (k)
    {
    case GET:
      printf ("Master volume %s (to speaker, handset and phone)\n", sget_volume (SOUND_MIXER_VOLUME));
      return 1;
    case NOT_CMP:
      return 0;
    default:
      set_volume (SOUND_MIXER_VOLUME, k - 1);
      return 1;
    }
}

int
check_mic (char *s)
{
  int k;

  if ((supported & SOUND_MASK_MIC) == 0)
    return 0;

  k = str_cmp (MIC_OPT, s);
  switch (k)
    {
    case GET:
      printf ("Mic/handset %s\n", sget_volume (SOUND_MIXER_MIC));
      return 1;
    case NOT_CMP:
      return 0;
    default:
      set_volume (SOUND_MIXER_MIC, k - 1);
      return 1;
    }
}

int
check_reclev (char *s)
{
  int k;
  if ((supported & SOUND_MASK_RECLEV) == 0)
    return 0;
  k = str_cmp (RECLEV_OPT, s);
  switch (k)
    {
    case GET:
      printf ("Record volume %s\n", sget_volume (SOUND_MIXER_RECLEV));
      return 1;
    case NOT_CMP:
      return 0;
    default:
      set_volume (SOUND_MIXER_RECLEV, k - 1);
      return 1;
    }
}

int
check_dsp (char *s)
{
  int k;
  if ((supported & SOUND_MASK_PCM) == 0)
    return 0;
  k = str_cmp (DSP_OPT, s);
  switch (k)
    {
    case GET:
      printf ("DSP volume %s\n", sget_volume (SOUND_MIXER_PCM));
      return 1;
    case NOT_CMP:
      return 0;
    default:
      set_volume (SOUND_MIXER_PCM, k - 1);
      return 1;
    }
}

int
check_fm (char *s)
{
  int k;
  if ((supported & SOUND_MASK_SYNTH) == 0)
    return 0;
  k = str_cmp (FM_OPT, s);
  switch (k)
    {
    case GET:
      printf ("FM volume %s\n", sget_volume (SOUND_MIXER_SYNTH));
      return 1;
    case NOT_CMP:
      return 0;
    default:
      set_volume (SOUND_MIXER_SYNTH, k - 1);
      return 1;
    }

}

int
check_recmon (char *s)
{
  int k;
  if ((supported & SOUND_MASK_IMIX) == 0)
    return 0;
  k = str_cmp (RECMON_OPT, s);
  switch (k)
    {
    case GET:
      printf ("RecMon volume %s\n", sget_volume (SOUND_MIXER_IMIX));
      return 1;
    case NOT_CMP:
      return 0;
    default:
      set_volume (SOUND_MIXER_IMIX, k - 1);
      return 1;
    }
}


int
check_private (char *s)
{
    int k, private;
    
    k = str_cmp (PRIVATE_OPT, s);
    switch (k)
    {
    case GET:

        private = 0;
        if (ioctl (mixer_fd, MIXER_READ (SOUND_MIXER_PRIVATE4), &private) < 0)
        {
            printf("Error reading the private mixer control...\n");
            return 0;
        }
        printf ("Private [0x%X]\tIntSpkr %s\tLineOut %s\n\t\tPhone %s\tHandset %s\tRecSource %s\n",
                private,
                private & VNC_MUTE_INTERNAL_SPKR ? "OFF" : "ON",
                private & VNC_MUTE_LINE_OUT      ? "OFF" : "ON",
                private & VNC_PHONE_DETECT       ? "DETECTED" : "ABSENT",
                private & VNC_HANDSET_DETECT     ? "DETECTED" : "ABSENT",
                private & VNC_NO_AUTOSELECT      ? "SW" : "AUTO");

        return 1;
    case NOT_CMP:
        return 0;

    default:
        private = (k - 1) & 0xFF;
        printf ("Setting PRIVATE to %X.\n", private);
        if (ioctl (mixer_fd, MIXER_WRITE (SOUND_MIXER_PRIVATE1), &private) < 0)
        {
            printf("Error setting the private mixer control...\n");
            return(0);
        }
        return 1;
    }
}

int
check_caps(char* s)
{
    int k;
    unsigned int cap;
    
    k = str_cmp(CAPS_OPT, s);
    switch(k) {
    case NOT_CMP:
    case SET:
        return 0;
    }
    /* must be GET */
    printf("Capabilities:\t");
    ioctl(mixer_fd, SOUND_MIXER_READ_CAPS, &cap);
    if (cap & 1)
        printf("Record from only 1 source at a time\n");
    else
        printf("\n");

    ioctl(mixer_fd, SOUND_MIXER_READ_DEVMASK, &cap);
    printf("\t\tChannels: ");
    for (k=1; k<32; k++) {
        if ((1<<k) & cap)
            printf("\t%s",snd_names[k]);
        if (k==8 && (cap & 0xffffff00)) printf("\n\t\t\t");
    }

    ioctl(mixer_fd, SOUND_MIXER_READ_STEREODEVS, &cap);
    printf("\n\t\tStereo on: ");
    for (k=1; k<32; k++) {
        if ((1<<k) & cap)
            printf("\t%s",snd_names[k]);
    }

    ioctl(mixer_fd, SOUND_MIXER_READ_RECMASK, &cap);
    printf("\n\t\tCan record on: ");
    for (k=1; k<32; k++) {
        if ((1<<k) & cap)
            printf("\t%s",snd_names[k]);
    }
    printf("\n");
}

/* convert a number from hex to spread out hex, representing
 the binary. For example, 0x23 becomes 0x00100011 */
unsigned int toBin(unsigned int input)
{
    int i,ret;
    for (ret=0, i=1; input; i<<=4, input>>=1)
        if (input & 1)
            ret |= i;
    return ret;
}

unsigned int mask[] = {0,1,3,7,15,31,63,127,255,
0x1ff, 0x3ff, 0x7ff, 0xfff, 0x1fff, 0x3fff, 0x7fff, 0xffff};

/* dump the mixer register set */
int
check_regs(char* s)
{
    unsigned int regs[15];
    int* p = regs;
    int i;

    i = str_cmp(REGS_OPT, s);
    if (i == NOT_CMP)
        return 0;
    
    regs[14] = MIXER_PRIVATE3_READ;
    if (ioctl(mixer_fd, MIXER_WRITE( SOUND_MIXER_PRIVATE3 ), &p) < 0)
    {
        printf("Error getting registers\n");
        return 0;
    }
#define EXBITS(a,s,n) (( (a) >> s ) & mask[n])
#define PERCENT(a,s,n) (((( (a) >> s ) & mask[n]) * 100) / mask[n])
    printf("WaveArtist mixer registers:\n"
           "Raw\tReg#\tline-to-mixer\taux1-to-mixer\t~mute\n");
    printf("%4.4x\t%1.1xL\t%5.5x(%d%%)\t%5.5x(%d%%)\t%1.1x (left)\n",
           regs[0],
           EXBITS(regs[0],12,4),
           toBin(EXBITS(regs[0],6,5)),
           PERCENT(regs[0],6,5),
           toBin(EXBITS(regs[0],1,5)),
           PERCENT(regs[0],1,5),
           EXBITS(regs[0],0,1));
    printf("%4.4x\t%1.1xR\t%5.5x(%d%%)\t%5.5x(%d%%)\t%1.1x (right)\n",
           regs[4],
           EXBITS(regs[4],12,4),
           toBin(EXBITS(regs[4],6,5)),
           PERCENT(regs[4],6,5),
           toBin(EXBITS(regs[4],1,5)),
           PERCENT(regs[4],1,5),
           EXBITS(regs[4],0,1));
                 
    printf("\n\t\taux2-to-mixer\tcrossmic-mixer\t~mute\n");
    printf("%4.4x\t%1.1xL\t%5.5x(%d%%)\t%5.5x(%d%%)\t%1.1x (mono)\n",
           regs[1],
           EXBITS(regs[1],12,4),
           toBin(EXBITS(regs[1],6,5)),
           PERCENT(regs[1],6,5),
           toBin(EXBITS(regs[1],1,5)),
           PERCENT(regs[1],1,5),
           EXBITS(regs[1],0,1));
    printf("%4.4x\t%1.1xR\t%5.5x(%d%%)\t%5.5x(%d%%)\n",
           regs[5],
           EXBITS(regs[5],12,4),
           toBin(EXBITS(regs[5],6,5)),
           PERCENT(regs[5],6,5),
           toBin(EXBITS(regs[5],1,5)),
           PERCENT(regs[5],1,5),
           EXBITS(regs[5],0,1));
    
    printf("\n\t\tmic-to-mixer\tmic gain\tmixer gain\n");
    printf("%4.4x\t%1.1xL\t%5.5x(%d%%)\t%2.2x(%d%%)\t\t%3.3x(%d%%)\n",
           regs[2],
           EXBITS(regs[2],12,4),
           toBin(EXBITS(regs[2],6,5)),
           PERCENT(regs[2],6,5),
           toBin(EXBITS(regs[2],4,2)),
           PERCENT(regs[2],4,2),
           toBin(EXBITS(regs[2],1,3)),
           PERCENT(regs[2],1,3));
    printf("%4.4x\t%1.1xR\t%5.5x(%d%%)\t%2.2x(%d%%)\t\t%3.3x(%d%%)\n",
           regs[6],
           EXBITS(regs[6],12,4),
           toBin(EXBITS(regs[6],6,5)),
           PERCENT(regs[6],6,5),
           toBin(EXBITS(regs[6],4,2)),
           PERCENT(regs[6],4,2),
           toBin(EXBITS(regs[6],1,3)),
           PERCENT(regs[6],1,3));

    printf("\n\t\tmixer select\trecord level\n");
    printf("%4.4x\t%1.1xL\t%7.7x\t\t%4.4x(%d%%)\n",
           regs[3],
           EXBITS(regs[3],12,4),
           toBin(EXBITS(regs[3],4,7)),
           toBin(EXBITS(regs[3],0,4)),
           PERCENT(regs[3],0,4));
    printf("%4.4x\t%1.1xR\t%7.7x\t\t%4.4x(%d%%)\n",
           regs[7],
           EXBITS(regs[7],12,4),
           toBin(EXBITS(regs[7],4,7)),
           toBin(EXBITS(regs[7],0,4)),
           PERCENT(regs[7],0,4));
    
    printf("\n\t\tmono-to-mixer\tright-in-sel\tleft-in-sel\n");
    printf("%4.4x\t%1.1X\t%5.5x\t\t%3.3x\t\t%3.3x\n",
           regs[8],
           EXBITS(regs[8],12,4),
           toBin(EXBITS(regs[8],6,5)),
           toBin(EXBITS(regs[8],3,3)),
           toBin(EXBITS(regs[8],0,3)));
    printf("\t\textra, mostly test bits\n");
    printf("%4.4x\t%1.1X\n",regs[9],EXBITS(regs[9],12,4));

    printf("\n\t\tdsp output gain\n");
    printf("%4.4x\t%X\tL %4.4x(%d%%)\n",
           regs[10],
           11,
           EXBITS(regs[10],0,15),
           PERCENT(regs[10],0,15));
    printf("%4.4x\t%X\tR %4.4x(%d%%)\n",
           regs[11],
           12,
           EXBITS(regs[11],0,15),
           PERCENT(regs[11],0,15));
    printf("\t\tfm output gain\n");
    printf("%4.4x\t%X\tL %4.4x(%d%%)\n",
           regs[12],
           13,
           EXBITS(regs[12],0,15),
           PERCENT(regs[12],0,15));
    printf("%4.4x\t%X\tR %4.4x(%d%%)\n",
           regs[13],
           14,
           EXBITS(regs[13],0,15),
           PERCENT(regs[13],0,15));
    
    return 1;
        
}

#if 0
int
check_line_out(char *s)
{
  int k;
  if ((supported & SOUND_MASK_VOLUME) == 0)
    return 0;
  k = str_cmp (LINEOUT_OPT, s);
  switch (k)
    {
    case GET:
      printf ("LINE OUT vol %s\n", sget_volume (SOUND_MIXER_VOLUME));
      return 1;
    case NOT_CMP:
      return 0;
    default:
      set_volume (SOUND_MIXER_VOLUME, k - 1);
      return 1;
    }
}

int
check_phone (char *s)
{
  int k;
  if ((supported & SOUND_MASK_LINE1) == 0)
    return 0;
  k = str_cmp (PHONE_OPT, s);
  switch (k)
    {
    case GET:
      printf ("PHONE recmon volume %s\n", sget_volume (SOUND_MIXER_LINE1));
      return 1;
    case NOT_CMP:
      return 0;
    default:
      set_volume (SOUND_MIXER_LINE1, k - 1);
      return 1;
    }
}

int
check_spkr (char *s)
{
  int k;
  if ((supported & SOUND_MASK_SPEAKER) == 0)
    return 0;
  k = str_cmp (SPKR_OPT, s);
  switch (k)
    {
    case GET:
      printf ("SPKR (and handset and telephone earpiece) volume %s\n",
	      sget_volume (SOUND_MIXER_SPEAKER));
      return 1;
    case NOT_CMP:
      return 0;
    default:
      set_volume (SOUND_MIXER_SPEAKER, k - 1);
      return 1;
    }
}
#endif

void 
check_all (char *s)
{
    check_master (s);
//    check_line_out(s);
    check_dsp (s);
    check_fm (s);
    check_mic (s);
    check_recmon (s);
    check_reclev (s);
    check_source (s);
    check_private (s);
    check_caps(s);
}

void 
usage (char *s)
{
  printf ("SYNOPSIS\n");
  printf ("\t%s [options]\n\n", s);
  printf ("OPTIONS\n");
  printf ("\t-master=n\tset master volume to n (speaker, handset & phone)\n");
//  printf ("\t-lineout=n\tset line out volume to n\n");
  printf ("\t-dsp=n\t\tset dsp synth volume to n\n");
  printf ("\t-fm=n\t\tset fm synth volume to n\n");
  printf ("\t-source={6,7,14,20} set source to {Line, Mic, Handset, Phone}\n");
  printf ("\t-mic=n\t\tset mic preamp to n\n");
  printf ("\t-reclev=n\tset recording level to n\n");
  printf ("\t-recmon=n\tset recording monitor level to n\n");
  printf ("\t-caps\t\tshow capabilities\n");
  printf ("\t-regs\t\tshow full mixer registers\n");
  printf ("\t-help\t\tshow this screen\n");
  printf ("\tuse volume=(left,right) for stereo setting.\n");
}

int 
main (int argc, char *argv[])
{
  int k;

  all_chk = 1;

  mixer_fd = open ("/dev/mixer", O_RDWR);
  if (mixer_fd < 0)
    {
      perror ("Mixer.main()");
      exit (-1);
    }

  if (ioctl (mixer_fd, SOUND_MIXER_READ_DEVMASK, &supported) < 0)
    supported = 0xffff;

  if (argc == 1)
    {
      check_all ("-all");
      printf ("\n`%s -help\' for more information.\n", argv[0]);
      close (mixer_fd);
      exit (0);
    }
  if (strcmp (HELP_OPT, argv[1]) == 0)
    {
      usage (argv[0]);
      close (mixer_fd);
      exit (0);
    }
  for (k = 1; k < argc; k++) {
      check_all (argv[k]);
      /* only dump regs if expicitly requested */
      check_regs(argv[k]);
  }
  close (mixer_fd);
  exit (0);
}

