/*
 * GQmpeg
 * (C)1998, 1999 John Ellis
 *
 * Author: John Ellis
 *
 * This software is released under the GNU General Public License.
 * Please read the included file COPYING for more information.
 * This software comes with no warranty of any kind, use at you own risk!
 */

/* current systems with supported mixer functions:
 *   Linux
 *   the BSDs (FreeBSD, NetBSD)
 *   SPARC Ultra 1 (tested with Solaris 2.5.1)
 */

#include "gqmpeg.h"

#include <fcntl.h>
#include <sys/ioctl.h>

#ifdef linux
#include <linux/soundcard.h>
#endif

#if defined(sun) && defined(__svr4__) || defined(__NetBSD__)
#include <sys/audioio.h>
#endif

#ifdef __FreeBSD__
#include <machine/soundcard.h>
#endif

#ifdef __sgi
#include <math.h>
#include <audio.h>
#endif

#define LEFT  1
#define RIGHT 256
#define LEFT_MASK 0x00ff
#define RIGHT_MASK 0xff00

typedef struct _DeviceData DeviceData;
struct _DeviceData
{
	gint device_id;
	gchar *device_name;
	gint stereo;
};

static void mixer_set_vol(DeviceData *device, gint vol);
static gint mixer_get_vol(DeviceData *device);

static gint mixer_enabled = FALSE;
static gint current_vol = 0x0000;
static gint current_bal = 50;

static GList *device_list = NULL;
static DeviceData *current_device = NULL;

#if defined (linux) || defined (__FreeBSD__)
void mixer_init(gint init_device_id)
{
	char *device_names[] = SOUND_DEVICE_NAMES;
	int dev_mask = 0;
	int rec_mask = 0;
	int stereo_mask = 0;
	int i;
	int mf = -1;
	int t;

	mf = open("/dev/mixer",O_RDWR);
	if(mf < 0)
		{
		printf("unable to open /dev/mixer\n");
		mixer_enabled = FALSE;
		return;
		}

	t = ioctl(mf, SOUND_MIXER_READ_DEVMASK, &dev_mask);
	t|= ioctl(mf, SOUND_MIXER_READ_RECMASK, &rec_mask);
	t|= ioctl(mf, SOUND_MIXER_READ_STEREODEVS, &stereo_mask);
	if (t!=0)
		{
		printf("Unable to determine mixer devices\n");
		close(mf);
		mixer_enabled = FALSE;
		return;
		}

	current_device = NULL;

	/* get device listing */
	for (i=0; i<SOUND_MIXER_NRDEVICES; i++)
		{
		if ((dev_mask & (1<<i)) && !(rec_mask & (1<<i))) /* skip unsupported & record devs */
 			{
			DeviceData *device = g_new0(DeviceData, 1);
			device->device_id = i;
			device->device_name = device_names[i];
			device->stereo = (stereo_mask & (1<<i));
			device_list = g_list_append(device_list, device);

			if (debug_mode) printf("Mixer device added to list: %d, %s, %d\n",
					device->device_id, device->device_name, device->stereo);

			if (init_device_id == i) current_device = device;
			}
		}

	close (mf);

	if (device_list)
		{
		mixer_enabled = TRUE;
		if (!current_device) current_device = device_list->data;
		current_vol = mixer_get_vol(current_device);
		}
	else
		{
		mixer_enabled = FALSE;
		}
}

static void mixer_set_vol(DeviceData *device, gint vol)
{
	int mf = -1;
	if (!mixer_enabled) return;
	if (!device) return;

	mf = open("/dev/mixer",O_RDWR);
	if(mf < 0)
		{
		printf("unable to open /dev/mixer\n");
		return;
		}

	if(ioctl(mf, MIXER_WRITE(device->device_id), &vol)<0)
		{
		printf("failed to set volume on %d\n", device->device_id);
		}

	close(mf);

	if (debug_mode) printf("volume set to %d,%d\n", vol & LEFT_MASK, (vol & RIGHT_MASK) >> 8);
}

static gint mixer_get_vol(DeviceData *device)
{
	int mf = -1;
	gint vol = 0;
	if (!mixer_enabled) return 0;
	if (!device) return 0;

	mf = open("/dev/mixer",O_RDWR);
	if(mf < 0)
		{
		printf("unable to open /dev/mixer\n");
		return 0;
		}

	if(ioctl(mf, MIXER_READ(device->device_id), &vol)<0)
		{
		printf("failed to get volume on %d\n", device->device_id);
		vol = 0;
		}

	close(mf);

	if (debug_mode) printf("volume is %d,%d\n", vol & LEFT_MASK, (vol & RIGHT_MASK) >> 8);



	
	return vol;
}

#elif defined(__NetBSD__)
mixer_devinfo_t *infos;
mixer_ctrl_t *values;
void mixer_init(gint init_device_id)
{
  int fd, i, ndev;
  char *mixer_device;
  audio_device_t adev;
  mixer_devinfo_t dinfo;

  mixer_device = getenv("MIXERDEVICE");
  if (mixer_device == NULL)
    mixer_device = "/dev/mixer0";

  if ((fd = open(mixer_device, O_RDWR)) == -1) {
    perror(mixer_device);
    mixer_enabled = FALSE;
  }

  if (ioctl(fd, AUDIO_GETDEV, &adev) == -1) {
    perror(mixer_device);
    close(fd);
    mixer_enabled = FALSE;
  }

  for (ndev = 0; ; ndev++) {
    dinfo.index = ndev;
    if (ioctl(fd, AUDIO_MIXER_DEVINFO, &dinfo) == -1)
      break;
  }
  infos = calloc(ndev, sizeof *infos);
  values = calloc(ndev, sizeof *values);

  for (i = 0; i < ndev; i++) {
    infos[i].index = i;
    ioctl(fd, AUDIO_MIXER_DEVINFO, &infos[i]);
  }

  for (i = 0; i < ndev; i++) {
    values[i].dev = i;
    values[i].type = infos[i].type;
    if (infos[i].type == AUDIO_MIXER_VALUE) {
      DeviceData *device = g_new0(DeviceData, 1);
      device->device_id = i;
      device->device_name = infos[i].label.name;
      device->stereo = 1;
      device_list = g_list_append(device_list, device);

      values[i].un.value.num_channels = 2;
      if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) == -1) {
        values[i].un.value.num_channels = 1;
        if (ioctl(fd, AUDIO_MIXER_READ, &values[i]) == -1)
          perror("AUDIO_MIXER_READ");
      }

      if (debug_mode) printf("Mixer device added to list: %d, %s, %d\n",
                             device->device_id, device->device_name,
                             device->stereo);
      if (init_device_id == i) current_device = device;
    }
  }

  close(fd);

  if (device_list) {
    mixer_enabled = TRUE;
    if (!current_device)
      current_device = device_list->data;
    current_vol = mixer_get_vol(current_device);
  } else {
    mixer_enabled = FALSE;
  }
}

static void mixer_set_vol(DeviceData *device, gint vol)
{
  int fd;
  char *mixer_device;
  mixer_ctrl_t *m;

  mixer_device = getenv("MIXERDEVICE");
  if (mixer_device == NULL)
    mixer_device = "/dev/mixer0";

  if ((fd = open(mixer_device, O_RDWR)) == -1) {
    perror(mixer_device);
    close(fd);
  }

  m = &values[device->device_id];
  if (ioctl(fd, AUDIO_MIXER_WRITE, m) == -1) {
    perror("AUDIO_MIXER_WRITE");
    close(fd);
  }

  close(fd);

  if (m->un.value.num_channels == 2) {
    /* input and output seem to only have one channel?? */
    if (device->device_id == 13 || device->device_id == 14) {
      m->un.value.level[0] = vol * AUDIO_MAX_GAIN / 100;
    } else if (current_bal < 50) {
      m->un.value.level[0] = vol * AUDIO_MAX_GAIN / 100;
      m->un.value.level[1] = m->un.value.level[0] * current_bal / 50;
    } else if (current_bal > 50) {
      m->un.value.level[1] = vol * AUDIO_MAX_GAIN / 100;
      m->un.value.level[0] = m->un.value.level[1] * (100 - current_bal) / 50;
    } else {
      m->un.value.level[0] = m->un.value.level[1] = vol * AUDIO_MAX_GAIN / 100;
    }
  } else {
    m->un.value.level[0] = vol * AUDIO_MAX_GAIN / 100;
  }
  /* from AUDIO_MIN_GAIN (0) to AUDIO_MAX_GAIN (255) */

  if (debug_mode) printf("volume set to %d (%d)\n", vol, current_bal);
}

static gint mixer_get_vol(DeviceData *device)
{
  int fd;
  char *mixer_device;
  mixer_ctrl_t *m;

  mixer_device = getenv("MIXERDEVICE");
  if (mixer_device == NULL)
    mixer_device = "/dev/mixer0";

  if ((fd = open(mixer_device, O_RDWR)) == -1) {
    perror(mixer_device);
    close(fd);
    return -1;
  }

  m = &values[device->device_id];
  if (ioctl(fd, AUDIO_MIXER_READ, m) == -1) {
    perror("AUDIO_MIXER_READ");
    close(fd);
    return -1;
  }

  close(fd);

  if (m->un.value.num_channels == 2) {
    if (m->un.value.level[0] > m->un.value.level[1]) {
      current_bal = m->un.value.level[1] * 50 / m->un.value.level[0];
      return m->un.value.level[0] * 100 / AUDIO_MAX_GAIN;
    } else if (m->un.value.level[0] < m->un.value.level[1]) {
      current_bal = 100 - (m->un.value.level[0] * 50 / m->un.value.level[1]);
      return m->un.value.level[1] * 100 / AUDIO_MAX_GAIN;
    } else {
      current_bal = 50;
      return m->un.value.level[0] * 100 / AUDIO_MAX_GAIN;
    }
  } else {
    current_bal = 50;
    return m->un.value.level[0] * 100 / AUDIO_MAX_GAIN;
  }
}

#elif defined(sun) && defined(__svr4__)
static int device_ids[] = { AUDIO_SPEAKER,
			    AUDIO_LINE_OUT,
			    AUDIO_HEADPHONE
};
void mixer_init(gint init_device_id)
{
  char *device_names[] = { "speaker",
			   "lineout",
			   "headphone"
  };  
/* /dev/audioctl is needed for 2.6, is /dev/audio needed for 2.5? */
  const char *default_device = "/dev/audioctl";
  int afd = -1;
  int mode = O_RDONLY;
  audio_device_t adev;
  const char *device = default_device;
  int i;
  
  if ((afd = open(device, mode)) == -1) {
    perror(device);
    mixer_enabled = FALSE;
    return ;
  }
  
  if (ioctl(afd, AUDIO_GETDEV, &adev) == -1) {
    perror(device);
    close(afd);
    mixer_enabled = FALSE;
    return;
  }
  close(afd);

  for (i=0; i < 3; i++) {
    DeviceData *device = g_new0(DeviceData, 1);
    device->device_id = i;
    device->device_name = device_names[i];
    device->stereo = 1;
    device_list = g_list_append(device_list, device);
    
    if (debug_mode) printf("Mixer device added to list: %d, %s, %d\n",
			   device->device_id, device->device_name,
			   device->stereo);   
    if (init_device_id == i) current_device = device;
  }
  
  if (device_list) {
    mixer_enabled = TRUE;
    if (!current_device)
      current_device = device_list->data;
    current_vol = mixer_get_vol(current_device);
  } else {
    mixer_enabled = FALSE;
  }
}

static void mixer_set_vol(DeviceData *device, gint vol)
{
  const char *default_device = "/dev/audioctl";  /* see comment for previous /dev/audioctl */
  int afd = -1;
  int mode = O_RDONLY;
  audio_device_t adev;
  
  if ((afd = open(default_device, mode)) == -1) {
    perror(default_device);
    mixer_enabled = FALSE;
    return ;
  }
  
  if (ioctl(afd, AUDIO_GETDEV, &adev) == -1) {
    perror(default_device);
    close(afd);
    mixer_enabled = FALSE;
    return;
  }

  {    
    int port;
    audio_info_t ainfo;
    
    AUDIO_INITINFO(&ainfo);
    port = device_ids[device->device_id];
    
    /* SUN: from AUDIO_MIN_GAIN (0) to AUDIO_MAX_GAIN (255) */
    ainfo.play.gain = vol * AUDIO_MAX_GAIN / 100;
    ainfo.play.port = port;
    
    /* SUN: from AUDIO_LEFT_BALANCE (0) to AUDIO_RIGHT_BALANCE (64) */
    ainfo.play.balance = current_bal * AUDIO_RIGHT_BALANCE / 100;
        
    if (ioctl(afd, AUDIO_SETINFO, &ainfo) == -1) {
      perror("AUDIO_SETINFO 1");
      close(afd);
      return;
    }
    if (debug_mode) {
      printf("volume set to %d (%d)\n", vol, current_bal);
    }
  }
  close(afd);
  
  return;
}

static gint mixer_get_vol(DeviceData *device)
{
  const char *default_device = "/dev/audio";  
  int afd = -1;
  int mode = O_RDONLY;
  audio_device_t adev;
  audio_info_t ainfo;
  
  if ((afd = open(default_device, mode)) == -1) {
    perror(default_device);
    mixer_enabled = FALSE;
    return -1;
  }
  
  if (ioctl(afd, AUDIO_GETDEV, &adev) == -1) {
    perror(default_device);
    close(afd);
    mixer_enabled = FALSE;
    return -1;
  }
    
  /*  AUDIO_INITINFO(&ainfo); */
  
  if (ioctl(afd, AUDIO_GETINFO, &ainfo) == -1) {
    perror("AUDIO_GETINFO");
    close(afd);
    return -1;
  }

  if (ainfo.play.balance > AUDIO_RIGHT_BALANCE)
    current_bal = 50;
  else
    current_bal = ainfo.play.balance * 100 / AUDIO_RIGHT_BALANCE;
  
  close(afd);
  
  return ainfo.play.gain * 100 / AUDIO_MAX_GAIN; 
}

#elif defined(__sgi)

long volconv[101];

void mixer_init(gint init_device_id)
{
	float conv;
	int i;
	DeviceData *device = g_new0(DeviceData, 1);
	device->device_id = AL_DEFAULT_DEVICE;
	device->device_name = "Default Audio";
	device->stereo = 1;
	device_list = g_list_append(device_list, device);

	for( i=0; i<101; i++ )
	{
		conv = i;
		conv /= 100;
		conv *= conv * conv; /* scale = percent * percent * percent */
		volconv[i] = (long)(conv*255);
	}

	current_vol = mixer_get_vol( device );

	mixer_enabled = TRUE;
}

static void mixer_set_vol(DeviceData *device, gint vol)
{
	long	volset[4];

	volset[0] = AL_LEFT_SPEAKER_GAIN;
	volset[2] = AL_RIGHT_SPEAKER_GAIN;

	if (vol < 0) vol = 0;
	if (vol > 100) vol = 100;

	if ( 50 != current_bal )
	{
		float	lpcnt, rpcnt, balpcnt;

		lpcnt = ((float)vol)/100.F;
		rpcnt = lpcnt;

		balpcnt = ((float)(50 - current_bal))/50.F;
		lpcnt *= (1.F - balpcnt);
		rpcnt *= (1.F + balpcnt);

		volset[1] = volconv[(long)(lpcnt*100.F)];
		volset[3] = volconv[(long)(rpcnt*100.F)];
	}
	else
	{
		volset[1] = volconv[vol];
		volset[3] = volconv[vol];
	}
	
	ALsetparams( AL_DEFAULT_DEVICE, volset, 4 );
}

static gint mixer_get_vol(DeviceData *device)
{
	gint retvol = 0;
	long volset[4];
	float lpcnt, rpcnt;

	volset[0] = AL_LEFT_SPEAKER_GAIN;
	volset[2] = AL_RIGHT_SPEAKER_GAIN;

	ALgetparams( AL_DEFAULT_DEVICE, volset, 4 );

	if ( volset[1] != volset[3] )
	{

		lpcnt = powf( ( (float)volset[1] / 255.F ), 1.F/3.F );
		rpcnt = powf( ( (float)volset[3] / 255.F ), 1.F/3.F );
		retvol = (gint)( 100. * ( (lpcnt+rpcnt) * 0.5 ) );

		current_bal = 50 - 50 * ( lpcnt - rpcnt );
	}
	else if ( volset[1] == 0 )
	{
		current_bal = 50;
		retvol = 0;
	}
	else
	{
		current_bal = 50;
		lpcnt = powf( ((float)volset[1])/255.F, 1.F/3.F );
		retvol = (gint)(100.*lpcnt);
	}

	return retvol;
}

#else

/* fixme: non linux compiling makes dummy functions */
void mixer_init(gint init_device_id)
{
	mixer_enabled = FALSE;
}

static void mixer_set_vol(DeviceData *device, gint vol)
{
	return;
}

static gint mixer_get_vol(DeviceData *device)
{
	return 0;
}

#endif

#if defined(sun) && defined(__svr4__) || defined(__NetBSD__)
/* from 0 through 100% */
void set_volume(gint vol)
{

	if (vol < 0) vol = 0;
	if (vol > 100) vol = 100;

	if (current_device && !current_device->stereo)  current_bal = 50;

	mixer_set_vol(current_device, vol);

	current_vol = vol;
}

gint get_volume()
{
  return current_vol;
}

/* from 0 through 100: 0 left, 50 center, 100 right */
void set_balance(gint bal)
{  
	
	if (current_device && !current_device->stereo)  {
	  current_bal = AUDIO_MID_BALANCE;
	  return;
	}
	
	if (bal < 0) bal = 0;
	if (bal > 100) bal = 100;


	current_bal = bal;
	
	if (debug_mode) printf("balance set to %d @ %d \n", bal,
				    current_vol);       	
	mixer_set_vol(current_device, current_vol);
}

gint get_balance() {
  return current_bal;
}

#elif defined(__sgi)
/* from 0 through 100% */
void set_volume(gint vol)
{

	if (vol < 0) vol = 0;
	if (vol > 100) vol = 100;

	if (current_device && !current_device->stereo)  current_bal = 50;

	mixer_set_vol(current_device, vol);

	current_vol = vol;
}

gint get_volume()
{
  return current_vol;
}

/* from 0 through 100: 0 left, 50 center, 100 right */
void set_balance(gint bal)
{  
	
	if (current_device && !current_device->stereo)  {
	  current_bal = 50;
	  return;
	}
	
	if (bal < 0) bal = 0;
	if (bal > 100) bal = 100;


	current_bal = bal;
	
	if (debug_mode) printf("balance set to %d @ %d \n", bal,
				    current_vol);       	
	mixer_set_vol(current_device, current_vol);
}

gint get_balance() {
  return current_bal;
}

#else

/* from 0 through 100% */
void set_volume(gint vol)
{
	gint l, r;
	gint new_vol = 0x0000;

	if (vol < 0) vol = 0;
	if (vol > 100) vol = 100;

	if (current_device && !current_device->stereo) current_bal = 50;

	if (current_bal == 50)
		{
		l = vol;
		r = vol;
		}
	else if (current_bal > 50)
		{
		l = (float)vol * (100 - current_bal) / 50;
		r = vol;
		}
	else
		{
		l = vol;
		r = (float)vol * current_bal / 50;
		}

	new_vol = l * LEFT + r * RIGHT;

	mixer_set_vol(current_device, new_vol);

	current_vol = new_vol;
}

gint get_volume()
{
	gint l;
	gint r;

	current_vol = mixer_get_vol(current_device);

	l = current_vol & LEFT_MASK;
	r = (current_vol & RIGHT_MASK) >> 8;

	if (l > r)
		return l;
	else
		return r;
	
}

/* from 0 through 100: 0 left, 50 center, 100 right */
void set_balance(gint bal)
{
	gint l, r, v;
	gint new_vol;

	if (current_device && !current_device->stereo)
		{
		current_bal = 50;
		return;
		}

	if (bal < 0) bal = 0;
	if (bal > 100) bal = 100;

	l = (current_vol & LEFT_MASK);
	r = (current_vol & RIGHT_MASK) >> 8;

	if (l == r)
		v = l;
	else if (l < r)
		v = r;
	else
		v = l;

	if (bal == 50)
		{
		l = v;
		r = v;
		}
	else if (bal > 50)
		{
		l = (float)v * (100 - bal) / 50;
		r = v;
		}
	else
		{
		l = v;
		r = (float)v * bal / 50;
		}

	new_vol = l * LEFT + r * RIGHT;

	if (debug_mode) printf("balance set to %d,%d @ %d\n", new_vol & LEFT_MASK, (new_vol & RIGHT_MASK) >> 8, bal);

	mixer_set_vol(current_device, new_vol);

	current_vol = new_vol;
	current_bal = bal;
}

gint get_balance()
{
	gint l, r, bal;

	l = current_vol & LEFT_MASK;
	r = (current_vol & RIGHT_MASK) >> 8;

	if (l == r)
		bal = 50;
	else if (l<r)
		bal = 50 + (float)(r - l) / r * 50;
	else
		bal = 50 - (float)(l - r) / l * 50;

	current_bal = bal;

	return bal;
}
#endif

/* do _not_ free the data contained in the list, only the list */
GList *get_mixer_device_list()
{
	GList *list = NULL;
	GList *work = device_list;

	while(work)
		{
		DeviceData *device = work->data;
		list = g_list_append(list, device->device_name);
		work = work->next;
		}
	return list;
}

gint get_mixer_device_id(gchar *device_name)
{
	GList *list = device_list;
	if (!device_name) return -1;

	while(list)
		{
		DeviceData *device = list->data;
		if (strcmp(device->device_name, device_name) == 0) return device->device_id;
		list = list->next;
		}

	return -1;
}

void set_mixer_device(gint device_id)
{
	GList *list = device_list;

	while(list)
		{
		DeviceData *device = list->data;
		if (device->device_id == device_id)
			{
			current_device = device;
			mixer_get_vol(device);
			return;
			}
		list = list->next;
		}
}

