/*
 *  Linux Audio Mixer - with ncurses interface
 *
 *  AUTHOR: Savio Lam (lam836@cs.cuhk.hk)
 *
 *  HISTORY:
 *
 *  23/10/93 - Version 0.1 released.
 *
 *  27/10/93 - Doesn't assume existence of any device (volume, bass, etc).
 *           - Some minor bug fixes.
 *
 *  30/10/93 - Some bug fixes to work correctly with SB Pro.
 *           - Should now work correctly when multiple copies are run
 *             simutaneously.
 *
 *  28/11/93 - Version 0.2 released.
 */

/*   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 General Public License
     along with this program; if not, write to the Free Software
     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */


#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <ncurses.h>

#define BG_COLOR 1
#define TITLE_COLOR 2
#define BOTTOM_LINE_COLOR 3
#define PANEL_COLOR 4
#define BORDER_COLOR 5
#define HANDLE_COLOR 6
#define TRACK_COLOR 7
#define MARKING1_COLOR 8
#define MARKING2_COLOR 9
#define MARKING3_COLOR 10
#define MARKING4_COLOR 11
#define MARKING5_COLOR 12
#define MARKING6_COLOR 13
#define REC_LED_COLOR 14
#define PLAY_LED_COLOR 15
#define ACTIVE_COLOR 16
#define INACTIVE_COLOR 17
#define CONTROL_SPACING 8
#define INCREMENT 2


void InitColors(void);
void DrawHandles(int dev);
void Adjust(void);


/*
 * Global variables
 */
WINDOW *w_panel;
int devmask = 0, recmask = 0, recsrc = 0, stereodevs = 0, mixer_fd;
char *devname[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
int level[SOUND_MIXER_NRDEVICES];

int main()
{
  int i, j;

  if ((mixer_fd = open("/dev/mixer", O_RDWR)) < 0) {
    fprintf(stderr, "Error opening /dev/mixer.");
    exit(1);
  }
  if (ioctl(mixer_fd, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) {
    perror("SOUND_MIXER_READ_DEVMASK");
    exit(-1);
  }
  if (ioctl(mixer_fd, SOUND_MIXER_READ_RECMASK, &recmask) == -1) {
    perror("SOUND_MIXER_READ_RECMASK");
    exit(-1);
  }
  if (ioctl(mixer_fd, SOUND_MIXER_READ_RECSRC, &recsrc) == -1) {
    perror("SOUND_MIXER_READ_RECSRC");
    exit(-1);
  }
  if (ioctl(mixer_fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs) == -1) {
    perror("SOUND_MIXER_READ_STEREODEVS");
    exit(-1);
  }


  if (!devmask) {
    fprintf(stderr, "No device found.");
    exit(-1);
  }

  initscr();
  leaveok(stdscr, TRUE);
  keypad(stdscr, TRUE);
  cbreak();
  noecho();

#ifdef COLOR_CHECK
  if (!has_colors())
    fprintf(stderr, "WARNING: no color capability detected.");
#endif

  InitColors();
  move(0, 0);
  attrset(COLOR_PAIR(TITLE_COLOR) | A_BOLD | A_ALTCHARSET);
  printw("%c%c%c Linux Audio Mixer version 0.2"
         "                               by Savio Lam %c%c%c",
         178, 177, 176, 176, 177, 178);
  attrset(COLOR_PAIR(BOTTOM_LINE_COLOR) | A_BOLD | A_ALTCHARSET);
  move(LINES-1, 0);
  addstr("  PgUp/PgDn - Prev/Next  SPACE - Rec/Play  UP/DOWN - Higher/Lower"
         "  ESC - Exit   ");
  refresh();

  w_panel = newwin(LINES - 2, COLS, 1, 0);
  wattrset(w_panel, COLOR_PAIR(PANEL_COLOR) | A_DIM | A_ALTCHARSET);
  wclear(w_panel);
  wattrset(w_panel, COLOR_PAIR(BORDER_COLOR) | A_BOLD | A_ALTCHARSET);
  wborder(w_panel, 219, 219, 223, 220, 219, 219, 219, 219);

/* Draw vertical controls */
  for (i = 0; i < SOUND_MIXER_NRDEVICES-3; i++) {
    for (j = 3; j < 14; j++) {
      if (j < 7)
        wattrset(w_panel, COLOR_PAIR(MARKING1_COLOR) | A_BOLD | A_ALTCHARSET);
      else
        if (j < 11) 
          wattrset(w_panel, COLOR_PAIR(MARKING2_COLOR) | A_DIM | A_ALTCHARSET);
        else
          wattrset(w_panel, COLOR_PAIR(MARKING3_COLOR) | A_DIM | A_ALTCHARSET);
      wmove(w_panel, j, i*CONTROL_SPACING+6);
      wprintw(w_panel, "%c %c", 249, 249);
      wattrset(w_panel, COLOR_PAIR(TRACK_COLOR) | A_DIM | A_ALTCHARSET);
      wmove(w_panel, j, i*CONTROL_SPACING+7);
      waddch(w_panel, 179);
    }
    if ((1 << (i+3)) & devmask) {
      DrawHandles(i+3);
    /* Print Rec/Play LED indicators */
      if ((1 << (i+3)) & recmask) {
        wattrset(w_panel,
          COLOR_PAIR(((1 << (i+3)) & recsrc ? REC_LED_COLOR : PLAY_LED_COLOR))
          | A_BOLD | A_ALTCHARSET);
        wmove(w_panel, 2, i*CONTROL_SPACING+7);
        waddch(w_panel, 223);
      }
      wattrset(w_panel, COLOR_PAIR(INACTIVE_COLOR) | A_BOLD | A_ALTCHARSET);
    }
    else
      wattrset(w_panel, COLOR_PAIR(INACTIVE_COLOR) | A_DIM | A_ALTCHARSET);
  /* Draw vertical control labels */
    wmove(w_panel, 15, i*CONTROL_SPACING+6);
    waddstr(w_panel, devname[i+3]);
  }

/* Draw horizontal controls */
  for (i = 1; i < 12; i++) {
    if (i < 5)
      wattrset(w_panel, COLOR_PAIR(MARKING6_COLOR) | A_DIM | A_ALTCHARSET);
    else
      if (i < 9)
        wattrset(w_panel, COLOR_PAIR(MARKING5_COLOR) | A_BOLD | A_ALTCHARSET);
      else
        wattrset(w_panel, COLOR_PAIR(MARKING4_COLOR) | A_BOLD | A_ALTCHARSET);
    wmove(w_panel, 18, i*2+10);
    waddch(w_panel, 254);
    wmove(w_panel, 17, i*2+50);
    waddch(w_panel, 254);
  }
  wattrset(w_panel, COLOR_PAIR(TRACK_COLOR) | A_DIM | A_ALTCHARSET);
  for (i = 1; i < 24; i++) {
    wmove(w_panel, 19, i+10);
    waddch(w_panel, 196);
    wmove(w_panel, 18, i+50);
    waddch(w_panel, 196);
    wmove(w_panel, 20, i+50);
    waddch(w_panel, 196);
  }

  if (devmask & SOUND_MASK_VOLUME) {
    DrawHandles(SOUND_MIXER_VOLUME);
    wattrset(w_panel, COLOR_PAIR(INACTIVE_COLOR) | A_BOLD | A_ALTCHARSET);
  }
  else
    wattrset(w_panel, COLOR_PAIR(INACTIVE_COLOR) | A_DIM | A_ALTCHARSET);
  wmove(w_panel, 19, 4);
  waddstr(w_panel, "Volume");

  if (devmask & SOUND_MASK_BASS) {
    DrawHandles(SOUND_MIXER_BASS);
    wattrset(w_panel, COLOR_PAIR(INACTIVE_COLOR) | A_BOLD | A_ALTCHARSET);
  }
  else
    wattrset(w_panel, COLOR_PAIR(INACTIVE_COLOR) | A_DIM | A_ALTCHARSET);
  wmove(w_panel, 18, 44);
  waddstr(w_panel, "Bass");

  if (devmask & SOUND_MASK_TREBLE) {
    DrawHandles(SOUND_MIXER_TREBLE);
    wattrset(w_panel, COLOR_PAIR(INACTIVE_COLOR) | A_BOLD | A_ALTCHARSET);
  }
  else
    wattrset(w_panel, COLOR_PAIR(INACTIVE_COLOR) | A_DIM | A_ALTCHARSET);
  wmove(w_panel, 20, 44);
  waddstr(w_panel, "Treble");

  wrefresh(w_panel);
  Adjust(); 

  close(mixer_fd);
  clear();
  refresh();
  endwin();

  return 0;
}
/* End of main() */

void InitColors(void)
{
  start_color();
  init_pair(BG_COLOR, COLOR_WHITE, COLOR_BLACK);
  init_pair(TITLE_COLOR, COLOR_WHITE, COLOR_RED);
  init_pair(BOTTOM_LINE_COLOR, COLOR_CYAN, COLOR_BLUE);
  init_pair(PANEL_COLOR, COLOR_BLACK, COLOR_BLACK);
  init_pair(BORDER_COLOR, COLOR_BLACK, COLOR_BLACK);
  init_pair(HANDLE_COLOR, COLOR_RED, COLOR_BLACK);
  init_pair(TRACK_COLOR, COLOR_WHITE, COLOR_BLACK);
  init_pair(MARKING1_COLOR, COLOR_CYAN, COLOR_BLACK);
  init_pair(MARKING2_COLOR, COLOR_CYAN, COLOR_BLACK);
  init_pair(MARKING3_COLOR, COLOR_BLUE, COLOR_BLACK);
  init_pair(MARKING4_COLOR, COLOR_YELLOW, COLOR_BLACK);
  init_pair(MARKING5_COLOR, COLOR_RED, COLOR_BLACK);
  init_pair(MARKING6_COLOR, COLOR_RED, COLOR_BLACK);
  init_pair(REC_LED_COLOR, COLOR_RED, COLOR_BLACK);
  init_pair(PLAY_LED_COLOR, COLOR_GREEN, COLOR_BLACK);
  init_pair(ACTIVE_COLOR, COLOR_YELLOW, COLOR_WHITE);
  init_pair(INACTIVE_COLOR, COLOR_WHITE, COLOR_BLACK);
}
/* End of InitColors() */


void DrawHandles(int dev)
{
  int temp;

  if (ioctl(mixer_fd, MIXER_READ(dev), &temp) == -1) {
    perror("MIXER_READ");
    endwin();
    exit(-1);
  }
  if ((1 << dev) & stereodevs) {
    /* Take the average of left and right settings */
    temp = ((temp & 0x7F) + ((temp >> 8) & 0x7F)) / 2;
    temp |= (temp << 8);
    /* Reset so that left and right settings are equal */
    if (ioctl(mixer_fd, MIXER_WRITE(dev), &temp) == -1) {
      perror("MIXER_WRITE");
      endwin();
      exit(-1);
    }
  }
  level[dev] = temp;
  wattrset(w_panel, COLOR_PAIR(HANDLE_COLOR) | A_BOLD | A_ALTCHARSET);
  switch (dev) {
    case SOUND_MIXER_VOLUME:
      wmove(w_panel, 19, 12 + ((temp & 0x7F)+2) / 5);
      waddch(w_panel, 221);
      break;
    case SOUND_MIXER_BASS:
      wmove(w_panel, 18, 52 + ((temp & 0x7F)+2) / 5);
      waddch(w_panel, 221);
      break;
    case SOUND_MIXER_TREBLE:
      wmove(w_panel, 20, 52 + ((temp & 0x7F)+2) / 5);
      waddch(w_panel, 221);
      break;
    default:
      wmove(w_panel, 13 - (((temp & 0x7F)+5) / 10), (dev-3)*CONTROL_SPACING+7);
      waddch(w_panel, 219);
  }
}
/* End of DrawHandles() */


void Adjust(void)
{
  int key, incr, dir, dev, temp, temp1;

/* Find first existing device */
  for (dev = 0; !(devmask & (1 << dev)); dev++);
/* Highlight device label */
  wattrset(w_panel, COLOR_PAIR(ACTIVE_COLOR) | A_BOLD | A_ALTCHARSET);
  switch (dev) {
    case SOUND_MIXER_VOLUME:
      wmove(w_panel, 19, 4);
      waddstr(w_panel, "Volume");
      break;
    case SOUND_MIXER_BASS:
      wmove(w_panel, 18, 44);
      waddstr(w_panel, "Bass");
      break;
    case SOUND_MIXER_TREBLE:
      wmove(w_panel, 20, 44);
      waddstr(w_panel, "Treble");
      break;
    default:
      wmove(w_panel, 15, (dev-3)*CONTROL_SPACING+6);
      waddstr(w_panel, devname[dev]);
  }

  while (1) {
    wmove(w_panel, 2, 2);
    wrefresh(w_panel);
    key = getch();
    incr = 0;
    dir = 0;
    switch (key) {
      case 27:          /* Escape */
        return;
      case KEY_PPAGE:    /* Prev Page */
        dir = -1;
      case 9:            /* TAB */
      case KEY_NPAGE:    /* Next Page */
        if (!dir)    /* if not Prev Page pressed */
          dir = 1; 
      /* Un-highlight current device label */
        wattrset(w_panel, COLOR_PAIR(INACTIVE_COLOR) | A_BOLD | A_ALTCHARSET);
        switch (dev) {
          case SOUND_MIXER_VOLUME:
            wmove(w_panel, 19, 4);
            waddstr(w_panel, "Volume");
            break;
          case SOUND_MIXER_BASS:
            wmove(w_panel, 18, 44);
            waddstr(w_panel, "Bass");
            break;
          case SOUND_MIXER_TREBLE:
            wmove(w_panel, 20, 44);
            waddstr(w_panel, "Treble");
            break;
          default:
            wmove(w_panel, 15, (dev-3)*CONTROL_SPACING+6);
            waddstr(w_panel, devname[dev]);
        }

        do {    /* Switch to next existing device */
          if (dir == 1) {
            dev++;
            if (dev > SOUND_MIXER_NRDEVICES-1)
              dev = 0;
          }
          else {
            dev--;
            if (dev < 0)
              dev = SOUND_MIXER_NRDEVICES-1;
          }
        } while (!((1 << dev) & devmask));

      /* Highlight new device label */
        wattrset(w_panel, COLOR_PAIR(ACTIVE_COLOR) | A_BOLD | A_ALTCHARSET);
        switch (dev) {
          case SOUND_MIXER_VOLUME:
            wmove(w_panel, 19, 4);
            waddstr(w_panel, "Volume");
            break;
          case SOUND_MIXER_BASS:
            wmove(w_panel, 18, 44);
            waddstr(w_panel, "Bass");
            break;
          case SOUND_MIXER_TREBLE:
            wmove(w_panel, 20, 44);
            waddstr(w_panel, "Treble");
            break;
          default:
            wmove(w_panel, 15, (dev-3)*CONTROL_SPACING+6);
            waddstr(w_panel, devname[dev]);
        }
        break;
      case ' ':    /* Space */
      /* Print Rec/Play LED indicators */
        if ((1 << dev) & recmask) {
          if (recsrc & (1 << dev))
            recsrc &= ~(1 << dev);    /* Turn off recording */
          else
            recsrc |= (1 << dev);     /* Trun on recording */
          if (ioctl(mixer_fd, SOUND_MIXER_WRITE_RECSRC, &recsrc) == -1) {
            perror("SOUND_MIXER_WRITE_RECSRC");
            endwin();
            exit(-1);
          }
          wattrset(w_panel, COLOR_PAIR(((1 << dev) & recsrc
              ? REC_LED_COLOR : PLAY_LED_COLOR)) | A_BOLD | A_ALTCHARSET);
          wmove(w_panel, 2, (dev-3)*CONTROL_SPACING+7);
          waddch(w_panel, 223);
        }
        break;
      case '+':         /* '+'/UP/RIGHT */
      case KEY_UP:
      case KEY_RIGHT:
        incr = INCREMENT;
      case '-':         /* '-'/DOWN/LEFT */
      case KEY_DOWN:
      case KEY_LEFT:
        if (!incr)    /* if not '+'/UP/RIGHT pressed */
          incr = -INCREMENT;
        temp = level[dev] & 0x7F;
        temp1 = temp;
        temp += incr;
        if (incr > 0) {      /* Increase */
          if (temp > 100)
            temp = 100;
        }
        else                 /* Decrease */
          if (temp < 0)
            temp = 0;
        temp |= (temp << 8);
        level[dev] = temp;
        if (ioctl(mixer_fd, MIXER_WRITE(dev), &temp) == -1) {
          perror("MIXER_WRITE");
          endwin();
          exit(-1);
        }
        switch (dev) {
          case SOUND_MIXER_VOLUME:
          /* Erase handle from old position */
            wattrset(w_panel, COLOR_PAIR(TRACK_COLOR) | A_ALTCHARSET);
            wmove(w_panel, 19, 12 + (temp1+2) / 5);
            waddch(w_panel, 196);
          /* Draw handle at new position */
            wattrset(w_panel, COLOR_PAIR(HANDLE_COLOR) | A_BOLD | A_ALTCHARSET);
            wmove(w_panel, 19, 12 + ((level[dev] & 0x7F)+2) / 5);
            waddch(w_panel, 221);
            break;
          case SOUND_MIXER_BASS:
          /* Erase handle from old position */
            wattrset(w_panel, COLOR_PAIR(TRACK_COLOR) | A_ALTCHARSET);
            wmove(w_panel, 18, 52 + (temp1+2) / 5);
            waddch(w_panel, 196);
          /* Draw handle at new position */
            wattrset(w_panel, COLOR_PAIR(HANDLE_COLOR) | A_BOLD | A_ALTCHARSET);
            wmove(w_panel, 18, 52 + ((level[dev] & 0x7F)+2) / 5);
            waddch(w_panel, 221);
            break;
          case SOUND_MIXER_TREBLE:
          /* Erase handle from old position */
            wattrset(w_panel, COLOR_PAIR(TRACK_COLOR) | A_ALTCHARSET);
            wmove(w_panel, 20, 52 + (temp1+2) / 5);
            waddch(w_panel, 196);
          /* Draw handle at new position */
            wattrset(w_panel, COLOR_PAIR(HANDLE_COLOR) | A_BOLD | A_ALTCHARSET);
            wmove(w_panel, 20, 52 + ((level[dev] & 0x7F)+2) / 5);
            waddch(w_panel, 221);
            break;
          default:
          /* Erase handle from old position */
            wattrset(w_panel, COLOR_PAIR(TRACK_COLOR) | A_ALTCHARSET);
            wmove(w_panel, 13 - ((temp1+5) / 10),
              (dev-3)*CONTROL_SPACING+7);
            waddch(w_panel, 179);
          /* Draw handle at new position */
            wattrset(w_panel, COLOR_PAIR(HANDLE_COLOR) | A_BOLD | A_ALTCHARSET);
            wmove(w_panel, 13 - (((level[dev] & 0x7F)+5) / 10),
              (dev-3)*CONTROL_SPACING+7);
            waddch(w_panel, 219);
        }
        break;
    }
  }
}
/* End of Adjust() */
