From alsa-devel-owner@alsa.jcu.cz  Sun Apr 19 11:53:26 1998
Received: from birgrave.birnet.private (root@dip155-2.hamburg.netsurf.de [194.195.202.155])
	by marvin.jcu.cz (8.8.8/8.8.8) with ESMTP id LAA05327
	for <alsa-devel@alsa.jcu.cz>; Sun, 19 Apr 1998 11:53:13 +0200
Received: from birgrave (timj@localhost)
	by birgrave.birnet.private (8.8.5/8.8.5) with internal id LAA07623;
	Sun, 19 Apr 1998 11:52:42 +0200
From: Tim Janik <timj@gtk.org>
Message-Id: <199804190952.LAA07623%timj@gtk.org>
Date: Sun, 19 Apr 1998 11:52:40 +0200 (CEST)
To: alsa-devel@alsa.jcu.cz
Subject: AlsaMixer v0.9
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII
Reply-To: alsa-devel@alsa.jcu.cz
Sender: alsa-devel-owner@alsa.jcu.cz
Precedence: list

ok folks,

here is the code for AlsaMixer v0.9.
it still has the pereviously mentioned bug about the channel
limits, because i believe this is a driver bug (especially since
AlsaMixer handles all channels equally).

AlsaMixer supports:
-	fullscreen mode with cursor movements
-	right/left channel volume adjustment
	raise:   q, w, e, cursor_up,   page_up,   home, +
	lower: y,z, x, c, cursor_down, page_down, end,  -
-	balance adjustment: b, =
-	right/left muting: m, <, >, dot, comma
-	setting of record source: space
-	visible channel scrolling with automatic screen layout adjustment
	(for really small terminals (xterm) or very much channels)
-	black/white or color mode

AlsaMixer lacks:
-	a help screen, that's why i mentioned the kkeybindings in the above
	lines ;)


this was a one day shot at a mixer program, so please be kind ;)

---
ciaoTJ


/* AlsaMixer - Commandline mixer for the ALSA project
 * Copyright (C) 1998 Tim Janik <timj@gtk.org> & Jaroslav Kysela <perex@jcu.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
 * of the License, 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 Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/sound.h>

#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/signal.h>

#include <ncurses.h>
#include <time.h>

/* example compilation commandline:
 * clear; gcc -Wall -Werror -pipe -O2 alsamixer.c -o alsamixer -lncurses
 */

/* --- defines --- */
#define	PRGNAME "alsamixer"
#define	PRGNAME_UPPER "AlsaMixer"
#define	VERSION "v0.9"

#undef MAX
#define MAX(a, b)  (((a) > (b)) ? (a) : (b))
#undef MIN
#define MIN(a, b)  (((a) < (b)) ? (a) : (b))
#undef ABS
#define ABS(a)     (((a) < 0) ? -(a) : (a))
#undef CLAMP
#define CLAMP(x, low, high)  (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))

#define MIXER_BLACK	(COLOR_BLACK)
#define MIXER_DARK_RED  (COLOR_RED)
#define MIXER_RED       (COLOR_RED | A_BOLD)
#define MIXER_GREEN     (COLOR_GREEN | A_BOLD)
#define MIXER_ORANGE    (COLOR_YELLOW)
#define MIXER_YELLOW    (COLOR_YELLOW | A_BOLD)
#define MIXER_MARIN     (COLOR_BLUE)
#define MIXER_BLUE      (COLOR_BLUE | A_BOLD)
#define MIXER_MAGENTA   (COLOR_MAGENTA)
#define MIXER_DARK_CYAN (COLOR_CYAN)
#define MIXER_CYAN      (COLOR_CYAN | A_BOLD)
#define MIXER_GREY      (COLOR_WHITE)
#define MIXER_GRAY      (MIXER_GREY)
#define MIXER_WHITE     (COLOR_WHITE | A_BOLD)


/* --- variables --- */
static WINDOW  		*mixer_window = NULL;
static int		 mixer_max_x = 0;
static int		 mixer_max_y = 0;
static int		 mixer_ofs_x = 0;
static float		 mixer_extra_space = 0;
static int		 mixer_ofs_y = 0;

static int		 mixer_fd = -1;
static char		*mixer_device_file = NULL;
static char		*mixer_card_name = NULL;
static char		*mixer_device_name = NULL;

static int		 mixer_n_channels = 0;
static int		 mixer_n_vis_channels = 0;
static int		 mixer_first_vis_channel = 0;
static int		 mixer_focus_channel = 0;

static int		 mixer_lvolume_delta = 0;
static int		 mixer_rvolume_delta = 0;
static int		 mixer_balance_volumes = 0;
static int		 mixer_toggle_mute_left = 0;
static int		 mixer_toggle_mute_right = 0;
static int		 mixer_toggle_record = 0;


/* --- draw contexts --- */
enum
{
  DC_BACK,
  DC_FRAME,
  DC_TEXT,
  DC_PROMPT,
  DC_CBAR_FRAME,
  DC_CBAR_MUTE,
  DC_CBAR_NOMUTE,
  DC_CBAR_RECORD,
  DC_CBAR_NORECORD,
  DC_CBAR_EMPTY,
  DC_CBAR_FULL_1,
  DC_CBAR_FULL_2,
  DC_CBAR_FULL_3,
  DC_CBAR_NUMBER,
  DC_CBAR_LABEL,
  DC_CBAR_FOCUS_LABEL,
  DC_FOCUS,
  DC_LAST
};

static int dc_fg[DC_LAST] = { 0 };
static int dc_attrib[DC_LAST] = { 0 };
static int dc_char[DC_LAST] = { 0 };
static int mixer_do_color = 0;

static void
mixer_init_dc (int c,
	       int n,
	       int f,
	       int b,
	       int a)
{
  dc_fg[n] = f;
  dc_attrib[n] = a;
  dc_char[n] = c;
  if (n > 0)
    init_pair (n, dc_fg[n] & 0xf, b & 0x0f);
}

static int
mixer_dc (int n)
{
  if (mixer_do_color)
    attrset (COLOR_PAIR (n) | (dc_fg[n] & 0xfffffff0));
  else
    attrset (dc_attrib[n]);
  
  return dc_char[n];
}

static void
mixer_init_draw_contexts (void)
{
  start_color ();
  
  mixer_init_dc ('.', DC_BACK,		MIXER_WHITE,	MIXER_BLACK,	A_NORMAL); 
  mixer_init_dc ('.', DC_FRAME,		MIXER_DARK_CYAN, MIXER_BLACK,	A_NORMAL);
  mixer_init_dc ('.', DC_TEXT,		MIXER_YELLOW,	MIXER_BLACK,	A_BOLD);
  mixer_init_dc ('.', DC_PROMPT,	MIXER_DARK_CYAN, MIXER_BLACK,	A_BOLD);
  mixer_init_dc ('.', DC_CBAR_FRAME,	MIXER_CYAN,	MIXER_BLACK,	A_NORMAL);
  mixer_init_dc ('M', DC_CBAR_MUTE,	MIXER_CYAN,	MIXER_BLACK,	A_BOLD);
  mixer_init_dc ('-', DC_CBAR_NOMUTE,	MIXER_CYAN,	MIXER_BLACK,	A_NORMAL);
  mixer_init_dc ('x', DC_CBAR_RECORD,	MIXER_DARK_RED,	MIXER_BLACK,	A_BOLD);
  mixer_init_dc ('-', DC_CBAR_NORECORD,	MIXER_GRAY,	MIXER_BLACK,	A_NORMAL);
  mixer_init_dc (' ', DC_CBAR_EMPTY,	MIXER_GRAY,	MIXER_BLACK,	A_DIM);
  mixer_init_dc ('#', DC_CBAR_FULL_1,	MIXER_WHITE,	MIXER_BLACK,	A_BOLD);
  mixer_init_dc ('#', DC_CBAR_FULL_2,	MIXER_GREEN,	MIXER_BLACK,	A_BOLD);
  mixer_init_dc ('#', DC_CBAR_FULL_3,	MIXER_RED,	MIXER_BLACK,	A_BOLD);
  mixer_init_dc ('.', DC_CBAR_NUMBER,	MIXER_YELLOW,	MIXER_BLACK,	A_BOLD);
  mixer_init_dc ('.', DC_CBAR_LABEL,	MIXER_WHITE,	MIXER_BLUE,	A_REVERSE | A_BOLD);
  mixer_init_dc ('.', DC_CBAR_FOCUS_LABEL, MIXER_RED,	MIXER_BLUE,	A_REVERSE | A_BOLD);
  mixer_init_dc ('.', DC_FOCUS,		MIXER_RED,	MIXER_BLACK,	A_BOLD);
}


/* --- error types --- */
typedef enum
{
  ERR_NONE,
  ERR_OPEN,
  ERR_IOCTL,
  ERR_SIGNAL,
  ERR_WINSIZE,
} ErrType;


/* --- prototypes --- */
static void	mixer_abort	(ErrType	error,
				 const char    *err_string)
     __attribute__
((noreturn));


/* --- functions --- */
static void
mixer_abort (ErrType     error,
	     const char *err_string)
{
  if (mixer_window)
  {
    endwin ();
    mixer_window = NULL;
  }
  printf ("\n");

  switch (error)
  {
  case  ERR_OPEN:
    fprintf (stderr,
	     PRGNAME ": failed to open `%s': %s\n",
	     err_string,
	     sys_errlist[errno]);
    break;
  case  ERR_IOCTL:
    fprintf (stderr,
	     PRGNAME ": ioctl (,%s) failed: %s\n",
	     err_string,
	     sys_errlist[errno]);
    break;
  case  ERR_SIGNAL:
    fprintf (stderr,
	     PRGNAME ": aborting due to signal `%s'\n",
	     err_string);
    break;
  case  ERR_WINSIZE:
    fprintf (stderr,
	     PRGNAME ": screen size too small (%dx%d)\n",
	     mixer_max_x,
	     mixer_max_y);
    break;
  default:
    break;
  }
  
  exit (error);
}

static int
mixer_cbar_get_pos (int channel_index,
		    int *x_p,
		    int *y_p)
{
  int x;
  int y;
  
  if (channel_index < mixer_first_vis_channel ||
      channel_index - mixer_first_vis_channel >= mixer_n_vis_channels)
    return FALSE;
  
  channel_index -= mixer_first_vis_channel;
  
  x = mixer_ofs_x + 1;
  y = mixer_ofs_y;
  x += channel_index * (3 + 2 + 3 + 1 + mixer_extra_space);
  y += mixer_max_y / 2;
  y += 6;
  
  if (x_p)
    *x_p = x;
  if (y_p)
    *y_p = y;
  
  return TRUE;
}

static void
mixer_update_cbar (int channel_index)
{
  char string[64];
  char c;
  snd_mixer_channel_info_t cinfo = { 0 };
  snd_mixer_channel_t cdata = { 0 };
  int x, y, i;


  /* get new channel values
   */
  cinfo.channel = channel_index;
  if (ioctl (mixer_fd,
	     SND_MIXER_IOCTL_CHANNEL_INFO,
	     &cinfo) < 0)
    mixer_abort (ERR_IOCTL, "SND_MIXER_IOCTL_CHANNEL_INFO");
  cdata.channel = channel_index;
  if (ioctl (mixer_fd,
	     SND_MIXER_IOCTL_CHANNEL_READ,
	     &cdata) < 0)
    mixer_abort (ERR_IOCTL, "SND_MIXER_IOCTL_CHANNEL_READ");

  /* set new channel values
   */
  if (channel_index == mixer_focus_channel &&
      (mixer_lvolume_delta || mixer_rvolume_delta ||
       mixer_toggle_mute_left || mixer_toggle_mute_right ||
       mixer_balance_volumes ||
       mixer_toggle_record))
  {
    cdata.flags &= ~SND_MIXER_FLG_DECIBEL;
    cdata.left = CLAMP (cdata.left + mixer_lvolume_delta, cinfo.min, cinfo.max);
    cdata.right = CLAMP (cdata.right + mixer_rvolume_delta, cinfo.min, cinfo.max);
    mixer_lvolume_delta = mixer_rvolume_delta = 0;
    if (mixer_balance_volumes)
    {
      cdata.left = (cdata.left + cdata.right) / 2;
      cdata.right = cdata.left;
      mixer_balance_volumes = 0;
    }
    if (mixer_toggle_mute_left)
    {
      if (cdata.flags & SND_MIXER_FLG_MUTE_LEFT)
	cdata.flags &= ~SND_MIXER_FLG_MUTE_LEFT;
      else
	cdata.flags |= SND_MIXER_FLG_MUTE_LEFT;
    }
    if (mixer_toggle_mute_right)
    {
      if (cdata.flags & SND_MIXER_FLG_MUTE_RIGHT)
	cdata.flags &= ~SND_MIXER_FLG_MUTE_RIGHT;
      else
	cdata.flags |= SND_MIXER_FLG_MUTE_RIGHT;
    }
    mixer_toggle_mute_left = mixer_toggle_mute_right = 0;
    if (mixer_toggle_record)
    {
      if (cdata.flags & SND_MIXER_FLG_RECORD)
	cdata.flags &= ~SND_MIXER_FLG_RECORD;
      else
	cdata.flags |= SND_MIXER_FLG_RECORD;
    }
    mixer_toggle_record = 0;
    
    if (ioctl (mixer_fd,
	       SND_MIXER_IOCTL_CHANNEL_WRITE,
	       &cdata) < 0)
      mixer_abort (ERR_IOCTL, "SND_MIXER_IOCTL_CHANNEL_WRITE");
    if (ioctl (mixer_fd,
	       SND_MIXER_IOCTL_CHANNEL_READ,
	       &cdata) < 0)
      mixer_abort (ERR_IOCTL, "SND_MIXER_IOCTL_CHANNEL_READ");
  }

  /* get channel bar position
   */
  if (!mixer_cbar_get_pos (channel_index, &x, &y))
    return;

  /* channel bar name
   */
  mixer_dc (channel_index == mixer_focus_channel ? DC_CBAR_FOCUS_LABEL : DC_CBAR_LABEL);
  cinfo.name[8] = 0;
  for (i = 0; i < 8; i++)
  {
    string[i] = ' ';
  }
  sprintf(string + (8 - strlen (cinfo.name)) / 2, "%s          ", cinfo.name);
  string[8] = 0;
  mvaddstr (y, x, string);
  y--;

  /* current channel values
   */
  mixer_dc (DC_BACK);
  mvaddstr (y, x, "         ");
  mixer_dc (DC_CBAR_NUMBER);
  sprintf (string, "%d", cdata.left);
  mvaddstr (y, x + 3 - strlen (string), string);
  mixer_dc (DC_CBAR_FRAME);
  mvaddch (y, x + 3, '<');
  mvaddch (y, x + 4, '>');
  mixer_dc (DC_CBAR_NUMBER);
  sprintf (string, "%d", cdata.right);
  mvaddstr (y, x + 5, string);
  y--;

  /* left/right bar
   */
  mixer_dc (DC_CBAR_FRAME);
  mvaddstr (y, x, "         ");
  mvaddch (y, x + 2, ACS_LLCORNER);
  mvaddch (y, x + 3, ACS_HLINE);
  mvaddch (y, x + 4, ACS_HLINE);
  mvaddch (y, x + 5, ACS_LRCORNER);
  y--;
  for (i = 0; i < 10; i++)
  {
    mvaddstr (y - i, x, "         ");
    mvaddch (y - i, x + 2, ACS_VLINE);
    mvaddch (y - i, x + 5, ACS_VLINE);
  }
  string[2] = 0;
  for (i = 0; i < 10; i++)
  {
    static int dc[10] = {
      DC_CBAR_FULL_1, DC_CBAR_FULL_1, DC_CBAR_FULL_1, DC_CBAR_FULL_1,
      DC_CBAR_FULL_2, DC_CBAR_FULL_2, DC_CBAR_FULL_2,
      DC_CBAR_FULL_3, DC_CBAR_FULL_3, DC_CBAR_FULL_3,
    };
    if (cdata.left > i * 10)
      mvaddch (y, x + 3,  mixer_dc (dc[i]));
    else
      mvaddch (y, x + 3,  mixer_dc (DC_CBAR_EMPTY));
    if (cdata.right > i * 10)
      mvaddch (y, x + 4,  mixer_dc (dc[i]));
    else
      mvaddch (y, x + 4,  mixer_dc (DC_CBAR_EMPTY));
    y--;
  }

  /* muted?
   */
  mixer_dc (DC_BACK);
  mvaddstr (y, x, "         ");
  c = cinfo.caps & SND_MIXER_CINFO_CAP_MUTE ? '-' : ' ';
  mixer_dc (DC_CBAR_FRAME);
  mvaddch (y, x + 2, ACS_ULCORNER);
  mvaddch (y, x + 3, mixer_dc (cdata.flags & SND_MIXER_FLG_MUTE_LEFT ?
			       DC_CBAR_MUTE : DC_CBAR_NOMUTE));
  mvaddch (y, x + 4, mixer_dc (cdata.flags & SND_MIXER_FLG_MUTE_RIGHT ?
			       DC_CBAR_MUTE : DC_CBAR_NOMUTE));
  mixer_dc (DC_CBAR_FRAME);
  mvaddch (y, x + 5, ACS_URCORNER);
  y--;

  /* record input?
   */
  if (cdata.flags & SND_MIXER_FLG_RECORD)
  {
    mixer_dc (DC_CBAR_RECORD);
    mvaddstr (y, x + 1, "RECORD");
  }
  else if (cinfo.caps & SND_MIXER_CINFO_CAP_RECORD)
    for (i = 0; i < 6; i++)
      mvaddch (y, x + 1 + i, mixer_dc (DC_CBAR_NORECORD));
  else
  {
    mixer_dc (DC_BACK);
    mvaddstr (y, x, "         ");
  }
  y--;
}

static void
mixer_update_cbars (void)
{
  static int o_x = 0;
  static int o_y = 0;
  int i, x, y;

  if (!mixer_cbar_get_pos (mixer_focus_channel, &x, &y))
  {
    if (mixer_focus_channel < mixer_first_vis_channel)
      mixer_first_vis_channel = mixer_focus_channel;
    else if (mixer_focus_channel >= mixer_first_vis_channel + mixer_n_vis_channels)
      mixer_first_vis_channel = mixer_focus_channel - mixer_n_vis_channels + 1;
    mixer_cbar_get_pos (mixer_focus_channel, &x, &y);
  }
  for (i = 0; i < mixer_n_vis_channels; i++)
    mixer_update_cbar (i + mixer_first_vis_channel);

  /* draw focused cbar
   */
  mixer_dc (DC_BACK);
  mvaddstr (o_y, o_x, " ");
  mvaddstr (o_y, o_x + 9, " ");
  o_x = x - 1;
  o_y = y;
  mixer_dc (DC_FOCUS);
  mvaddstr (o_y, o_x, "<");
  mvaddstr (o_y, o_x + 9, ">");
}

static void
mixer_draw_frame (void)
{
  char string[128];
  int i;
  int max_len;

  mixer_dc (DC_FRAME);

  /* corners
   */
  mvaddch (0, 0, ACS_ULCORNER);
  mvaddch (mixer_max_y - 1, 0, ACS_LLCORNER);
  mvaddch (mixer_max_y - 1, mixer_max_x - 1, ACS_LRCORNER);
  mvaddch (0, mixer_max_x - 1, ACS_URCORNER);

  /* lines
   */
  for (i = 1; i < mixer_max_y - 1; i++)
  {
    mvaddch (i, 0, ACS_VLINE);
    mvaddch (i, mixer_max_x - 1, ACS_VLINE);
  }
  for (i = 1; i < mixer_max_x - 1; i++)
  {
    mvaddch (0, i, ACS_HLINE);
    mvaddch (mixer_max_y - 1, i, ACS_HLINE);
  }

  /* program title
   */
  sprintf (string, "%s %s", PRGNAME_UPPER, VERSION);
  max_len = strlen (string);
  mvaddch (0, mixer_max_x / 2 - max_len / 2 - 1, '[');
  mvaddch (0, mixer_max_x / 2 - max_len / 2 + max_len, ']');
  mixer_dc (DC_TEXT);
  mvaddstr (0, mixer_max_x / 2 - max_len / 2, string);

  /* card name
   */
  mixer_dc (DC_PROMPT);
  mvaddstr (1, 2, "Card:");
  mixer_dc (DC_TEXT);
  sprintf (string, "%s", mixer_card_name);
  max_len = mixer_max_x - 2 - 6 - 2;
  if (strlen (string) > max_len)
    string[max_len] = 0;
  mvaddstr (1, 2 + 6, string);
  
  /* device name
   */
  mixer_dc (DC_PROMPT);
  mvaddstr (2, 2, "Chip: ");
  mixer_dc (DC_TEXT);
  sprintf (string, "%s", mixer_device_name);
  max_len = mixer_max_x - 2 - 6 - 2;
  if (strlen (string) > max_len)
    string[max_len] = 0;
  mvaddstr (2, 2 + 6, string);
}

static void
mixer_init (char *device)
{
  static snd_mixer_info_t mixer_info = { 0 };
  static char *card_names[] =
  {
    "<Unknown>",
    "Gus-Classic",
    "Gus-Extreme",
    "Gus-ACE",
    "Gus-Max",
    "Amd-Interwave",
    "SB-10",
    "SB-20",
    "SB-Pro",
    "SB-16",
    "SB-AWE",
    "Ess-es1688",
  };
  static int n_cards = sizeof (card_names) / sizeof (card_names[0]);
  int num;

  /* open mixer device, set to EXACT mode
   */
  mixer_device_file = device;
  mixer_fd = open (mixer_device_file, O_RDONLY);
  if (mixer_fd < 0)
    mixer_abort (ERR_OPEN, mixer_device_file);
  if (ioctl (mixer_fd, SND_MIXER_IOCTL_PVERSION, &num) < 0)
    mixer_abort (ERR_IOCTL, "SND_MIXER_IOCTL_PVERSION");
  num = 1;
  if (ioctl (mixer_fd,
	     SND_MIXER_IOCTL_EXACT,
	     &num ) < 0)
    mixer_abort (ERR_IOCTL, "SND_MIXER_IOCTL_EXACT");

  /* setup global variables
   */
  if (ioctl (mixer_fd,
	     SND_MIXER_IOCTL_INFO,
	     &mixer_info) < 0)
    mixer_abort (ERR_IOCTL, "SND_MIXER_IOCTL_INFO");
  mixer_n_channels = mixer_info.channels;
  mixer_card_name = card_names[mixer_info.type > n_cards ? 0 : mixer_info.type];
  mixer_device_name = mixer_info.name;
}

static int
mixer_iteration (void)
{
  int key;
  int finished = 0;

  key = getch ();
  switch (key)
  {
  case  27:	/* Escape */
    finished = 1;
    break;
  case  9:	/* Tab */
  case  KEY_RIGHT:
  case  'n':
    mixer_focus_channel += 1;
    break;
  case  KEY_LEFT:
  case  'p':
    mixer_focus_channel -= 1;
    break;
  case  KEY_PPAGE:
    mixer_lvolume_delta = 10;
    mixer_rvolume_delta = 10;
    break;
  case  KEY_NPAGE:
    mixer_lvolume_delta = -10;
    mixer_rvolume_delta = -10;
    break;
  case KEY_BEG:
  case KEY_HOME:
    mixer_lvolume_delta = 100;
    mixer_rvolume_delta = 100;
    break;
  case KEY_LL:
  case KEY_END:
    mixer_lvolume_delta = -100;
    mixer_rvolume_delta = -100;
    break;
  case  '+':
    mixer_lvolume_delta = 1;
    mixer_rvolume_delta = 1;
    break;
  case '-':
    mixer_lvolume_delta = -1;
    mixer_rvolume_delta = -1;
    break;
  case  'w':
  case  'W':
  case  KEY_UP:
    mixer_lvolume_delta = 2;
    mixer_rvolume_delta = 2;
    break;
  case 'x':
  case 'X':
  case  KEY_DOWN:
    mixer_lvolume_delta = -2;
    mixer_rvolume_delta = -2;
    break;
  case  'q':
  case  'Q':
    mixer_lvolume_delta = 2;
    break;
  case 'y':
  case 'Y':
  case 'z':
  case 'Z':
    mixer_lvolume_delta = -2;
    break;
  case  'e':
  case  'E':
    mixer_rvolume_delta = 2;
    break;
  case 'c':
  case 'C':
    mixer_rvolume_delta = -2;
    break;
  case  'm':
  case  'M':
    mixer_toggle_mute_left = 1;
    mixer_toggle_mute_right = 1;
    break;
  case  'b':
  case  'B':
  case  '=':
    mixer_balance_volumes = 1;
    break;
  case  '<':
  case  ',':
    mixer_toggle_mute_left = 1;
    break;
  case  '>':
  case  '.':
    mixer_toggle_mute_right = 1;
    break;
  case ' ':
    mixer_toggle_record = 1;
    break;
  }
  mixer_focus_channel = CLAMP (mixer_focus_channel, 0, mixer_n_channels - 1);

  return finished;
}

static void
mixer_signal_handler (int signal)
{
  mixer_abort (ERR_SIGNAL, sys_siglist[signal]);
}

int
main (int    argc,
      char **argv)
{
  char mdevice[80] = "/dev/sndmixer00";
  int opt;
  
  /* parse args
   */
  do
  {
    int card_id = 0;
    int mixer_id = 0;
    
    opt = getopt (argc, argv, "hc:m:" );
    switch (opt)
    {
    case  '?':
    case  'h':
      fprintf (stderr, "%s %s\n", PRGNAME_UPPER, VERSION);
      fprintf (stderr, "Usage: %s [-c <card: 1..8>] [-m <mixer: 1..2>]\n", PRGNAME);
      mixer_abort (ERR_NONE, "");
    case  'c':
      card_id = CLAMP (optarg[0], '1', '8');
      mdevice[13] = card_id - 1;
      break;
    case  'm':
      mixer_id = CLAMP (optarg[0], '1', '2');
      mdevice[14] = mixer_id - 1;
      break;
    }
  }
  while (opt > 0);
  
  /* initialize mixer
   */
  mixer_init (mdevice);

  /* setup signal handlers
   */
  signal (SIGINT,  mixer_signal_handler);
  signal (SIGTRAP, mixer_signal_handler);
  signal (SIGABRT, mixer_signal_handler);
  signal (SIGQUIT, mixer_signal_handler);
  signal (SIGBUS,  mixer_signal_handler);
  signal (SIGSEGV, mixer_signal_handler);
  signal (SIGPIPE, mixer_signal_handler);
  signal (SIGTERM, mixer_signal_handler);

  /* initialize ncurses
   */
  mixer_window = initscr ();
  refresh ();
  mixer_do_color = has_colors ();
  getmaxyx (mixer_window, mixer_max_y, mixer_max_x);
  if (mixer_max_x < 23 ||
      mixer_max_y < 19)
    mixer_abort (ERR_WINSIZE, "");
  mixer_ofs_x = 2;
  mixer_ofs_y = 2;
  mixer_extra_space = 0;
  mixer_n_vis_channels = MIN ((mixer_max_x - 2 * mixer_ofs_x + 1) / (9 + mixer_extra_space),
			      mixer_n_channels);
  mixer_extra_space = ((mixer_max_x - 2 * mixer_ofs_x - 1 - mixer_n_vis_channels * 9.0) /
		       (mixer_n_vis_channels - 1));
  if (mixer_n_vis_channels < mixer_n_channels)
  {
    /* recalc
     */
    mixer_extra_space = MAX (mixer_extra_space, 1);
    mixer_n_vis_channels = MIN ((mixer_max_x - 2 * mixer_ofs_x + 1) / (9 + mixer_extra_space),
				mixer_n_channels);
    mixer_extra_space = ((mixer_max_x - 2 * mixer_ofs_x - 1 - mixer_n_vis_channels * 9.0) /
			 (mixer_n_vis_channels - 1));
  }
  mixer_first_vis_channel = 0;
  mixer_init_draw_contexts ();
  mixer_dc (DC_BACK);

  /* react on key presses
   * and draw window
   */
  keypad (mixer_window, TRUE);
  leaveok (mixer_window, TRUE);
  cbreak ();
  noecho ();
  do
  {
    mixer_update_cbars ();
    mixer_draw_frame ();
    refresh ();
  }
  while (!mixer_iteration ());

  mixer_abort (ERR_NONE, "");
};


