/* libSoX statistics "effect" file.
 *
 * Compute various statistics on file and print them.
 *
 * Output is unmodified from input.
 *
 * July 5, 1991
 * Copyright 1991 Lance Norskog And Sundry Contributors
 * This source code is freely redistributable and may be used for
 * any purpose.  This copyright notice must be maintained.
 * Lance Norskog And Sundry Contributors are not responsible for
 * the consequences of using this software.
 */

#include "sox_i.h"

#include <string.h>

/* Private data for stat effect */
typedef struct {
  double min, max, mid;
  double asum;
  double sum1, sum2;            /* amplitudes */
  double dmin, dmax;
  double dsum1, dsum2;          /* deltas */
  double scale;                 /* scale-factor */
  double last;                  /* previous sample */
  uint64_t read;               /* samples processed */
  int volume;
  int srms;
  int fft;
  unsigned long bin[4];
  float *re_in;
  float *re_out;
  unsigned long fft_size;
  unsigned long fft_offset;
} priv_t;


/*
 * Process options
 */
static int sox_stat_getopts(sox_effect_t * effp, int argc, char **argv)
{
  priv_t * stat = (priv_t *) effp->priv;

  stat->scale = SOX_SAMPLE_MAX;
  stat->volume = 0;
  stat->srms = 0;
  stat->fft = 0;

  --argc, ++argv;
  for (; argc > 0; argc--, argv++) {
    if (!(strcmp(*argv, "-v")))
      stat->volume = 1;
    else if (!(strcmp(*argv, "-s"))) {
      if (argc <= 1) {
        lsx_fail("-s option: invalid argument");
        return SOX_EOF;
      }
      argc--, argv++;              /* Move to next argument. */
      if (!sscanf(*argv, "%lf", &stat->scale)) {
        lsx_fail("-s option: invalid argument");
        return SOX_EOF;
      }
    } else if (!(strcmp(*argv, "-rms")))
      stat->srms = 1;
    else if (!(strcmp(*argv, "-freq")))
      stat->fft = 1;
    else if (!(strcmp(*argv, "-d")))
      stat->volume = 2;
    else {
      lsx_fail("Summary effect: unknown option");
      return SOX_EOF;
    }
  }

  return SOX_SUCCESS;
}

/*
 * Prepare processing.
 */
static int sox_stat_start(sox_effect_t * effp)
{
  priv_t * stat = (priv_t *) effp->priv;
  int i;

  stat->min = stat->max = stat->mid = 0;
  stat->asum = 0;
  stat->sum1 = stat->sum2 = 0;

  stat->dmin = stat->dmax = 0;
  stat->dsum1 = stat->dsum2 = 0;

  stat->last = 0;
  stat->read = 0;

  for (i = 0; i < 4; i++)
    stat->bin[i] = 0;

  stat->fft_size = 4096;
  stat->re_in = stat->re_out = NULL;

  if (stat->fft) {
    stat->fft_offset = 0;
    stat->re_in = lsx_malloc(sizeof(float) * stat->fft_size);
    stat->re_out = lsx_malloc(sizeof(float) * (stat->fft_size / 2 + 1));
  }

  return SOX_SUCCESS;
}

/*
 * Print power spectrum to given stream
 */
static void print_power_spectrum(unsigned samples, double rate, float *re_in, float *re_out)
{
  float ffa = rate / samples;
  unsigned i;

  lsx_power_spectrum_f((int)samples, re_in, re_out);
  for (i = 0; i < samples / 2; i++) /* FIXME: should be <= samples / 2 */
    fprintf(stderr, "%f  %f\n", ffa * i, re_out[i]);
}

/*
 * Processed signed long samples from ibuf to obuf.
 * Return number of samples processed.
 */
static int sox_stat_flow(sox_effect_t * effp, const sox_sample_t *ibuf, sox_sample_t *obuf,
                        size_t *isamp, size_t *osamp)
{
  priv_t * stat = (priv_t *) effp->priv;
  int done, x, len = min(*isamp, *osamp);
  short count = 0;

  if (len) {
    if (stat->read == 0)          /* 1st sample */
      stat->min = stat->max = stat->mid = stat->last = (*ibuf)/stat->scale;

    if (stat->fft) {
      for (x = 0; x < len; x++) {
        SOX_SAMPLE_LOCALS;
        stat->re_in[stat->fft_offset++] = SOX_SAMPLE_TO_FLOAT_32BIT(ibuf[x], effp->clips);

        if (stat->fft_offset >= stat->fft_size) {
          stat->fft_offset = 0;
          print_power_spectrum((unsigned) stat->fft_size, effp->in_signal.rate, stat->re_in, stat->re_out);
        }

      }
    }

    for (done = 0; done < len; done++) {
      long lsamp = *ibuf++;
      double delta, samp = (double)lsamp / stat->scale;
      /* work in scaled levels for both sample and delta */
      stat->bin[(lsamp >> 30) + 2]++;
      *obuf++ = lsamp;

      if (stat->volume == 2) {
          fprintf(stderr,"%08lx ",lsamp);
          if (count++ == 5) {
              fprintf(stderr,"\n");
              count = 0;
          }
      }

      /* update min/max */
      if (stat->min > samp)
        stat->min = samp;
      else if (stat->max < samp)
        stat->max = samp;
      stat->mid = stat->min / 2 + stat->max / 2;

      stat->sum1 += samp;
      stat->sum2 += samp*samp;
      stat->asum += fabs(samp);

      delta = fabs(samp - stat->last);
      if (delta < stat->dmin)
        stat->dmin = delta;
      else if (delta > stat->dmax)
        stat->dmax = delta;

      stat->dsum1 += delta;
      stat->dsum2 += delta*delta;

      stat->last = samp;
    }
    stat->read += len;
  }

  *isamp = *osamp = len;
  /* Process all samples */

  return SOX_SUCCESS;
}

/*
 * Process tail of input samples.
 */
static int sox_stat_drain(sox_effect_t * effp, sox_sample_t *obuf UNUSED, size_t *osamp)
{
  priv_t * stat = (priv_t *) effp->priv;

  /* When we run out of samples, then we need to pad buffer with
   * zeros and then run FFT one last time to process any unprocessed
   * samples.
   */
  if (stat->fft && stat->fft_offset) {
    unsigned int x;

    for (x = stat->fft_offset; x < stat->fft_size; x++)
      stat->re_in[x] = 0;

    print_power_spectrum((unsigned) stat->fft_size, effp->in_signal.rate, stat->re_in, stat->re_out);
  }

  *osamp = 0;
  return SOX_EOF;
}

/*
 * Do anything required when you stop reading samples.
 * Don't close input file!
 */
static int sox_stat_stop(sox_effect_t * effp)
{
  priv_t * stat = (priv_t *) effp->priv;
  double amp, scale, rms = 0, freq;
  double x, ct;

  ct = stat->read;

  if (stat->srms) {  /* adjust results to units of rms */
    double f;
    rms = sqrt(stat->sum2/ct);
    f = 1.0/rms;
    stat->max *= f;
    stat->min *= f;
    stat->mid *= f;
    stat->asum *= f;
    stat->sum1 *= f;
    stat->sum2 *= f*f;
    stat->dmax *= f;
    stat->dmin *= f;
    stat->dsum1 *= f;
    stat->dsum2 *= f*f;
    stat->scale *= rms;
  }

  scale = stat->scale;

  amp = -stat->min;
  if (amp < stat->max)
    amp = stat->max;

  /* Just print the volume adjustment */
  if (stat->volume == 1 && amp > 0) {
    fprintf(stderr, "%.3f\n", SOX_SAMPLE_MAX/(amp*scale));
    return SOX_SUCCESS;
  }
  if (stat->volume == 2)
    fprintf(stderr, "\n\n");
  /* print out the info */
  fprintf(stderr, "Samples read:      %12" PRIu64 "\n", stat->read);
  fprintf(stderr, "Length (seconds):  %12.6f\n", (double)stat->read/effp->in_signal.rate/effp->in_signal.channels);
  if (stat->srms)
    fprintf(stderr, "Scaled by rms:     %12.6f\n", rms);
  else
    fprintf(stderr, "Scaled by:         %12.1f\n", scale);
  fprintf(stderr, "Maximum amplitude: %12.6f\n", stat->max);
  fprintf(stderr, "Minimum amplitude: %12.6f\n", stat->min);
  fprintf(stderr, "Midline amplitude: %12.6f\n", stat->mid);
  fprintf(stderr, "Mean    norm:      %12.6f\n", stat->asum/ct);
  fprintf(stderr, "Mean    amplitude: %12.6f\n", stat->sum1/ct);
  fprintf(stderr, "RMS     amplitude: %12.6f\n", sqrt(stat->sum2/ct));

  fprintf(stderr, "Maximum delta:     %12.6f\n", stat->dmax);
  fprintf(stderr, "Minimum delta:     %12.6f\n", stat->dmin);
  fprintf(stderr, "Mean    delta:     %12.6f\n", stat->dsum1/(ct-1));
  fprintf(stderr, "RMS     delta:     %12.6f\n", sqrt(stat->dsum2/(ct-1)));
  freq = sqrt(stat->dsum2/stat->sum2)*effp->in_signal.rate/(M_PI*2);
  fprintf(stderr, "Rough   frequency: %12d\n", (int)freq);

  if (amp>0)
    fprintf(stderr, "Volume adjustment: %12.3f\n", SOX_SAMPLE_MAX/(amp*scale));

  if (stat->bin[2] == 0 && stat->bin[3] == 0)
    fprintf(stderr, "\nProbably text, not sound\n");
  else {

    x = (float)(stat->bin[0] + stat->bin[3]) / (float)(stat->bin[1] + stat->bin[2]);

    if (x >= 3.0) {             /* use opposite encoding */
      if (effp->in_encoding->encoding == SOX_ENCODING_UNSIGNED)
        fprintf(stderr,"\nTry: -t raw -s -1 \n");
      else
        fprintf(stderr,"\nTry: -t raw -u -1 \n");
    } else if (x <= 1.0 / 3.0)
      ;                         /* correctly decoded */
    else if (x >= 0.5 && x <= 2.0) { /* use ULAW */
      if (effp->in_encoding->encoding == SOX_ENCODING_ULAW)
        fprintf(stderr,"\nTry: -t raw -u -1 \n");
      else
        fprintf(stderr,"\nTry: -t raw -U -1 \n");
    } else
      fprintf(stderr, "\nCan't guess the type\n");
  }

  /* Release FFT memory */
  free(stat->re_in);
  free(stat->re_out);

  return SOX_SUCCESS;

}

static sox_effect_handler_t sox_stat_effect = {
  "stat",
  "[ -s N ] [ -rms ] [-freq] [ -v ] [ -d ]",
  SOX_EFF_MCHAN | SOX_EFF_MODIFY,
  sox_stat_getopts,
  sox_stat_start,
  sox_stat_flow,
  sox_stat_drain,
  sox_stat_stop,
  NULL, sizeof(priv_t)
};

const sox_effect_handler_t *lsx_stat_effect_fn(void)
{
  return &sox_stat_effect;
}
