//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sun Sep 30 23:06:34 PDT 2007
// Last Modified: Tue Oct  2 04:57:29 PDT 2007
// Filename:      ...sig/examples/all/smoother.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/smoother.cpp
// Syntax:        C++; museinfo
//
// Description:   Smoothes data with exponential smoothing filter.
//

#include "humdrum.h"
#include <stdlib.h>
#include <math.h>

#ifndef OLDCPP
   using namespace std;
#endif

#define GEOMETRIC  1
#define ARITHMETIC 2

#define EMPTY  0
#define PEAK   1
#define VALLEY 2

///////////////////////////////////////////////////////////////////////////

// function declarations
void      checkOptions(Options& opts, int argc, char* argv[]);
void      example(void);
void      usage(const char* command);
void      processFile(HumdrumFile& infile);
void      smoothData(Array<double>& output, 
                              Array<double>& input, double gain);
void      generateRow(Array<char>& imagerow, int row, int rows, 
                              double gain, Array<double>& sequence);
void      printImage(Array<Array<char> >& image);

// global variables
Options   options;            // database for command-line arguments
double    sgain = 0.05;       // maximum smoothing amount
int       height = 100;       // number of pixels high for image
                              // (width controlled by sequence length)
int       method = GEOMETRIC; // GEOMETRIC or ARITHMETIC interpolation
                              // betwin lines in image.

///////////////////////////////////////////////////////////////////////////

int main(int argc, char* argv[]) {
   HumdrumFile infile;

   // process the command-line options
   checkOptions(options, argc, argv);

   const char* filename;
   infile.clear();
   // if no command-line arguments read data file from standard input
   int numinputs = options.getArgCount();
   if (numinputs < 1) {
      infile.read(cin);
   } else {
      filename = options.getArg(1);
      infile.read(options.getArg(1));
   }
   processFile(infile);

}

///////////////////////////////////////////////////////////////////////////


//////////////////////////////
//
// processFile -- only read the first row for processing for now...
//

void processFile(HumdrumFile& infile) {
   Array<double> sequence;
   sequence.setSize(infile.getNumLines());
   sequence.setSize(0);

   double value;
   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() == E_humrec_data) {
         value = atof(infile[i][0]);
         sequence.append(value);
      }
   }
   int thesize = sequence.getSize();


   Array<Array<char> > image;
   image.setSize(height);
   image.allowGrowth(0);
   for (i=0; i<height; i++) {
      image[i].setSize(thesize);
      image[i].allowGrowth(0);
      image[i].setAll(0);
      generateRow(image[i], i, height, sgain, sequence);
   }

   printImage(image);

}



//////////////////////////////
//
// printImage --
//

void printImage(Array >& image) {
   cout << "P3\n";
   cout << image[0].getSize() << " " << image.getSize() << "\n";
   cout << "255\n";

   int i, j;
   for (i=0; i<image.getSize(); i++) {
      for (j=0; j<image[i].getSize(); j++) {
         switch (image[i][j]) {
            case PEAK:      cout << "255 0 0";      break;
            case VALLEY:    cout << "0 0 255";      break;
            default:        cout << "255 255 255";
         }
         if (j<image[i].getSize()-1) {
            cout << " ";
         }
      }
      cout << "\n";
   }
}



//////////////////////////////
//
// generateRow --
//

void generateRow(Array<char>& imagerow, int row, int rows, double gain, 
      Array<double>& sequence) {

   Array<double> smoothed;
   double localgain;
   if (method == GEOMETRIC) {
      localgain = gain * pow(1.0 / gain, (double)row/(rows-1));
   } else {
      localgain = (double)row/(rows-1) * (1.0 - gain);
   }

   smoothData(smoothed, sequence, localgain);

   int i;
   for (i=2; i<imagerow.getSize()-2; i++) {
      if (smoothed[i] > smoothed[i-1] && smoothed[i] > smoothed[i+1] &&
	  smoothed[i] > smoothed[i-2] && smoothed[i] > smoothed[i+2] ) {
         imagerow[i] = PEAK;
      } else if (smoothed[i] < smoothed[i-1] && smoothed[i] < smoothed[i+1] &&
	  smoothed[i] < smoothed[i-2] && smoothed[i] < smoothed[i+2] ) {
         imagerow[i] = VALLEY;
      }
   }
}



//////////////////////////////
//
// smoothData --
//

void smoothData(Array& output, Array& input, double gain) {
   int thesize = input.getSize();
   output.setSize(thesize);

   double filterk = gain;
   double oneminusk = 1.0 - filterk;

   // reverse filtering first
   output[thesize-1] = input[thesize-1];
   int i;
   for (i=thesize-2; i>=0; i--) {
      output[i] = filterk*input[i] + oneminusk*output[i+1];
   }

   // then forward filtering
   for (i=1; i<thesize; i++) {
      output[i] = filterk*output[i] + oneminusk*output[i-1];
   }

}



//////////////////////////////
//
// checkOptions -- validate and process command-line options.
//

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("s|smooth=d:0.05");   // smoothing factor limit
   opts.define("r|rows=i:100");      // number of rows in the image
   opts.define("A|arithmetic=b");    // do arithmetic rather than geometric

   opts.define("debug=b");           // determine bad input line num
   opts.define("author=b");          // author of program
   opts.define("version=b");         // compilation info
   opts.define("example=b");         // example usages
   opts.define("h|help=b");          // short description
   opts.process(argc, argv);
   
   // handle basic options:
   if (opts.getBoolean("author")) {
      cout << "Written by Craig Stuart Sapp, "
           << "craig@ccrma.stanford.edu, Oct 2007" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 1 Oct 2007" << endl;
      cout << "compiled: " << __DATE__ << endl;
      cout << MUSEINFO_VERSION << endl;
      exit(0);
   } else if (opts.getBoolean("help")) {
      usage(opts.getCommand());
      exit(0);
   } else if (opts.getBoolean("example")) {
      example();
      exit(0);
   }

   sgain = opts.getDouble("smooth");
   height = opts.getInteger("rows");
   if (opts.getBoolean("arithmetic")) {
      method = ARITHMETIC;
   }
}



//////////////////////////////
//
// example -- example usage of the program
//

void example(void) {
   cout <<
   "                                                                         \n"
   << endl;
}



//////////////////////////////
//
// usage -- gives the usage statement for the program
//

void usage(const char* command) {
   cout <<
   "                                                                         \n"
   << endl;
}



// md5sum: 8c27480c3a73647fa55856f922638a54 levogram.cpp [20080518]