/*
sd  --  silence detector

Algorithm:
Use as input the average of the absolute buffer values.  The threshold
is set to be the minimum average plus 'hysteresis'.  If the packet
average is greater than the threshold for 'interval' packets, the
'avg' (minimum average) value is incremented by one, but not beyond
'max_avg'.

Return TRUE if average is less than threshold.  In that case, update
the minimum average, if necessary.  Silence detection copied almost
verbatim from VT ((c) isi).

Copyright 1995 by Henning Schulzrinne; all rights reserved
*/

#include "types.h"
#include <string.h>
#include <stdlib.h>  /* atoi(), malloc() */
#include <tcl.h>
#define DEBUG 0

static char rcsid[] = "$Id: sd.c,v 1.2 1995/03/08 13:42:06 hgs Exp $";

typedef struct {
  int enable;           /* enable silence detector */
  float hyst;           /* non-silent if this much above long-term average */
  float avg;            /* current average energy */
  float max_threshold;  /* maximum silence threshold */
  float min_threshold;  /* minimum silence threshold */
  float echo_threshold; /* additional energy if echo cancellation is on */ 
  float threshold;      /* current threshold */
  float increase;       /* threshold increase in dB/packet */
} sd_t;

#define SD_PRIVATE
#include "sd.h"

static double const DB_RANGE = 60.;
/* absolute dB to 0..1 */
#define DB_ABS(x) (1. + (x)/DB_RANGE)
/* relative dB to 0..1 offset */
#define DB_REL(x) ((x)/DB_RANGE)

/*
* Configure silence detector (enable, tc, low, high).
*/
char *sd_trace(ClientData client, Tcl_Interp *interp, 
  char *name1, char *name2, int flags)
{
  sd_t *s = (sd_t *)client;
  char *value = Tcl_GetVar2(interp, name1, name2, flags & TCL_GLOBAL_ONLY);
  float v = atof(value);

  if (strcmp(name2, "enable") == 0) {
    s->enable = v;
  }
  else if (strcmp(name2, "echo") == 0) {
    s->echo_threshold = DB_REL(v);
  }
  else if (strcmp(name2, "hysteresis") == 0) {
    s->hyst = DB_REL(v);
  }
  else if (strcmp(name2, "maximum") == 0) {
    s->max_threshold = s->threshold = DB_ABS(v);
  }
  else if (strcmp(name2, "minimum") == 0) {
    s->min_threshold = s->threshold = DB_ABS(v);
  }
  else if (strcmp(name2, "increase") == 0) {
    s->increase = DB_REL(v);
  }
  return NULL;
} /* sd_trace */


/*
* Initialize sd structure to reasonable defaults.
* Enable silence detector by default.
*/
sd_t *sd_init(void)
{
  sd_t *s = (sd_t *)malloc(sizeof(sd_t));

  if (!s) return 0;
  s->max_threshold  = DB_ABS(-20.);
  s->min_threshold  = DB_ABS(-45.);
  s->echo_threshold = DB_REL(3.);
  s->threshold      = s->max_threshold;
  s->avg            = 0;
  s->increase       = DB_REL(0.06);
  s->hyst           = DB_REL(3.);
  s->enable         = 1;
  return s;
} /* sd_init */


/*
* Actual silence detection. If not enabled, always return FALSE.
* Flag 'echo' indicates whether echo suppression is on.
* Return TRUE if silent.
*/
int sd(sd_t *s, audio_stats_t *as, int echo)
{
  float energy = as->c[0].peak;
  int silent;

  if (!s->enable) return FALSE;
  if (energy > s->threshold + (echo ? s->echo_threshold : 0)) {   /* non-silence: adjust threshold upwards */
    if (s->threshold < s->max_threshold) s->threshold += s->increase;
    silent = 0;
  }
  else { /* silence: adjust min. average */
    s->avg = s->avg + 0.0625 * (energy - s->avg);
    s->threshold = s->avg + s->hyst;
    if (s->threshold > s->max_threshold) s->threshold = s->max_threshold;
    else if (s->threshold < s->min_threshold) s->threshold = s->min_threshold;
    silent = 1;
  }
#if DEBUG
  printf("%c v=%6.2f th=%6.2f avg=%6.2f min=%6.2f max=%6.2f\n", 
    silent ? '_' : 'T', energy, s->threshold, 
    s->avg, s->min_threshold, s->max_threshold);
#endif
  return silent;
} /* sd */
