//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Fri Nov 18 09:18:15 PST 2005
// Last Modified: Fri Nov 18 09:18:18 PST 2005
// Filename:      ...soundfile/examples/tempochange.cpp
// Syntax:        C++ 
//
// Description:   Make a click track for experiments in tempo perception.
//

#include "soundfile.h"
#include <stdlib.h>

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

#define CLICKWIDTH 44

void getClickTimes(Array<long>& clicktimes, double startdur, double gain);


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

int main(int argc, char** argv) {

   srand48(time(NULL));

   Options options;
   options.define("p|padding=i:1000",    "samples of slience at ends of sound");
   options.define("a|amp=d:0.25",        "amplitude scaling factor");
   options.define("n|clicks=i:16",       "number of clicks to produce");
   options.define("s|startdur=d:1000.0", "starting duration in milliseconds");
   options.define("g|gain|f|fraction=d:0.0", "the fractional change between beats");
   options.define("v|verbose=b",         "verbose: display timings of clicks");
   options.define("t|tempo=d:60.0",      "starting tempo");
   options.define("r|randgain=b",        "randomize gain direction");

   options.process(argc, argv);
   
   int         clickcount = options.getInteger("clicks");
   int         padding    = options.getInteger("padding"); 
   double      startdur   = options.getDouble("startdur");
   double      gain       = options.getDouble("gain");
   double      amp        = options.getDouble("amp");
   const char* outputname = options.getArg(1);

   if (options.getBoolean("tempo")) {
      startdur = 60.0 /  options.getDouble("tempo") * 1000.0;
   }

   if (startdur < 10) {
      startdur = 10;
   } else if (startdur > 10000) {
      startdur = 10000;
   }

   if (clickcount < 3) {
      clickcount = 3;
   } else if (clickcount > 1000) {
      clickcount = 1000;
   }

   if (padding < 0) {
      padding = 0;
   } else if (padding > 100000) {
      padding = 100000;
   }

   gain = -gain;   // convert tempo gain into duration gain.

   if (options.getBoolean("randgain")) {
      if (lrand48() * 2.0/INT_MAX - 1.0 < 0) {
         gain = -gain;
      } 
      cout << "GAIN = " << -gain;
      if (-gain < 0.0) {
          cout << " (slower)";
      } else if (-gain > 0.0) {
          cout << " (faster)";
      } else {
          cout << " (constant)";
      } 
      cout << endl;
   }

   gain = gain + 1.0;

   Array<long> clicktimes;
   clicktimes.setSize(clickcount);
   clicktimes.setGrowth(0);
   clicktimes.setAll(0);

   getClickTimes(clicktimes, startdur, gain);

   int i;
   double clicksample;
   SoundHeader    header;
   header.setHighMono();
   SoundFileWrite outsound(outputname, header);

   int clicki = 0;
   int clickstatus = 0;

   for (i=0; i<clicktimes.getSize(); i++) {
      clicktimes[i] += padding;
   }

   int samplecount = clicktimes[clicktimes.getSize()-1] + CLICKWIDTH + padding;
   
   for (i=0; i<samplecount; i++) {
      if ((clicki < clickcount) && (clicktimes[clicki] == i)) {
         clickstatus = 1;
         clicki++;
      }
      if (clickstatus) {
         clicksample = ((lrand48() * 2.0)/INT_MAX - 1.0) * amp;
         clickstatus++;
         if (clickstatus > CLICKWIDTH) {
            clickstatus = 0;
         }
      } else {
         clicksample = 0.0;
      }
      outsound.writeSampleDouble(clicksample);
   }

   return 0;
}



//////////////////////////////
//
// getClickTimes --
//

void getClickTimes(Array& clicktimes, double startdur, double gain) {
   double duration = startdur * 44100.0 / 1000.0;
   clicktimes[0] = 0;
   int i;
   for (i=1; i<clicktimes.getSize(); i++) {
      clicktimes[i] = (int)(clicktimes[i-1] + duration + 0.5);
      duration *= gain;
   }
}



// md5sum: a51e61725e520c5d091efe382d3a575f tempochange.cpp [20060502]