/* file = fmeat.c (the meat of the featurize operation) */
/* converts a raw audio data file to a parmetric representation */
/* Nigel Ward, University of Tokyo, April 1994 */

#include "cheap.h"  

/*-----------------------------------------------------------------------------*/
/* all frames beginning at or before this sample have been calculated */
static int featurized_up_to;

/*-----------------------------------------------------------------------------*/
init_input_sig(s_ptr)      struct signature *s_ptr;
{
  s_ptr->frame_period = FRAME_SPACING;
  s_ptr->frame_shift = SAMPLING_RATE * s_ptr->frame_period / 1000;
  s_ptr->nbins = DEFAULT_NBINS;
  strcpy(s_ptr->feature_type, "filterbank");
  strcpy(s_ptr->info_string,  "input grabbed from /dev/audio");
}

/*-----------------------------------------------------------------------------*/
init_featurize_some()
{  featurized_up_to = -1; }

/*-----------------------------------------------------------------------------*/
int featurize_some(raw_data, signal_start, current_end, windowp, sig_ptr)
     char raw_data[]; int signal_start, current_end; 
     int windowp;    struct signature *sig_ptr;
{
  static int current_frame;
  static float data[MAX_SAMPLES], fft_output[SAMPLES_PER_FRAME + 1];
  int i;

  /* fprintf(stderr, " fmeat: featurize_some: signal_start %d, current_end %d; \n",
	  signal_start, current_end);   */
  if (featurized_up_to == -1) {
    featurized_up_to = signal_start;
    current_frame = 0;}
  for (i = featurized_up_to; i < current_end; i++) {
    data[i] = (float) audio_u2c( raw_data[i]); }
  while (featurized_up_to + SAMPLES_PER_FRAME < current_end) {
    /* fprintf(stderr," fs: doing fft and binify, sample=%d, frame=%d\n",
	    featurized_up_to, current_frame); */  
    do_fft(data, featurized_up_to, fft_output);
    binify(fft_output, current_frame, windowp, sig_ptr); 
    featurized_up_to += sig_ptr->frame_shift;
    current_frame ++;
  } 
  if (current_frame == 0)  return(-1);     /* nothing yet featurized */
  else return(current_frame - 1);          /* count of featurized frames */
}

/*-----------------------------------------------------------------------------*/
do_au_file(filename, sig_ptr, windowp)
     char *filename; struct signature *sig_ptr;    int windowp;
{
  Audio_filehdr	hdr;	
  int nsamples;
  char raw_data[MAX_SAMPLES];
  int in_fd; FILE *out_fp;
  char new_path[MAX_PATH_LEN];

  /* set defaults */
  strcpy(sig_ptr->info_string, "");
  
  make_new_path(new_path, filename, ".au");
  in_fd = open_rdonly_with_check(new_path);
  if (in_fd < 0) { exit(STRANGE); }

  nsamples =  read_au_into_array(filename, &hdr, sig_ptr->info_string, raw_data); 
  close(in_fd);

  if (sig_ptr->nbins != FB_NBINS) {
    printf(" feat: note: for filterbank, nbins must be %d (not %d); adjusted \n", 
	   FB_NBINS, sig_ptr->nbins);
    sig_ptr->nbins = FB_NBINS; }
  convert_fft_binify(raw_data, nsamples, sig_ptr, windowp);

  out_fp = fopen_out_file(filename, ".fe");

  write_fe_header(out_fp, sig_ptr);
  write_fe_data(out_fp, sig_ptr); 
    
  if (windowp) {
    write_baseline_ticks(sig_ptr->nbins);
    sleep(10);       /* keep the window display up long enough to see */
    clean_up_features_canvas(); }
}

/*-----------------------------------------------------------------------------*/
/* my implementation of bandpass filtering.
   first fft the signal, to get the energy at each frequency,
   then sum up over each frequency range to get the value for each bin.
   There must be a better way to do this */
convert_fft_binify(raw_data, nsamples, sig_ptr, windowp)
     char raw_data[]; int nsamples; struct signature *sig_ptr;   int windowp;
{
  float data[MAX_SAMPLES];
  float fft_output[SAMPLES_PER_FRAME + 1];
  int i, nframes, start_sample;

  nframes = 0;
  for (i = 0; i < nsamples; i++) 
    data[i] = (float) audio_u2c( raw_data[i]);
  for (start_sample = 0; start_sample + SAMPLES_PER_FRAME < nsamples;
       start_sample += sig_ptr->frame_shift) {
    /* printf("doing fft for %d \n", start_sample); */
    do_fft(data, start_sample, fft_output);
    binify(fft_output, nframes, windowp, sig_ptr);
    nframes++; }
  sig_ptr->nframes = nframes; }

/*-----------------------------------------------------------------------------*/
/* in terms of Hz */
/*  a overlapping sawtooth set of filters
   the center freq of one band is the upper bound of the band to the left,
   and is also the lower bound of the band to the right */
static int bounds_and_centers[2 + FB_NBINS] = 
  {150, 350, 550, 800, 1050, 1350, 1700, 2300, 3000, 3900 };  
/*   {150, 375, 600, 825, 1050, 1350, 1700, 2300, 3000, 3900 }; is no better */
 
hertz_to_index (hertz)      int hertz;
{
  return( (int) (hertz * (float) (SAMPLES_PER_FRAME / (float) SAMPLING_RATE)));
}

/*-----------------------------------------------------------------------------*/
/* note that ttl is slightly greater than the sum of the bins, since we use
   triangle filters */
binify(fft_output, frame_number, windowp, sig_ptr)
     float fft_output[];
     int frame_number, windowp;
     struct signature *sig_ptr;
{
  float bin_sum;
  float total_for_this_frame = 0.0;
  int lower_boundary, center_freq, upper_boundary;
  int bin, j;

  for (bin = 0; bin < FB_NBINS; bin++) {
    bin_sum = 0.0;
    lower_boundary = hertz_to_index(bounds_and_centers[bin]);
    center_freq = hertz_to_index(bounds_and_centers[bin+1]);
    upper_boundary = hertz_to_index(bounds_and_centers[bin+2]);

    for (j = lower_boundary; j <= center_freq; j++) {
      bin_sum += ((j - lower_boundary) / ((1.0 * center_freq) - lower_boundary))
	          * fft_output[j];
      total_for_this_frame += fft_output[j]; }

    for (j = center_freq; j < upper_boundary; j++) {
      bin_sum += ((j - center_freq) / (upper_boundary - 1.0 * center_freq))
	* fft_output[j]; }
    if (sig_ptr->logp)
      sig_ptr->bins[frame_number][bin] =  log( (double) bin_sum);
    else
      sig_ptr->bins[frame_number][bin] =  bin_sum;
    if (windowp)
      draw_fb_point(frame_number, bin, bin_sum);

  }
  if (sig_ptr->logp)
    sig_ptr->energy[frame_number] = log ( (double) total_for_this_frame);
  else
    sig_ptr->energy[frame_number] = total_for_this_frame;
}

/* ----------------------------------------------------------------------------- */
/*-----------------------------------------------------------------------------*/
#define CANVAS_HEIGHT      720
#define YSPACING 80
#define FILTERBAND_YGAIN .10
#define MARGIN 30
#define CANVAS_WIDTH       (MAX_SAMPLES / SAMPLES_PER_PIXEL)   /* 800 pixels */

prepare_features_canvas()
{ sg_create(CANVAS_WIDTH, CANVAS_HEIGHT, 1, "spectral (or cepstral) bands"); 
  sg_clear(); }

clean_up_features_canvas()
{ sg_close();}

draw_fb_point(frame, bin, yvalue)       int frame, bin; float yvalue;
{ sg_pset(MARGIN + frame , CANVAS_HEIGHT - MARGIN - YSPACING * (1 + bin)
	  - (int) (FILTERBAND_YGAIN * yvalue)); }

write_baseline_ticks(nbins)
{ int i, baseline;
  for (i = 0; i < nbins; i++) {
    baseline = CANVAS_HEIGHT - MARGIN - YSPACING * (1 + i);
    sg_line(0, baseline, MARGIN - 10, baseline); } }

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