//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Thu Jul 12 12:47:53 PDT 2001
// Last Modified: Thu Jul 12 15:33:30 PDT 2001
// Filename:      ...sig/doc/examples/sig/sigfile/preen/preen.cpp
// Syntax:        C++; sig
//
// Description:   multiple comb filters affecting an input soundfile
//                at various random times.  An input file listing
//                comb filter applications can be specified with the
//                -f command-line option.
//

#include "sigAudio.h"

#include <stdlib.h>
#include <string.h>

#ifndef OLDCPP
   #include <iostream>
   using namespace std;
#else
   #include <iostream.h>
#endif

#define BLANKCHARS " \t\n,:;"

void checkOptions(Options& opts, int argc, char** argv);
void example(void);
void usage(const char* command);

class CombInfo {
   public:
      int start1;       // sample time of comb connect to source
      int start2;       // sample time of comb connect to summation (not used)
      int end1;         // sample time of comb disconnect from sound source
      int end2;         // sample time of comb disconnect from summation
      int delay;        // delayline length in samples
      int id;           // from 0 to size - 1;
      sampleType gain;  // feedback gain of delayline
      sampleType scale; // output volume from comb filter
};

// function declarations:
int  prepareCombList(int numSamples, Collection<CombInfo>& combinfo);
void printCombInfo(Collection<CombInfo>& combinfo);
int  start1compare(const void* a, const void* b);
int  end1compare(const void* a, const void* b);
int  end2compare(const void* a, const void* b);

// user interface variables:
Options options;
int combcount = 0;      // used with the -c option
int printQ = 0;         // used with the -p option
int quietQ = 0;         // used with the -q option


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

int main(int argc, char* argv[]) {
   checkOptions(options, argc, argv);

   SoundHeader header;
   char* infilename = "";
   char* outfilename = NULL;
   int numSamples;
 
   if (options.getArgCount() == 1) {   // use whitenoise if no input sound
      outfilename = options.getArg(1);
      header.setHighMono();
      if (options.getInt("samples") > 0) {
         numSamples = options.getInt("samples");
      } else {
         numSamples = (int)(options.getDouble("duration") * 44100 + 0.5);
      }
   } else {                            // use input soundfile
      infilename = options.getArg(1);
      header.setHeader(infilename);
      numSamples = header.getSamples();
      outfilename = options.getArg(2);
   }

   // Elements:
   SoundFileIn  insound(infilename);
   SoundFileOut outsound(outfilename, header);
   Envelope     noiseenv(options.getString("gain-env"));
   WhiteNoise   noise(1);
   Empty        soundsource;
   Multiply     mul;
   Multiply     nmul;
   Array<Comb>  comb;
   Array<Scale> combscale;
   Add          combsum;
   Envelope     globalscale(options.getString("amp-env"));
   DCBlock      dcblock;

   Collection<CombInfo> combinfo;
   numSamples = prepareCombList(numSamples, combinfo);

   comb.setSize(combinfo.getSize());
   combscale.setSize(combinfo.getSize());

   Collection<CombInfo*> combend1(combinfo.getSize());
   Collection<CombInfo*> combend2(combinfo.getSize());
   int i;
   for (i=0; i<combinfo.getSize(); i++) {
      combscale[i].connect(comb[i]);
      combend1[i] = &combinfo[i];
      combend2[i] = &combinfo[i];
   }

   qsort(combend1.getBase(), combend1.getSize(), sizeof(CombInfo*), 
         end1compare);
   qsort(combend2.getBase(), combend2.getSize(), sizeof(CombInfo*), 
         end2compare);

   // Connections:
   dcblock.connect(mul);
   outsound.connect(dcblock);
   mul.connect(combsum);
   mul.connect(globalscale);
   nmul.connect(noise);
   nmul.connect(noiseenv);

   if (strlen(infilename) == 0) {         // use noise
      soundsource.connect(nmul);
   } else {                               // use input file
      soundsource.connect(insound);
   }

   if (printQ) {
      printCombInfo(combinfo);
   }

   int s1i = 0;
   int e1i = 0;
   int e2i = 0;
   int csize = combinfo.getSize();

   for (i=0; i<numSamples; i++) {
topcheck:
      if (s1i < csize && combinfo[s1i].start1 <= i) {
         if (!quietQ) {
            cout << "Starting " << combinfo[s1i].id << " at time " << i << endl;
         }
         // connect sound source to a comb filter and to summation
         comb[combinfo[s1i].id].connect(soundsource, 0);
         comb[combinfo[s1i].id].setDelaySamples(combinfo[s1i].delay);
         comb[combinfo[s1i].id].setGain(combinfo[s1i].gain);
         comb[combinfo[s1i].id].clear();
         combscale[combinfo[s1i].id].setScale(combinfo[s1i].scale);
         combsum.connect(combscale[combinfo[s1i].id]);
         s1i++;
         goto topcheck;  // incase of multiple combs starting at same time
      }

      if (e1i < csize && combinfo[e1i].end1 <= i) {
         if (!quietQ) {
            cout << "Ending " << combinfo[e1i].id << " at time " << i << endl;
         }
         // disconnect the comb from the sound source
         comb[combinfo[e1i].id].disconnect(0);
         e1i++;
         goto topcheck;
      }

      if (e2i < csize && combinfo[e2i].end2 <= i) {
         // disconnect the comb from the summation after it has died out
         if (!quietQ) {
            cout << "Finishing "<< combinfo[e2i].id << " at time " << i << endl;
         }
         combsum.disconnect(comb[combinfo[e2i].id]);
         e2i++;
         goto topcheck;
      }

      Tick(outsound);
   }

   return 0;
}


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



//////////////////////////////
//
// printCombInfo --
//

void printCombInfo(Collection& combinfo) {
   int i;
   cout << "#     start\tend\tfreq\tdecay\tvol\n";
   for (i=0; i<combinfo.getSize(); i++) {
      cout << "comb\t";
      cout << combinfo[i].start1;                    // start
      cout << "\t" << combinfo[i].end1;              // end;
      cout << "\t" << 44100.0 / combinfo[i].delay;   // resonance frequency
      cout << "\t" << -3.0 / combinfo[i].delay / 
                      log10(combinfo[i].gain);       // decay
      cout << "\t" << combinfo[i].scale;             // volume gain
      cout << "\n";
   }
}



//////////////////////////////
//
// prepareCombList -- generate a list of the comb filter insertions
//     and deletions into the audio signal stream.
//

int prepareCombList(int numSamples, Collection& combinfo) {
   combinfo.setSize(combcount);
   combinfo.setSize(0);
   CombInfo info; 

   if (options.getBoolean("comb-file")) {
      #ifndef OLDCPP
         fstream combfile(options.getString("comb-file"), ios::in);
      #else
         fstream combfile(options.getString("comb-file"), ios::in | ios::nocreate);
      #endif
      if (!combfile.is_open()) {
         cout << "Error cannot open file: " << options.getBoolean("comb-file") 
              << " for reading of comb filter settings" << endl;
         exit(1);
      }
      int id = 0;
      int in_start;
      int in_end; 
      double in_freq;
      double in_decay;
      double in_volume;
      char buffer[1024] = {0};
      char* sptr;
      combfile.getline(buffer, 512, '\n');
      while (!combfile.eof()) {
         sptr = strtok(buffer, BLANKCHARS);
         if (sptr == NULL || strcmp(sptr, "comb") != 0) {
            combfile.getline(buffer, 512, '\n');
            continue;
         }

         sptr = strtok(NULL, BLANKCHARS);
         if (sptr == NULL) {
            combfile.getline(buffer, 512, '\n');
            continue;
         }
         in_start = atoi(sptr);

         sptr = strtok(NULL, BLANKCHARS);
         if (sptr == NULL) {
            combfile.getline(buffer, 512, '\n');
            continue;
         }
         in_end = atoi(sptr);

         sptr = strtok(NULL, BLANKCHARS);
         if (sptr == NULL) {
            combfile.getline(buffer, 512, '\n');
            continue;
         }
         in_freq = strtod(sptr, NULL);

         sptr = strtok(NULL, BLANKCHARS);
         if (sptr == NULL) {
            combfile.getline(buffer, 512, '\n');
            continue;
         }
         in_decay = strtod(sptr, NULL);

         sptr = strtok(NULL, BLANKCHARS);
         if (sptr == NULL) {
            combfile.getline(buffer, 512, '\n');
            continue;
         }
         in_volume = strtod(sptr, NULL);

         info.start1 = in_start;
         info.delay = (int)(44100 / in_freq + 0.5);
         if (info.delay <10) {
            info.delay = 10;
         }
         info.start2 = info.start1 + info.delay;
         info.end1 = in_end;
         if (info.end1 < info.start1 + info.delay) {
            info.end1 = info.start1 + info.delay + 100;
         }
         info.gain = pow(0.001, 1.0/(info.delay * in_decay));
         info.end2 = info.end1 + (int)(-3.0 / info.delay / log10(info.gain));
         info.id = id++;
         info.scale = in_volume;

         combinfo.append(info);
         
         if (numSamples < info.end2) {
            numSamples = info.end2;
         }
      }

   } else {

      Distribution u;         u.doUniform(0,numSamples);
      Distribution scaled;    scaled.doTriangular(0.001, 0.1, 0.4);
      Distribution scalev;    scalev.doTriangular(0.75, 0.97, 0.999);
      Distribution g;         g.doGaussian(numSamples/10.0, 0.0);
      Distribution delayg;    delayg.doGaussian(1000.0, 2000.0);

      for (int i=0; i<combcount; i++) {
         info.start1 = (int)u.value();
         info.delay = abs((int)(44100.0/delayg.value()));  // fixed srate
         if (info.delay < 10) {
            info.delay += 10;
         }
         info.start2 = info.start1 + info.delay;
         info.end1 = info.start1 + (int)(fabs(g.value()));
         if (info.end1 - info.start1 < info.delay) {
            info.end1 = info.start1 + info.delay + 1000;
         }
         info.gain = scalev.value();
         info.end2 = info.end1 + (int)(-3.0/(info.delay * log10(info.gain)));
         info.scale = scaled.value();
         info.id = i;
         combinfo.append(info);
         if (info.end2 > numSamples) {
            numSamples = info.end2;
         }
      }

   }

   qsort(combinfo.getBase(), combinfo.getSize(), sizeof(CombInfo), 
         start1compare);

   return numSamples + 1000;
}



//////////////////////////////
//
// checkOptions -- handle command-line options.
//

void checkOptions(Options& opts, int argc, char** argv) {
   opts.define("a|amp|amp-env=s:0 0.2 1 0.2", "Amplitude env output");
   opts.define("g|gain-env=s:0 1 1 1", "Amplitude env output for noise");
   opts.define("d|dur|duration=d:1.0 second", "Time in sec. for noise");
   opts.define("s|samples=i", "Number of white noise sample if no soundfile");
   opts.define("c|count=i:10", "Number of comb filter to generate");
   opts.define("p|print=b", "For printing comb info parameters used");
   opts.define("q|quiet=b", "For printing comb progress");
   opts.define("f|file|comb-file=s", "Filename for input file of comb info");

   opts.define("author=b");
   opts.define("version=b");
   opts.define("example=b");
   opts.define("help=b");
   opts.process(argc, argv);

   if (opts.getBoolean("author")) {
      cout << "Written by Craig Stuart Sapp, "
           << "craig@ccrma.stanford.edu, July 2001" << endl;
      exit(0);
   }
   if (opts.getBoolean("version")) {
      cout << "Version 12 July 2001" << endl;
      cout << "compiled: " << __DATE__ << endl;
      cout << SIG_VERSION << endl;
      exit(0);
   }
   if (opts.getBoolean("help")) {
      usage(opts.getCommand());
      exit(0);
   }
   if (opts.getBoolean("example")) {
      example();
      exit(0);
   }

   // can only have one input and one output filename
   if (opts.getArgCount() == 0) {
      cout << "Error: need one output file name." << endl;
      usage(opts.getCommand());
      exit(1);
   } else if (opts.getArgCount() > 2) {
      cout << "Error: too many arguments.  Given "
           << opts.getArgCount() << " but need only 2." << endl;
      usage(opts.getCommand());
      exit(1);
   }

   combcount = opts.getInteger("count");
   printQ = opts.getBoolean("print");
   quietQ = opts.getBoolean("quiet");
}



//////////////////////////////
//
// example -- gives example calls to the comb program.
//

void example(void) {
}



//////////////////////////////
//
// usage -- how to run the osc program on the command line.
//

void usage(const char* command) {
   cout << endl;
   cout << 
      "A comb filter.  Uses whitenoise if no input sound is specified" << endl;
   cout << endl;
   cout << "Usage: " << command << " [insound] outsound" << endl;
   cout << endl;
}




///////////////////////////////
//
// start1compare -- for sorting the CombInfo entries by start1
//

int start1compare(const void* a, const void* b) {
   CombInfo& aa = *((CombInfo*)a);
   CombInfo& bb = *((CombInfo*)b);

   if (aa.start1 < bb.start1) {
      return -1;
   } else if (aa.start1 > bb.start1) {
      return 1;
   } else {
      return 0;
   }
}



///////////////////////////////
//
// end1compare -- for sorting the CombInfo* entries by end1
//

int end1compare(const void* a, const void* b) {
   CombInfo& aa = **((CombInfo**)a);
   CombInfo& bb = **((CombInfo**)b);

   if (aa.end1 < bb.end1) {
      return -1;
   } else if (aa.end1 > bb.end1) {
      return 1;
   } else {
      return 0;
   }
}

             

///////////////////////////////
//
// end2compare -- for sorting the CombInfo* entries by end1
//

int end2compare(const void* a, const void* b) {
   CombInfo& aa = **((CombInfo**)a);
   CombInfo& bb = **((CombInfo**)b);

   if (aa.end2 < bb.end2) {
      return -1;
   } else if (aa.end2 > bb.end2) {
      return 1;
   } else {
      return 0;
   }
}

             

/* Sample comb parameter file for use with -f option:
#     start     end     freq    decay   vol
comb    1119    9182    604.11  1.17297 0.191731
comb    1669    2051    1378.12 0.9478  0.209458
comb    4900    12895   2100    6.91364 0.146091
comb    4996    7041    2321.05 2.01521 0.263378
comb    7384    10647   2004.55 2.39535 0.189616
comb    8061    17086   4009.09 7.71661 0.330953
comb    8081    10681   2100    5.37237 0.260881
comb    8089    9199    2940    3.61925 0.0489331
comb    12809   13335   2940    21.4995 0.0354243
comb    17572   19144   2004.55 2.46674 0.172416
comb    17681   23163   2004.55 1.74748 0.209238
comb    19288   23852   711.29  0.76728 0.17405
comb    20696   24902   3675    20.2594 0.196204
comb    21635   22399   864.706 1.26352 0.117229
comb    21909   23868   1160.53 2.76592 0.0903678
comb    22520   26817   1837.5  2.09309 0.159363
comb    25045   32946   1575    1.43017 0.0452757
comb    26165   27622   1837.5  4.24314 0.0983655
comb    27883   29013   2321.05 5.73193 0.183661
comb    30439   37054   2450    5.95126 0.150158
comb    30739   33374   1225    5.87005 0.139962
comb    31920   32785   1225    8.8128  0.151489
comb    31966   32435   2594.12 3.28092 0.0921744
comb    36238   42315   2321.05 4.28848 0.145472
comb    37066   37779   2940    2.32236 0.151643
comb    38014   39960   1696.15 27.6497 0.132293
comb    38237   46489   1191.89 1.86635 0.119446
comb    38678   38833   1696.15 3.38284 0.0882454
comb    41651   42534   4009.09 12.9445 0.0445691
comb    41686   44190   2321.05 12.7503 0.131357

*/

/* Another test comb parameter file:
#
# preen score file for use with sound sounds/base/paperjam-French.au
# Thu Jul 12 20:57:06 PDT 2001
# Craig Stuart Sapp
#
# preen -a 1.2 -f preen.data sounds/base/paperjam-French.wav /tmp/output.wav  
#

#     start     end     freq    decay   vol
comb    100     10000    440    1.17297 0.2
comb  10000     20000    140    0.17297 0.4
comb  12000     29000   1760    1.17297 0.2
comb  18000     20000   2760   40.1729  0.4
comb  22000     30000   5000  100.0     0.05
comb  29000     45000    300    1.0     0.15
comb  44000     60000    220    2.0     0.1
comb  34000     52000    110    0.5     0.2
comb  50000     60000    660    1.1     0.3
comb  55000     63000    330    1.1     0.4
comb  60000     73000   1530   49.1     0.1



*/

// md5sum: f572a402545b6037c529892b631f3f88 preen.cpp [20050403]