/*
 *  A simple DB meter program (experimental)
 *
 *  AUTHOR: Savio Lam (lam836@cs.cuhk.hk)
 */

/*   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 <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>
#ifdef __STDC__
#include <errno.h>
#endif /* __STDC__ */

#define BUF_SIZE 10000
#define QUEUE_SIZE 1000

int queue[QUEUE_SIZE], front = 0, end = 0, write_lock;

void print_bar(int);
void write_finished(int);

int main(int argc, char *argv[])
{
  char buf[BUF_SIZE];
  int i, j, fd = 0, bytes_read, remain = 0, interval_size, first_enter = 1, temp;
  struct itimerval value, ovalue = {0, 0, 0, 0};

  if (argc < 3 || argc > 4) {
    fprintf(stderr, "\nUsage: dbmeter <infile> <samplerate> [outfile]\n");
    exit(-1);
  }

  /* Update meter every 1/10th second (every samplerate/10 samples) */
  interval_size = atoi(argv[2]) / 10;

  if (strcmp("-", argv[1]))
    if ((fd = open(argv[1], O_RDONLY, 0)) == -1) {
      perror(argv[1]);
      exit(-1);
    }

  if ((bytes_read = read(fd, buf, BUF_SIZE)) == -1) {
    perror(argv[1]);
    exit(-1);
  }
  if (argc == 4 && !strcmp(argv[3], "-"))
    write(1, buf, bytes_read);

  while (bytes_read) {
    if (remain) {
      for (i = 0; i < remain; i++)
        temp += abs((unsigned char) buf[i]-0x80);
      /* Wait until queue not full */
      while ((end == QUEUE_SIZE && front == 0) || end == front-1);
      queue[end++] = temp / interval_size;
      end %= QUEUE_SIZE;
    }
    for (i = remain; i < bytes_read; i += interval_size) {
      temp = 0;
      for (j = 0; j < interval_size && (i+j) < bytes_read; j++)
        temp += abs((unsigned char) buf[i+j]-0x80);
      if (!(remain = interval_size - j)) {
        /* Wait until queue not full */
        while ((end == QUEUE_SIZE && front == 0) || end == front-1);
        /* Save average amplitude in queue */
        queue[end++] = temp / interval_size;
        end %= QUEUE_SIZE;
      }
    }

    if (first_enter) {
      first_enter = 0;
      /* Schedule to update DB meter every 1/10th second */
      value.it_interval.tv_sec = 0;
      value.it_interval.tv_usec = 1000000 / 10;
      value.it_value.tv_sec = 0;
      value.it_value.tv_usec = 1000000 / 10;

      signal(SIGALRM, print_bar);
      setitimer(ITIMER_REAL, &value, &ovalue);
    }

    do {
      bytes_read = read(fd, buf, BUF_SIZE);
      if (bytes_read == -1 && errno != EINTR) {
        perror(argv[1]);
        exit(-1);
      }
    } while (bytes_read == -1 && errno == EINTR);
    if (!strcmp(argv[3], "-")) {
      write_lock = 1;               
      signal(SIGCHLD, write_finished);
      /* Write to standard output */
      if (!fork()) {
        /* Child */
        if (write(1, buf, bytes_read) == -1) {
          perror("stdout write");
          exit(-1);
        }
        exit(0);
      }
      /* Wait for child to finish writing */
      while(write_lock);
    }
  }  /* end while(bytes_read) */

  /* Wait for playing to finish */
  while (front != end);
  value.it_interval.tv_sec = 0;
  value.it_interval.tv_usec = 0;
  value.it_value.tv_sec = 0;
  value.it_value.tv_usec = 0;
  setitimer(ITIMER_REAL, &value, &ovalue);

  close(fd);
}

/*
 * Update DB meter (called every 1/10th second)
 */
void print_bar(int dummy)
{
  int i, value;
  static prev_value = -1;

  if (front == end) {
    fprintf(stderr, "\nqueue empty!\n");
    exit(-1);
  }

  /* Take saved average amplitude from queue and update DB meter */
  value = queue[front++];
  front %= QUEUE_SIZE;

  if (value != prev_value) {
    prev_value = value;
    if (value < 0 || value > 128)
      fprintf(stderr, "Wrong!: %d        \n", value); /* Value out of range */

    fprintf(stderr, "%c[1;1H", 27);    /* home cursor */
    for (i = 0; i < 80; i++)
      if (i < value)
        fprintf(stderr, "%c[1m#%c[0m", 27, 27);  /* print a '#' in bold */
      else
        fprintf(stderr, "#");    /* print a '#' unbolded */
    fprintf(stderr, "\n");
  }
  signal(SIGALRM, print_bar);
}

/*
 * Release write lock when child finishes writing
 */
void write_finished(int dummy)
{
  write_lock = 0;
}
