#include <u.h>
#include <sys/ioctl.h>
#ifdef __linux__
#include <linux/soundcard.h>
#else
#include <sys/soundcard.h>
#endif
#include <libc.h>
#include "audio.h"

static int setvolume(Audio*, char*, int, int);
static int getvolume(Audio*, char*, int*, int*);
static void closeaudio(Audio*);

enum
{
	Channels = 2,
	Rate = 44100,
	Bits = 16,
	Bigendian = 1,
};

/* maybe this should return -1 instead of sysfatal */
Audio*
openaudiodev(char *name, int flag)
{
	int afd, blksz, cfd, t;
	ulong ul;
	Audio *a;
	char buf[100], *p;

	afd = -1;
	cfd = -1;
	if(name == nil || strcmp(name, "local") == 0)
		name = "/dev/dsp";
	if(flag&AUDIO){
		if((afd = open(name, OWRITE)) < 0)
			return nil;
	
		blksz = 0;
		if(ioctl(afd, SNDCTL_DSP_GETBLKSIZE, &blksz) < 0)
			goto err;
		if(blksz < 32 || blksz > 65536)
			goto err;
	
		t = Bits;
		if(ioctl(afd, SNDCTL_DSP_SAMPLESIZE, &t) < 0)
			goto err;
	
		t = Channels-1;
		if(ioctl(afd, SNDCTL_DSP_STEREO, &t) < 0)
			goto err;
	
		ul = Rate;
		if(ioctl(afd, SNDCTL_DSP_SPEED, &ul) < 0)
			goto err;
	}
	if(flag&VOLUME){
		p = strstr(name, "dsp");
		if(p == nil)
			goto err;
		snprint(buf, sizeof buf, "%.*smixer%s", utfnlen(name, p-name), name, p+3);
		cfd = open(buf, ORDWR);
		if(cfd < 0)
			goto err;
	}
		
	a = mallocz(sizeof *a, 1);
	a->fd[0] = -1;
	a->fd[1] = afd;
	a->cfd[0] = cfd;
	a->cfd[1] = -1;
	a->setvolume = setvolume;
	a->getvolume = getvolume;
	a->close = closeaudio;
	a->blksz = blksz;
	return a;
	
err:
	close(afd);
	return nil;
}

static void
closeaudio(Audio *a)
{
	close(a->fd[1]);
	close(a->cfd[0]);
	free(a);
}

static struct {
	char *name;
	int id;
} names[] = {
	"master", 	SOUND_MIXER_VOLUME,
	"bass", 		SOUND_MIXER_BASS,
	"treble", 		SOUND_MIXER_TREBLE,
	"line", 		SOUND_MIXER_LINE,
	"pcm", 		SOUND_MIXER_PCM,
	"synth", 		SOUND_MIXER_SYNTH,
	"cd", 		SOUND_MIXER_CD,
	"mic", 		SOUND_MIXER_MIC,
	"record", 		SOUND_MIXER_RECLEV,
	"mix",		SOUND_MIXER_IMIX,
	"pcm2",		SOUND_MIXER_ALTPCM,
	"speaker",	SOUND_MIXER_SPEAKER,
	"line1",		SOUND_MIXER_LINE1,
	"line2",		SOUND_MIXER_LINE2,
	"line3",		SOUND_MIXER_LINE3,
	"digital1",	SOUND_MIXER_DIGITAL1,
	"digital2",	SOUND_MIXER_DIGITAL2,
	"digital3",	SOUND_MIXER_DIGITAL3,
	"phonein",		SOUND_MIXER_PHONEIN,
	"phoneout",		SOUND_MIXER_PHONEOUT,
	"radio",		SOUND_MIXER_RADIO,
	"video",		SOUND_MIXER_VIDEO,
	"monitor",	SOUND_MIXER_MONITOR,
	"igain",		SOUND_MIXER_IGAIN,
	"ogain",		SOUND_MIXER_OGAIN,
	0
};

static int
lookname(char *s)
{
	int i;
	
	for(i=0; names[i].name; i++)
		if(strcmp(s, names[i].name) == 0)
			return names[i].id;
	werrstr("no such volume '%s'", s);
	return -1;
}
	
static int
setvolume(Audio *a, char *what, int llevel, int rlevel)
{
	int can, id;
	ulong v;

	if((id = lookname(what)) == -1)
		return -1;
	if(ioctl(a->cfd[0], SOUND_MIXER_READ_DEVMASK, &can) < 0)
		can = ~0;
	if(!can & (1<<id)){
		werrstr("device cannot change '%s'", what);
		return -1;
	}
	v = llevel | (rlevel<<8);
	return ioctl(a->cfd[0], MIXER_WRITE(id), &v);
}

static int
getvolume(Audio *a, char *what, int *llevel, int *rlevel)
{
	int can, id;
	ulong v;

	if((id = lookname(what)) == -1)
		return -1;
	if(ioctl(a->cfd[0], SOUND_MIXER_READ_DEVMASK, &can) < 0)
		can = ~0;
	if(!can & (1<<id)){
		werrstr("device cannot change '%s'", what);
		return -1;
	}
	if(ioctl(a->cfd[0], MIXER_READ(id), &v) < 0)
		return -1;
	if(llevel)
		*llevel = v&0xFF;
	if(rlevel)
		*rlevel = (v>>8)&0xFF;
	return 0;
}



