#include "mmix.h"

#define MIXER_MAX    99 /* how much devices are probed */

#define MIX_VOL_MAX 100 /* the volumes are from 0 to 100, */
#define MIX_VOL_MIN   0 /* but 101 is also possible - hm?! */

static int mixer_tryopen(unsigned char *dev);
static int mixer_initdev(struct mmix *m);

static int mixer_tryopen(unsigned char *dev) {
 int fd=-1, dummy;
 if ((fd=open((char*)dev, O_RDWR|O_NONBLOCK))<0) return -1;
 if (ioctl(fd, SOUND_MIXER_READ_DEVMASK, &dummy)<0) return -1;
 return fd; /* reading mixer was okay */
}

static int mixer_initdev(struct mmix *m) {
 struct mixer_info mi;
 register int i;
 if (ioctl(m->fd, SOUND_MIXER_READ_DEVMASK, &m->devmask)<0) return -1;
 for (i=0,m->nrdevs=0;i<SOUND_MIXER_NRDEVICES;i++)
  if ((m->devmask &(1<<i)) == (1<<i)) {
   m->v[0][i].m=R_OFF; /* default is unmute */
   m->cfg_okay=FALSE;  /* no config was read */
   m->nrdevs++;
  }
 if (ioctl(m->fd, SOUND_MIXER_READ_RECMASK, &m->recmask)<0) return -1;
 if (ioctl(m->fd, SOUND_MIXER_READ_STEREODEVS, &m->stereo)<0) return -1;
 if (ioctl(m->fd, SOUND_MIXER_INFO, &mi)<0) return -1;
 m->mname=(char*)str_concat(mi.name, " (", mi.id, ")", NULL);
 return 0; /* dev seems okay */
}

/* global */
struct mmix *mixer_init(void) {
 char *devfile=mallocn(char, 20), *devstart;
 int i=0, mixer[MIXER_MAX+1], len;
 struct mmix *m,*f;

 if (is_devfs()) {
  devstart="/dev/sound/mixer";
 } else {
  devstart="/dev/mixer";
 }
 m=f=mallocn(struct mmix, 1);
 if ((mixer[mmix_found]=mixer_tryopen((unsigned char *)devstart))>0) mmix_found++;
 m->fd=mixer[i]; /* -> just .../mixer */
 str_cpy((unsigned char *)m->fname, (unsigned char *)devstart);
 /* now, we try: .../mixer1, .../mixer2, ... */
 for (len=str_len((unsigned char *)devstart),i=1; i<MIXER_MAX; i++) {
  xsnprintf(devfile, 19, "%s%d", devstart, i);
  if ((mixer[mmix_found]=mixer_tryopen((unsigned char *)devfile))>0) {
   m->next=mallocn(struct mmix,1); m=m->next;
   m->fd=mixer[i];
   str_cpy((unsigned char *)m->fname, (unsigned char *)devfile);
   m->next=NULL;
   mmix_found++;
  } else break; /* we should have have all mixers now */
 }
 xfree(devfile);
 if (!mmix_found) mmix_error(ERR_DEV);
 /* init the mixer, we have found */
 for (m=f;m!=NULL;m=m->next) {
  mixer_initdev(&m[0]);  mixer_read(&m[0]);
 }
 return f; /* return the beginning */
}

/* returns the real mixernumber, not my internal fuck */
int mixer_realres(struct mmix *m, int searched) {
 register int i,f;
 for (i=0,f=0;i<SOUND_MIXER_NRDEVICES;i++)
 if ((m->devmask & (1<<i)) == (1<<i)) {
  if (f==(searched-1)) return i; f++;
 }
 return 0; /* should never happen */
}

/* check for external updates to the given mixer */
void mixer_read(struct mmix *m) {
 register int i;

 /* read recording sources only once */
 (void)ioctl(m->fd, SOUND_MIXER_READ_RECSRC, &m->recsrc);

 for (i=0;i<SOUND_MIXER_NRDEVICES;i++) {
  int mask=(1<<i);

  /* if muted, skip it */
  if (m->v[0][i].m==R_ON) break;

  /* recsrc stuff -> vol.r */
  if (m->recmask &mask) {
   if ((m->recsrc &mask) == mask) {
    m->v[0][i].r=R_ON;
   } else {
    m->v[0][i].r=R_OFF;
   }
  } else {
   m->v[0][i].r=R_NO;
  }

  if (m->v[0][i].r!=m->v[c_cfg][i].r) {
   m->v[c_cfg][i].r=m->v[0][i].r;
   set(U_MIXVOL);
  } /* end recsrc */

  if ((m->devmask &mask) == mask) {
   int tmp;

   /* volume stuff -> vol.left and vol.right */
   if (ioctl(m->fd, MIXER_READ(i), &tmp)<0) tmp=0;
   m->v[0][i].right = (tmp >> 8);
   m->v[0][i].left  = (tmp & 0xff); /* read to temp */
   if ((m->v[0][i].left  != m->v[c_cfg][i].left) /* compare */
    || (m->v[0][i].right != m->v[c_cfg][i].right)) {
    m->v[c_cfg][i].left=m->v[0][i].left;   /* set left */
    m->v[c_cfg][i].right=m->v[0][i].right; /* set right */
    set(U_MIXVOL);
   } /* end of left/right volume */

   /* stereo stuff -> vol.s */
   if ((m->stereo  &mask) == mask) {
    m->v[0][i].s=R_ON;
   } else {
    m->v[0][i].s=R_OFF;
   }
   if (m->v[0][i].s != m->v[c_cfg][i].s) {
    m->v[c_cfg][i].s=m->v[0][i].s;
    set(U_MIXVOL);
   } /* end stereo */
  } /* end devmask */
 } /* for i<SOUND_MIXER_NRDEVICES */
}


void mixer_write(struct mmix *m, int res, int left, int right) {
 /* no stereo device */
 if ((m->stereo & (1<<res)) != (1<<res)) { right=0; }

 /* set new */
 m->v[0][res].left+=left;   /* left */
 if (m->v[0][res].left  <  MIX_VOL_MIN) m->v[0][res].left=MIX_VOL_MIN;
 if (m->v[0][res].left  >= MIX_VOL_MAX) m->v[0][res].left=MIX_VOL_MAX;
 m->v[0][res].right+=right; /* right */
 if (m->v[0][res].right <  MIX_VOL_MIN) m->v[0][res].right=MIX_VOL_MIN;
 if (m->v[0][res].right >= MIX_VOL_MAX) m->v[0][res].right=MIX_VOL_MAX;
 mixer_write_it(&m[0], res, m->v[0][res].right<<8, m->v[0][res].left);
}

void mixer_write_it(struct mmix *m, int res, int l, int r) {
 int new=l+r;
 if (m->v[0][res].m == R_ON) new=0; /* muted resource */
 (void)ioctl(m->fd, MIXER_WRITE(res), &new);
 set(U_MIXVOL);
}

void mixer_write_res(struct mmix *m, int res) {
 if (m->v[0][r_res].r==R_ON) {
  m->v[0][r_res].r=R_OFF;
  m->recsrc &= ~(1<<res);
 } else { 
  m->v[0][r_res].r=R_ON;
  m->recsrc |= (1<<res);
 }
 (void)ioctl(m->fd, SOUND_MIXER_WRITE_RECSRC, &m->recsrc);
}
