//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sat Sep  7 16:10:45  2002
// Last Modified: Sat Sep  7 22:30:41  2002
// Filename:      ...sig/doc/examples/sig/sigfile/addsyn/addsyn.cpp
// Syntax:        C++; sig
//
// Description:   make a soundfile with the specified moveable 
//                frequency/amplitude sinewaves
//

#include "sigAudio.h"

#include <stdlib.h>
#include <ctype.h>

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


class OscInfo {
   public:
      int         startSample;
      int         stopSample;
      double      phase;
      double      freq;
      double      amp;
      void        setFreqEnv (const char* string) {
                     int len = strlen(string);
                     if (freqEnv != NULL) {
                        delete [] freqEnv;
                     }
                     freqEnv = new char[len+1];
                     strcpy(freqEnv, string);
                  }
      void        setAmpEnv (const char* string) {
                     int len = strlen(string);
                     if (ampEnv != NULL) {
                        delete [] ampEnv;
                     }
                     ampEnv = new char[len+1];
                     strcpy(ampEnv, string);
                  }
      void print(void) {
         cout << "Start sample: " << startSample << endl;
         cout << "Stop  sample: " << stopSample << endl;
         cout << "Phase: "        << phase << endl;
         cout << "Frequency: "    << freq << endl;
         cout << "Amplitude: "    << amp << endl;
         cout << "FreqEnv: "      << getFreqEnv() << endl;
         cout << "AmpEnv: "       << getAmpEnv() << endl;
         cout << "======================" << endl;
      }
      const char* getFreqEnv(void) {
         if (freqEnv == NULL) return "";
         else return freqEnv;
      }
      const char* getAmpEnv(void) {
         if (ampEnv == NULL) return "";
         else return ampEnv;
      }
      OscInfo(void) { freqEnv = ampEnv = NULL; clear(); }
      OscInfo(OscInfo& a) { 
         startSample = a.startSample;
         stopSample = a.stopSample;
         phase = a.phase;
         freq  = a.freq;
         amp = a.amp;
         setFreqEnv(a.getFreqEnv());
         setAmpEnv(a.getAmpEnv());
      }
      OscInfo& operator=(OscInfo& a) {
         if (&a == this) return *this;
         startSample = a.startSample;
         stopSample = a.stopSample;
         phase = a.phase;
         freq  = a.freq;
         amp = a.amp;
         setFreqEnv(a.getFreqEnv());
         setAmpEnv(a.getAmpEnv());
         return *this;
      }
      void clear(void) { 
                      startSample = stopSample = 0;
                      freq = amp = -1000.0; 
                      phase = 0.0; 
                      if (freqEnv != NULL) {
                         delete [] freqEnv;
                         freqEnv = NULL;
                      }
                      if (ampEnv != NULL) {
                         delete [] ampEnv;
                         ampEnv = NULL;
                      }
                   }
   private:
      char* freqEnv;
      char* ampEnv;
};

class ActionTime {
   public: 
      int time;
      int number;
      ActionTime(void) { time = number = 0; }
};

// function declarations:
void   checkOptions(Options& opts);
void   example(void);
void   usage(const char* command);
int    timesort(const void* a, const void* b);
void   buildOscInfo(Array<OscInfo>& oscinfo, const char* filename);
void   setOnTimes(Array<ActionTime>& ontimes, Array<OscInfo>& oscinfo);
void   setOffTimes(Array<ActionTime>& offtimes, Array<OscInfo>& oscinfo);
void   checkOnConnections(int currtime, Add& add, Array<Osc>& osc,
                          Array<ActionTime>& ontimes, int& onindex);
void   checkOffConnections(int currtime, Add& add, Array<Osc>& osc,
                          Array<ActionTime>& offtimes, int& offindex);
void prepareOscillators(Array<OscInfo>& oscinfo, Array<Osc>& osc, 
      Array<Envelope>& freqEnv, Array<Envelope>& ampEnv);


// global variables:
Array<OscInfo> oscinfo;
int            srate    = 44100;      // sampling rate of output soundfile
int            verboseQ = 0;          // used with the -v option

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

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

   // determine how many samples in the output based on the input data
   int numSamples = 0;
   int i;
   for (i=0; i<oscinfo.getSize(); i++) {
      if (oscinfo[i].stopSample >= numSamples) {
         numSamples = oscinfo[i].stopSample + 1;
      }
   }

   Array<ActionTime> ontimes;
   Array<ActionTime> offtimes;
   int onindex = 0;
   int offindex = 0;

   setOnTimes(ontimes, oscinfo);
   setOffTimes(offtimes, oscinfo);

   // prepare for a monophonic output file
   SoundHeader header;
   header.setHighMono();

   Array<Envelope> freqEnv(oscinfo.getSize());
   Array<Envelope> ampEnv(oscinfo.getSize());
   Array<Osc>      osc(oscinfo.getSize());

   prepareOscillators(oscinfo, osc, freqEnv, ampEnv);

   Add          add;
   SoundFileOut outsound(options.getArg(2), header);

   // connections
   outsound.connect(add);

   for (i=0; i<numSamples; i++) {
      checkOnConnections(i, add, osc, ontimes, onindex);
      outsound.tick(i);
      checkOffConnections(i, add, osc, offtimes, offindex);
   }

   return 0;
}


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


//////////////////////////////
//
// prepareOscillators --
//

void prepareOscillators(Array<OscInfo>& oscinfo, Array<Osc>& osc, 
      Array<Envelope>& freqEnv, Array<Envelope>& ampEnv) {
   int i;
   for (i=0; i<oscinfo.getSize(); i++) {

      if (oscinfo[i].freq == -1000.0) {
         freqEnv[i].setEnvelope(oscinfo[i].getFreqEnv());
         freqEnv[i].setDurationSamples(oscinfo[i].stopSample - 
            oscinfo[i].startSample);
         osc[i].connect(freqEnv[i], 0);
      } else {
         osc[i].connect(oscinfo[i].freq, 0);
      }

      if (oscinfo[i].amp == -1000.0) {
         ampEnv[i].setEnvelope(oscinfo[i].getAmpEnv());
         ampEnv[i].setDurationSamples(oscinfo[i].stopSample - 
            oscinfo[i].startSample);
         osc[i].connect(ampEnv[i], 1);
      } else {
         osc[i].setAmplitude(oscinfo[i].amp);
      }
      osc[i].setPhase(oscinfo[i].phase/360.0);
   }
}



//////////////////////////////
//
// checkOnConnections --
//

void checkOnConnections(int currtime, Add& add, 
      Array<Osc>& osc,  Array<ActionTime>& ontimes, 
      int& onindex) {
   int i;
   for (i=onindex; i<ontimes.getSize(); i++) {
      if (currtime < ontimes[i].time) {
         break;
      } else {
         add.connect(osc[ontimes[i].number]);
      }
   }
   onindex = i;
}



//////////////////////////////
//
// checkOffConnections --
//

void checkOffConnections(int currtime, Add& add, Array<Osc>& osc, 
      Array<ActionTime>& offtimes, int& offindex) {
   int i;
   for (i=offindex; i<offtimes.getSize(); i++) {
      if (currtime < offtimes[i].time) {
         break;
      } else {
         add.disconnect(osc[offtimes[i].number]);
      }
   }
   offindex = i;
}



//////////////////////////////
//
// setOnTimes -- collect and sort the on times.
//

void setOnTimes(Array& ontimes, Array& oscinfo) {
   ontimes.setSize(oscinfo.getSize());
   int i;
   for (i=0; i<ontimes.getSize(); i++) {
      ontimes[i].number = i;
      ontimes[i].time   = oscinfo[i].startSample;
   }
   qsort(ontimes.getBase(), ontimes.getSize(), sizeof(ActionTime), timesort); 
}



//////////////////////////////
//
// setOffTimes -- collect and sort the off times.
//

void setOffTimes(Array& offtimes, Array& oscinfo) {
   offtimes.setSize(oscinfo.getSize());
   int i;
   for (i=0; i<offtimes.getSize(); i++) {
      offtimes[i].number = i;
      offtimes[i].time   = oscinfo[i].stopSample;
   }
   qsort(offtimes.getBase(), offtimes.getSize(), sizeof(ActionTime), timesort); 
}



//////////////////////////////
//
// timesort -- sort by specified time
//

int timesort(const void* a, const void* b) {
   ActionTime &A = *((ActionTime*)a);
   ActionTime &B = *((ActionTime*)b);
   if (A.time < B.time) {
      return -1;
   } else if (A.time > B.time) {
      return +1;
   } else {
      return 0;
   }
}



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

void checkOptions(Options& opts) {
   opts.define("v|verbose=b", "display input osc data");
   opts.define("author=b");
   opts.define("version=b");
   opts.define("example=b");
   opts.define("help=b");
   opts.process();

   if (opts.getBoolean("author")) {
      cout << "Written by Craig Stuart Sapp, "
           << "craig@ccrma.stanford.edu, September 2002" << endl;
      exit(0);
   }
   if (opts.getBoolean("version")) {
      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 output filename
   if (opts.getArgCount() != 2) {
      cout << "Error: need two arguments." << endl;
      usage(opts.getCommand());
      exit(1);
   }

   verboseQ = opts.getBoolean("verbose");
  
   oscinfo.setSize(0);
   buildOscInfo(oscinfo, opts.getArg(1));
}
   


//////////////////////////////
//
// buildOscInfo -- 
//

void buildOscInfo(Array& oscinfo, const char* filename) {
   #ifndef OLDCPP
      fstream infile(filename, ios::in);
   #else
      fstream infile(filename, ios::in | ios::nocreate);
   #endif

   if (!infile.is_open()) {
      cout << "Error: cannot open file: " << filename << endl;
      exit(1);
   }

   oscinfo.setSize(10000);
   oscinfo.setGrowth(10000);
   oscinfo.setSize(0);

   OscInfo tempinfo;

   char buffer[100001] = {0};
   infile.getline(buffer, 100000, '\n');
   double starttime = 0.0;
   double duration = 0.0;
   double phase = 0.0;
   double frequency = -1000;
   double amplitude = -1000;
   char* ptr = NULL;
   int line = 1;
   while (!infile.eof()) {
      tempinfo.clear();
      frequency = -1000.0;
      amplitude = -1000.0;
      phase = 0;
      if (buffer[0] == ';' || buffer[0] == '/' || buffer[0] == '#') {
         infile.getline(buffer, 100000, '\n');
         line++;
         continue;
      }
      if (strncmp(buffer, "osc", 3) == 0 ||
          strncmp(buffer, "OSC", 3) == 0 ||
          strncmp(buffer, "Osc", 3) == 0 ) {
         ptr = strtok(buffer, " \t\n:,;");  // OSC identifier
         ptr = strtok(NULL, " \t\n:,;");    // start time (required)
         if (ptr == NULL) {
            cout << "Error on line " << line 
                 << ": expecting start time." << endl;
            exit(1);
         }
         starttime = strtod(ptr, NULL);
         ptr = strtok(NULL, " \t\n:,;");    // duration (required)
         if (ptr == NULL) {
            cout << "Error on line " << line 
                 << ": expecting duration." << endl;
            exit(1);
         }
         duration = strtod(ptr, NULL);
         if (duration < 0.0) {
            duration = 0.0;
         }
         ptr = strtok(NULL, " \t\n:,;");    // frequency (optional)
         if (ptr != NULL) {
            frequency = strtod(ptr, NULL);
         }
         ptr = strtok(NULL, " \t\n:,;");    // amplitude (optional)
         if (ptr != NULL) {
            amplitude = strtod(ptr, NULL);
         }
         ptr = strtok(NULL, " \t\n:,;");    // phase (optional)
         if (ptr != NULL) {
            phase = strtod(ptr, NULL);
         }

         infile.getline(buffer, 100000, '\n');
         line++;

         // cout << "Start = " << starttime << endl;
         // cout << "Duration = " << duration << endl;
         if (duration == 0.0) {
            continue;
         }
         tempinfo.startSample = (int)(starttime * srate + 0.5);
         tempinfo.stopSample  = (int)((starttime + duration) * srate + 0.5);
         if (frequency != -1000.0) {
            tempinfo.freq = frequency;
         }
         if (amplitude != -1000.0) {
            tempinfo.amp = amplitude;
         }
         tempinfo.phase = phase;
         while (isspace(buffer[0])) {
            int n = 0;
            while ((buffer[n] != '\0') && isspace(buffer[n])) {
               n++;
            }
            if (strncmp(&buffer[n], "freq", 4) == 0) {
               n = n+4;
               while ((buffer[n] != '\0') && isspace(buffer[n])) { n++; }
               tempinfo.setFreqEnv(&buffer[n]);
               // cout << "Found freq env: " << &buffer[n]<< endl;
            } else if (strncmp(&buffer[n], "amp", 3) == 0) {
               n = n+3;
               while ((buffer[n] != '\0') && isspace(buffer[n])) { n++; }
               tempinfo.setAmpEnv(&buffer[n]);
               // cout << "Found amp env: " << &buffer[n] << endl;
            } else if (strncmp(&buffer[n], "phase", 5) == 0) {
               n = n+5;
               while ((buffer[n] != '\0') && isspace(buffer[n])) { n++; }
               // cout << "Found phase: " << &buffer[n] << endl;
               tempinfo.phase = strtod(&buffer[n], NULL);
            }
            infile.getline(buffer, 100000, '\n');
            line++;
 
         }
         if (verboseQ) {
            tempinfo.print();
         }
         oscinfo.append(tempinfo);
         continue;
      }
      infile.getline(buffer, 100000, '\n');
      line++;
      // cout << buffer << endl;
   }

}



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

void example(void) {
   cout << 
   " addsyn script output.wav                                                \n"
   "   where the file script contains:                                       \n"
   "                                                                         \n"
   "osc 0.0 1.0 440 0.5 0                                                    \n"
   "osc 0.3 0.5 880 0.1                                                      \n"
   "                                                                         \n"
   "osc 1.0 1.0                                                              \n"
   "   freq 0 0 1 1000 2 500                                                 \n"
   "   amp  0 0 5 0.5  95 0.5 100 0                                          \n"
   "   phase 90                                                              \n"
   "; script comment                                                         \n"
   "                                                                         \n"
   << endl;
}



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

void usage(const char* command) {
   cout << 
   "                                                                         \n"
   "Creates a frequency and amplitude varying sinusoids from a script file.  \n"
   "                                                                         \n"
   "Usage: " << command << " script outsound                                 \n"
   "                                                                         \n"
   "Options:                                                                 \n"
   "   -v        = display a list of the parsed oscillator inputs.           \n"
   "   --options = list of all options, aliases and default values.          \n"
   "                                                                         \n"
   "                                                                         \n"
   << endl;
}



// md5sum: abd9ba92e401a40ae6fd7474da7d40f8 addsyn.cpp [20050403]