/* file = gmeat.c --- the meat of the grab program */ 
/* Nigel Ward, University of Tokyo, April 1994 */

#include "cheap.h"
#include <multimedia/ulaw2linear.h>
#include <sun/audioio.h>
#include <sys/ioctl.h> 
#include <fcntl.h>     /* for O_NDELAY */

/* note that the signal-start/signal-end tests have not been tested
   except with chunk_size = 512, etc. etc. */


#define NOISE_TEST_LEN 2048     /* samples */
  /* number of samples to include before/after the endpoint of the monitor-region
     in which the signal start/end was detected.  Values chosen by trial and error */
#define PRE_LEEWAY 4096    
#define POST_LEEWAY -4096
#define MONITOR_WIDTH 5120      /* about 600 ms */
#define MIN_INTERVAL 128        /* used to determine array sizes */
#define MAX_CHUNKS_PER_MONITOR (MONITOR_WIDTH / MIN_INTERVAL)

#define REVERB_TIME 1024 /* samples */

static int buffer_size;
static int chunk_size;
static int dev_audio;
static int chunk_counter;
static int chunks_per_monitor;
/* circular buffer describing recent chunks: 0 = silence, 1 = signal */
static int status[MAX_CHUNKS_PER_MONITOR];
static int start_detected, end_detected; 
static float noise_level;
static int sig_start, sig_end;   /* start and end of the signal, as seen so far */
static int cummulative_bytes;

/*-----------------------------------------------------------------------------*/
/* technically, I think I should be squaring to get energy, but this works ok */
short convert_to_energy(raw)       char raw;
{
  /* fprintf(stderr," %d %d\n"   , raw, abs(audio_u2s(raw))); */
  return(abs(audio_u2s(raw)));
}

/*-----------------------------------------------------------------------------*/

/* The idea behind this is that you can't always be sure that an AD plugged to a 
   mike in a quiet room will give data values close to 0.
   There may be some systematic bias, with everything shifted by some amount.
   Hence this code.  I don't know if "liveliness" really means power or energy or
   something else, but "more lively" and "signal present" seem to correlate,
   and that's all I care about */
float liveliness(raw_data, start, length)    char raw_data[];  int start, length;
{
  int i; float raw_sum, avg, life;
  short converted[MAX_SAMPLES]; 
  raw_sum = 0.0;
  for (i = start; i < start + length; i++) {
    converted[i] = convert_to_energy (raw_data[i]); 
    raw_sum += converted[i]; }
  avg = raw_sum / length;
  life = 0.0;
  for (i = start; i < start + length; i++) {
    life += abs(converted[i] - avg); }
  return(life / length);
}

/*-----------------------------------------------------------------------------*/
/* measure ambient noise level */
/* threshold specifies how much signal is assumed to exceed background noise */
measure_noise_level(monitorp, threshold)        int monitorp;   float threshold;
{
  char background[NOISE_TEST_LEN];

  sleep(1);  /* !! to let any beeps fade away */

  dev_audio = open("/dev/audio", O_RDONLY | O_NDELAY); 
  if (dev_audio == -1 && errno == EBUSY) {
    fprintf(stderr, "grab: some other process is reading from /dev/audio !!\n");
    exit(STRANGE); }
    
  (void) read (dev_audio, &(background[0]), NOISE_TEST_LEN);
  noise_level = threshold * liveliness(background, 0, NOISE_TEST_LEN);
  if (monitorp) fprintf(stderr,"noise_threshold_level %f\n", noise_level); 
}

/* ----------------------------------------------------------------------------- */
/* set some global vars */
init_grab(buffer_sz, chunk_sz)      int buffer_sz, chunk_sz;
{
  int i;
  char discard[REVERB_TIME];

  buffer_size = buffer_sz;
  chunk_size = chunk_sz;
  chunks_per_monitor = (int) MONITOR_WIDTH / chunk_size;

  chunk_counter = 0;
  cummulative_bytes = 0;
  start_detected = FALSE;
  end_detected = FALSE;
  sig_start = -1;           /* start not detected yet */
  sig_end = buffer_size;    /* end not detected yet */
  for (i = 0; i < chunks_per_monitor; i++) {
    status[i] = FALSE; }

  /* beep(); */

  /* discard input data corrupted by beep (short beep, but room has reverb) */
  /* bytes_read = read(dev_audio, &discard, REVERB_TIME); */
}


/*-----------------------------------------------------------------------------*/
clean_up_grab()
{  close(dev_audio); }

/*-----------------------------------------------------------------------------*/
/* Each time this is called, it reads in a chunk of audio data */
grab_some(raw_data, signal_start_ptr, current_end_ptr, monitorp)
     char raw_data[];   int *signal_start_ptr, *current_end_ptr; int monitorp;
{
  int bytes_read;
  float avg_energy;

  if(cummulative_bytes + chunk_size >= buffer_size) {
    fprintf(stderr, "\n gmeat: recording terminated due to buffer size limit\n");
    *signal_start_ptr = 0;
    *current_end_ptr = cummulative_bytes;
    return(TRUE); }

  test_for_data_loss(dev_audio);
  
  bytes_read = read (dev_audio, &(raw_data[cummulative_bytes]), chunk_size);
  cummulative_bytes += bytes_read;
  chunk_counter++;
  /* fprintf(stderr,"bytes read = %d\n", bytes_read); */

  avg_energy = liveliness(raw_data, cummulative_bytes - bytes_read, bytes_read);

  if (monitorp) fprintf(stderr, " noise_threshold %.2f, current_avg %4.2f\n",
			noise_level, avg_energy);
  if (avg_energy > noise_level) {
    fprintf(stderr, "!");
    status[chunk_counter % chunks_per_monitor] = TRUE; }
  else {
    fprintf(stderr, ".");
    status[chunk_counter % chunks_per_monitor] = FALSE; }
  if (monitorp) { print_monitor();} 

  if (!start_detected) {
    /* test whether the signal starts here */
    if (live_chunks_in_status() > chunks_per_monitor / 2) {
      start_detected = TRUE;
      sig_start = (cummulative_bytes - PRE_LEEWAY > 0) ?
	cummulative_bytes - PRE_LEEWAY : 0;
      fprintf(stderr," \n start of signal infered at %d\n", sig_start); 
    } }
  else if (!end_detected) {
    /* test if signal ends here */
    if (live_chunks_in_status() <= chunks_per_monitor / 6) {
      end_detected = TRUE;
      sig_end = cummulative_bytes + POST_LEEWAY > buffer_size ? 
	buffer_size :  cummulative_bytes + POST_LEEWAY;
      fprintf(stderr, "\n end of signal infered at %d\n", sig_end);
    } }
  else {
    /* we've already detected end, and are just gathering a few more samples */;}
  
  *signal_start_ptr = sig_start;
  *current_end_ptr = cummulative_bytes;
  if (cummulative_bytes >= sig_end)
    return (TRUE);      /* finished */
  else return(FALSE);   /* not yet finished */
}

/*-----------------------------------------------------------------------------*/
int live_chunks_in_status()
{
  int k, count;
  count = 0;
  for (k = 0; k < chunks_per_monitor; k++) { count += status[k]; }
  return(count); }

/*-----------------------------------------------------------------------------*/
print_monitor()
{ int k;
  for (k = 0; k < chunks_per_monitor; k++) {fprintf(stderr,  "%d", status[k]);}
  fprintf(stderr, " "); } 

/*-----------------------------------------------------------------------------*/
echo_signal(data, signal_start, signal_length)
     char data[]; int signal_start, signal_length;
{
  usleep(750000); 
  beep();
  dev_audio = open_audio_write();
  (void) write(dev_audio, data + signal_start, signal_length);
  close(dev_audio);
  beep();
}

/*-----------------------------------------------------------------------------*/
test_for_data_loss(dev_audio)        int dev_audio;
{
  struct audio_info ai;
  (void) ioctl(dev_audio , AUDIO_GETINFO, &ai);
  if (ai.record.error) {
    fprintf(stderr, " *** audio data got lost ***\n");
    ai.record.error = FALSE;
    (void) ioctl(dev_audio , AUDIO_SETINFO, &ai); }
}
/*-----------------------------------------------------------------------------*/
