/*
 * pcsel.c
 *
 * /dev/pcsp implementation
 *
 * Copyright (C) 1993, 1994  Michael Beck
 */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <getopt.h>
#include <sys/ioctl.h>
#include <sys/pcsp.h>
 
#define AUDIO		"/dev/pcsp"

char *version = "pcsel 0.9c (12 Oct 95)\n";
int audio, omode = O_WRONLY;

/* trying to define independent ioctl for sounddrivers version 1 and 2+ */
#ifdef SOUND_VERSION
#define IOCTL(a,b,c)            (ioctl(a,b,&c))
#else
#define IOCTL(a,b,c)            (c = ioctl(a,b,c) )
#endif

struct _devices {
	char		*name;
	char		*nick;
	unsigned	code;
} devs[] = {
	{ "Stereo-on-One", "Sto1", SNDCARD_STO1 },
	{ "Mono DAC", "DACm",      SNDCARD_DACM },
	{ "Stereo DAC", "DACs",    SNDCARD_DACS },
	{ "PC-Speaker", "PCSP",    SNDCARD_PCSP }
};

#define LP_NO		3
#define CARD_NO		4

void actual_conf(unsigned long dev, unsigned long port, unsigned long portS)
{
	int i, volume, srate, maxrate, emulation;
	int version, minor, major;

	if (IOCTL(audio, PCSP_GET_VERSION, version) == 0) {
		major = (version >> 8);
		minor = (version & 0xFF);
		if (! (minor & 0xF))
			minor >>= 4;
		printf("PCSP driver version %d.%x\n", major, minor);
	}
	printf("Actual PCSP output device: ");
	for (i = 0; i < CARD_NO; ++i)
		if (devs[i].code == dev) {
			printf("%s ", devs[i].name);
			if (dev == SNDCARD_PCSP) {
				IOCTL(audio, PCSP_GET_VOL, volume);
				printf("\nVolume : %d %%", volume * 100 / 256);
				IOCTL(audio, PCSP_GET_SRATE, srate);
				if (srate > 0)
					printf(", real samplerate : %d Hz\n", srate);
				else
					printf(", Interval Mode, lower bound : %d Hz\n", -srate);
				IOCTL(audio, PCSP_GET_MEASURE, maxrate);
				if (maxrate)
					if (maxrate == -1)
						printf("PCSP is disabled\n");
					else
						printf("Maximum Samplerate is %d Hz\n", maxrate);
			}
			else {
				if (dev != SNDCARD_DACS)
					printf("at lp%ld\n", port);
				else
					printf("at lp%ld & lp%ld\n", port, portS);
			}
			emulation = PCSP_EMULATION_QUERY;
			if (IOCTL(audio, PCSP_SET_EMU_MODE, emulation) < 0)
				printf("Emulation mode is not supported\n");
			else {
				printf("16bit Stereo Emulation ");
				if (emulation == PCSP_EMULATION_ON)
					printf("enabled\n");
				else if (emulation == PCSP_EMULATION_OFF)
					printf("disabled\n");
				else
					printf("status unknown\n");
			}
			break;
		}
	if (i == CARD_NO) {
		printf("unknown device %ld !!!\n", dev);
		exit (EXIT_FAILURE);
	}	
}

unsigned long set_dev(char *devname)
{
	int i;
	int dev = 0;

	for (i = 0; i < CARD_NO; ++i)
		if (! strcasecmp(devname, devs[i].nick)) {
			dev = devs[i].code;
			if (IOCTL(audio, PCSP_SET_DEV, dev) < 0) {
				perror(AUDIO);
				exit(EXIT_FAILURE);
			}
		}
	return dev;
}

struct option options[] =
{
	{ "help",	no_argument,		NULL, 'h' },
	{ "version",	no_argument,		NULL, 'V' },
	{ "device",	required_argument,	NULL, 'd' },
	{ "emulation",	required_argument,	NULL, 'e' },
	{ "port",	required_argument,	NULL, 'p' },
	{ "right-port",	required_argument,	NULL, 'r' },
	{ "left-port",	required_argument,	NULL, 'l' },
	{ "mono",	no_argument,		NULL, 'M' },
	{ "stereo",	no_argument,		NULL, 'S' },
	{ "volume",	required_argument,	NULL, 'v' },
	{ "speed",	required_argument,	NULL, 's' },
	{ "realspeed",	required_argument,	NULL, 'b' },
	{ 0, 0, 0, 0 }
};

void usage(char *command)
{
	int i;

	fprintf(stderr, "Usage: %s [OPTIONS]\n\n", command);
	fprintf(stderr, "  -V --version           output version information and exit\n");
	fprintf(stderr, "  -d --device=DEVICE     set the output-device\n");
	fprintf(stderr, "  -p --port=PORT         the lp port to use (0-2) for single DAC\n");
	fprintf(stderr, "  -r --right-port=PORT   the lp port for the right DAC\n");
	fprintf(stderr, "  -l --left-port=PORT    the lp port for the left DAC\n");
	fprintf(stderr, "  -b --realspeed=SPEED   set the real sampling rate for PC-Speaker\n");
	fprintf(stderr, "  -e --emulation=on|off  enables or diables the 16bit stereo emulation\n");
	fprintf(stderr, "  -s --speed=SPEED       set the sampling rate\n");
	fprintf(stderr, "  -M --mono              set mono\n");
	fprintf(stderr, "  -S --stereo            set stereo if possible\n");
	fprintf(stderr, "  -v --volume=VOLUME     set the volume for PC-Speaker\n");
	fprintf(stderr, "  -h --help              display this help and exit\n");
        fprintf(stderr, "\nsupported devices :\n");
        for (i = 0; i < CARD_NO; ++i)
          fprintf(stderr, "  '%s' : %s\n", devs[i].nick, devs[i].name);
	fprintf(stderr, "\nWithout options %s reports the actual output-device and its parameter.\n", command);
}

int main(int argc, char *argv[])
{
  int index;
  int dev, port, portS, arg, stereo, samplerate;
  int volume = 0, srate = 0, emulation = PCSP_EMULATION_QUERY;
  char c, flag, acc, opt;

  audio = open(AUDIO, omode, 0);
  if (audio < 0) {
    perror(AUDIO);
    exit(EXIT_FAILURE);
  }

  IOCTL(audio, PCSP_GET_DEV, dev);
  if (dev != SNDCARD_PCSP) {
    IOCTL(audio, PCSP_GET_PORTS, port);
    portS = (port & 0xFF00) >> 8;
    port &= 0xFF;
  }
  else {
    port = 1; portS = 2;
  }
  stereo = 2; samplerate = 0;

  acc = 0; opt = 1;
  while ((c = getopt_long(argc, argv, "Vb:d:e:p:r:l:s:v:MShH?", 
                          options, &index)) != EOF) {
    flag = 1; opt = 0;
    switch (c) {
      case 'V':
        fprintf(stderr, version);
	exit(EXIT_SUCCESS);
      case 'b':
	if (! strcasecmp("max", optarg)) {
		IOCTL(audio, PCSP_GET_MEASURE, srate);
		if (srate > 18356)
			srate = 18356;
		break;
	}
	srate = atoi(optarg);
	if (srate < 10000 || srate > 18356) {
          fprintf(stderr, "%s: Real samplerate not in range [10000, 18356]\n", argv[0]);
          exit(EXIT_FAILURE);
        }
        break;
      case 'd': 
        dev = set_dev(optarg); 
        if (! dev) {
          fprintf(stderr, "%s: unknown device '%s'\n", argv[0], optarg);
          exit(EXIT_FAILURE);
        }
        break;
      case 'e':
        if (! strcasecmp("on", optarg))
		emulation = PCSP_EMULATION_ON;
	else if (! strcasecmp("off", optarg))
		emulation = PCSP_EMULATION_OFF;
	else {
	  fprintf(stderr, "%s: unknown keyword '%s'\n", argv[0], optarg);
	  exit(EXIT_FAILURE);
	}
	break;
      case 'p':
      case 'r': flag = 0;
      case 'l':
        arg = atoi(optarg);
        if (arg >= LP_NO)
          fprintf(stderr, "%s: wrong port\n", argv[0]);
        else {
          (flag) ? portS : port = arg;
          acc = 1;
        }
        break;
      case 'M':
        stereo = 0; break;
      case 'S':
        stereo = 1; break;
      case 's':
        samplerate = atoi(optarg); break;
      case 'v':
	volume = atoi(optarg); break;
      case 'h':
      case 'H':
      case '?':
        fprintf(stderr, version);
        usage(argv[0]);
	exit(EXIT_SUCCESS);
      default:
        usage(argv[0]);
        exit(EXIT_FAILURE);
    }
  }
  if (opt) {
    actual_conf(dev, port, portS);
    exit(EXIT_SUCCESS);
  }

  if (acc) {
    if (dev == SNDCARD_DACS && port == portS) {
      fprintf(stderr, "%s: a Stereo DAC need two different ports\n", argv[0]);
      exit(EXIT_FAILURE);
    }
    port |= portS << 8;
    if (IOCTL(audio, PCSP_SET_PORTS, port) < 0) {
      perror(AUDIO);
      exit(EXIT_FAILURE);
    }
  }

  if (stereo != 2)
    if (IOCTL(audio, SNDCTL_DSP_STEREO, stereo) < 0) 
      fprintf(stderr, "%s: can't set stereo\n", argv[0]);

  if (srate)
    if (IOCTL(audio, PCSP_SET_SRATE, srate) < 0)
      fprintf(stderr, "%s: can't set real samplerate for PC-Speaker\n", argv[0]);

  if (samplerate)
    if (IOCTL(audio, SNDCTL_DSP_SPEED, samplerate) < 0)
      fprintf(stderr, "%s: can't set samplerate to %d Hz\n", argv[0], samplerate);

  if (volume > 0) {
    volume = (volume * 256) / 100;
    if (IOCTL(audio, PCSP_SET_VOL, volume) < 0)
      fprintf(stderr, "%s: can't set volume for PC-Speaker\n", argv[0]);
  }

  if (emulation != PCSP_EMULATION_QUERY)
    if (IOCTL(audio, PCSP_SET_EMU_MODE, emulation) < 0)
      fprintf(stderr, "%s: can't set emulation mode\n", argv[0]);

  return (EXIT_SUCCESS);
}

