/*
 *  Simple matrix mixer utility for EMU10K1
 *
 *  Copyright (C) 2000 Daniel Koukola <dkoukola@atrey.karlin.mff.cuni.cz>
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2, or (at your option)
 *    any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public
 *    License along with this program; if not, write to the Free
 *    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
 *    USA.
 */

#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/soundcard.h>

#define MIXDEV		"/dev/mixer"

#define MIXSTEP		(0x7fffffff / 100)

#define OP_PRINT	0
#define OP_SET		1
#define OP_MUTE		2
#define OP_UNMUTE	3

#define NIN		9
#define NOUT		6

#define MUTE		0x80000000

#define CHAN(x,y,s)	digmix[((x) * NIN + (y)) * 2 + (s)]

int digmix[NIN * NOUT * 2];

char *input[NIN][4] = {{"dsp", "fx0", "in0"},
		       {"dsp1", "fx1", "in1"},
		       {"analog", "ac97rec", "in2"},
    	  	       {"spdif", "cd", "in3"},
		       {"in4"},
		       {"in5"},
		       {"in6", "mic", "linein"},
		       {"in7"},
		       {"in8"}};

char *output[NOUT][4] = {{"front", "ac97", "out0"},
	 	         {"out1"},
		         {"out2"},
		         {"out3", "headphones"},
		         {"rear", "out4"},
		         {"record", "out5"}};


int main(int argc, char **argv)
{
	int mixdev, argn, in = 0, out = 0, op = 0, val, vall = ~0, valr = ~0, i, j, k, l, r, neg;
	char *s, *e;
	
	if ((mixdev = open(MIXDEV, O_RDONLY)) == -1) {
		perror("open");
		return 1;
	}
	
	if (ioctl(mixdev, SOUND_MIXER_PRIVATE1, digmix)) {
		perror("ioctl");
		fprintf(stderr,"The dm utility doesn't work with driver versions > 0.7, this may be you problem.\n");
		fprintf(stderr,"It as been replaced by emu-dspmgr in utils/libdsp.\n");
		fprintf(stderr,"You should read the manual page to get you started:\n");
		fprintf(stderr,"man ./emu-dspmgr.1 (in utils/mixer)\n");
		return 1;
	}	
	
	for (argn = 1; argn < argc; argn++) {
		s = argv[argn];

		neg = (s[0] == '~');

		for (k = -1, i = 0; i < NIN; i++)
			for (j = 0; j < 4 && input[i][j] != NULL; j++)
				if (!strcmp(s + neg, input[i][j])) k = i;
		if (k != -1) {
			if (neg) in |= ~(1 << k); else in |= 1 << k;
			continue;
		}
		
		for (k = -1, i = 0; i < NOUT; i++)
			for (j = 0; j < 4 && output[i][j] != NULL; j++)
				if (!strcmp(s + neg, output[i][j])) k = i;
		
		if (k != -1) {
			if (neg) out |= ~(1 << k); else out |= 1 << k;
			continue;
		}

		if (neg) {
			fprintf(stderr, "invalid option: '%s'\n",s);
			return 1;
		}

		if (!op) {
			if (!strcmp(s, "mute")) {
				op = OP_MUTE;
				continue;
			}

			if (!strcmp(s, "unmute")) {
				op = OP_UNMUTE;
				continue;
			}
		}

		val = strtol(s, &e, 10);

		if (*e != '\0' || abs(val) > 100 || (op != OP_PRINT && op != OP_SET)) {
			fprintf(stderr, "invalid option: '%s'\n",s);
			return 1;
		}

		op = OP_SET;

		if (s[0] == '+' || s[0] == '-')
			val += 300;

		if (vall == ~0) vall = val;
		else if (valr == ~0) valr = val;
		else {
			fprintf(stderr, "invalid option: '%s'\n",s);
			return 1;
		}

	}

	if (!in) in = ~0;
	if (!out) out = ~0;
	if (valr == ~0) valr = vall;

	in &= (1 << NIN) - 1;
	out &= ( 1 << NOUT) - 1;

	if (op == OP_PRINT) {
		printf("\n%10s","");
		for (j = 0, k = in; k; k >>= 1, j++) if (k & 1) printf("%*s%-*s", (11 - strlen(input[j][0])) / 2, "", (12 + strlen(input[j][0])) / 2, input[j][0]);
		printf("\n");
	}

	for (i = 0; out; out >>= 1, i++) if (out & 1) {
		if (op == OP_PRINT) printf("\n%-10s ", output[i][0]);
		for (j = 0, k = in; k; k >>= 1, j++) if (k & 1) {
			switch (op) {
				case OP_PRINT:
					l = CHAN(i,j,0);
					r = CHAN(i,j,1);
					if (l & MUTE) printf(" * "); else printf("%03d", l / MIXSTEP);
					printf(" / ");
					if (r & MUTE) printf(" * "); else printf("%03d", r / MIXSTEP);
					printf("  ");
					break;
				case OP_MUTE:
					CHAN(i,j,0) |= MUTE;
					CHAN(i,j,1) |= MUTE;
					break;
				case OP_UNMUTE:
					CHAN(i,j,0) &= ~MUTE;
					CHAN(i,j,1) &= ~MUTE;
					break;
				case OP_SET:
					if ((CHAN(i,j,0) & MUTE) || (CHAN(i,j,1) & MUTE)) break;
					if (vall > 100) l = vall - 300 + CHAN(i,j,0) / MIXSTEP;	else l = vall;
					if (valr > 100) r = valr - 300 + CHAN(i,j,1) / MIXSTEP;	else r = valr;
					if (l > 100) l = 100; else if (l < 0) l = 0;
					if (r > 100) r = 100; else if (r < 0) r = 0;
					CHAN(i,j,0) = l * MIXSTEP;
					CHAN(i,j,1) = r * MIXSTEP;
			}
		}
	}

	if (op == OP_PRINT) printf("\n\n");

	if (ioctl(mixdev, SOUND_MIXER_PRIVATE2, digmix)) {
		perror("ioctl");
		return 1;
	}
	
	close(mixdev);
	
	return 0;
}
