Goto: [ Program Documentation ]

//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Wed Jun 25 15:33:07 GMT-0800 1997
// Last Modified: Wed Jul  2 07:05:40 GMT-0800 1997
// Filename:      ...sig/doc/examples/sigfile/line2sine/line2sine.cpp
// Syntax:        C++; sig
//
// Description:   convert lines from a Diagram document into sinusoids.
//


#include "line2sine.h"
#include "sig.h"

#include <iostream.h>
#include <fstream.h>
#include <strstream.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
#include <stdlib.h>


//////////////////////////////
//
// Global variables:
//

double durationSeconds;      // time for the output sound in seconds
double durationSamples;      // time for the output sound in samples
double minFreq;              // value of the y coordinate at bottom of window
double maxFreq;              // value of the y coordinate at top of window
double globalAmp;            // global scaling amplitude 
double srate;                // sampling rate
double maxX = -1;            // maximum X coordinate of Diagram document
double maxY = -1;            // maximum Y coordinate of Diagram document
const double hardX = 756.0;  // maximum X coordinate of Diagram page
const double hardY = 576.501;// maximum Y coordinate of Diagram page
const int bufferSize = 1000; // max size for temporary line buffers
const char* blanks = " \t";  // blank characters for strtok function
char yaxis = 'l';            // l=linear scale, g=geometric scale

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

int main(int argc, char* argv[]) {
   if (argc != 3) exitHelp(argv);
   SoundHeader header; 
   header.setHighMono();
   srate = header.getSrate();
   char buffer[bufferSize] = {0};
   char* buff;

   cout << "Enter the duration of the Diagram window x-axis (seconds): ";
   cin  >> durationSeconds;
   durationSamples = (int)(durationSeconds * header.getSrate() + 0.5);

   int parameter;
   cout << "Do you want the y-axis to represent [0=frequencies][1=intervals]: ";
   cin  >> parameter;
   if (parameter == 1) {
      yaxis = 'g';
   }

   cout << "Enter the minimum frequency of the Diagram window (Hertz): ";
   cin  >> minFreq;
   cout << "Enter the maximum frequency of the Diagram window (Hertz): ";
   cin  >> maxFreq;
   checkMinMax(minFreq, maxFreq);

   cout << "Enter a global scaling amplitude (1.0 for no scaling): ";
   cin  >> globalAmp;

   cout << "The default amplitude envelope is " << endl << "   "
        << OscParameter::defaultAmpEnv << endl;
   cout << "Do you want to change it? [y/n]: ";
   cin.getline(buffer, bufferSize);
   cin.getline(buffer, bufferSize);
   buff = strtok(buffer, blanks);
   if (buff != NULL && tolower(buff[0]) == 'y') {
      cout << "Enter a new default amplitude envelope: " << endl << "   ";
      cin.getline(buffer, bufferSize);
      strncpy(OscParameter::defaultAmpEnv, buffer, 10000);
      cout << "New envelope is: " << OscParameter::defaultAmpEnv << endl;
   }

   cout << "Do you want a printout of the oscillator parameters? "
        << "[0=No, 1=Yes]: ";
   cin  >> parameter;

   OscParameters sines;
   inputParameters(argv[1], sines);
   if (parameter) {
      sines.print();
   }
   int numSines = sines.data.getSize();
   cout << "Total of : " << numSines << " line segments." << endl;

   // Elements:
   SoundFileOut  outsound(argv[2], header);
   Add           add;
   Envelope      freqEnvs[numSines];
   Envelope      ampEnvs[numSines];
   Scale         ampScales[numSines];

   Osc          *oscs;                // Had to do this way: Segmentation
   oscs = new Osc[numSines];          // Fault otherwise for large numSines

   // Connections:
   outsound.connect(add);
   int i;
   for (i=0; i<numSines; i++) {
      ampScales[i].connect(ampEnvs[i]);
      oscs[i].connect(freqEnvs[i]);
      oscs[i].connect(ampScales[i]);
   }

   // Set paramters for sines:
   for (i=0; i<numSines; i++) {
      ampScales[i].setScale(sines.data[i].getAmp() * globalAmp);
      freqEnvs[i].setEnvelope(sines.data[i].getFreqEnv());
      freqEnvs[i].setDurationSamples(sines.data[i].getDuration());
      ampEnvs[i].setEnvelope(sines.data[i].getAmpEnv());
      ampEnvs[i].setDurationSamples(sines.data[i].getDuration());
   }

   int startIndex = 0;
   int endIndex = 0;

   cout << "\nSeconds of sound processed: ";
   for (i=0; i<durationSamples; i++) {

      while (startIndex < numSines && sines.start[startIndex].x <= i) {
         //  cout << "Connecting    oscillator " << sines.start[startIndex].y 
         //       << " at sample " << i << endl;
         add.connect(oscs[(int)(sines.start[startIndex].y)]);
         startIndex++;
      }
      while (endIndex < numSines && sines.end[endIndex].x <= i) {
         //  cout << "Disconnecting oscillator " << sines.end[endIndex].y 
         //       << " at sample " << i << endl;
         add.disconnect(oscs[(int)(sines.end[endIndex].y)]);
         endIndex++;
      }

      if (i % 11024 == 0) {
         if (i % 44096 == 0) {
            cout << i/44096;
         } else {
            cout << "." << flush;
         }
      }

      Tick(outsound);

   }
   cout << endl;

   return 0;
}


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



//////////////////////////////
//
// checkMinMax -- check minfreq and maxfreq for correct input
//

void checkMinMax(double& min, double& max) {
   if (min < 0 || max < 0) {
      cerr << "Error: cannot have negative frequencies" << endl;
      exit(1);
   }
   if (min > max) {
      double temp;
      temp = min;
      min = max;
      max = temp;
   }
   if (max > srate/2.0) {
      cerr << "Warning: aliasing will occur with frequencies greater than "
           << srate/2.0 << " Hertz." << endl;
   }
   if (min < 20 && yaxis == 'g') {
      cerr << "Warning: minimum frequency on interval scale cannot be less"
           << " than 20 Hz." << endl << "Resetting the minimum to 20." << endl;
      min = 20;
   }
   if (max < 20 && yaxis == 'g') {
      cerr << "Warning: maximum frequency on interval scale cannot be less"
           << "than 20 Hz." << endl << "Resetting the maximum to 20." << endl;
      max = 20;
   }
}



//////////////////////////////
//
// exitHelp -- prints help for program, then exits the program
//

void exitHelp(char* args[]) {
   cout << "Usage: " << args[0] << " input.diagram2 output.snd" << endl;
   cout << endl;

   // more help to go here.

   exit(1);
}



//////////////////////////////
//
// extractAmpEnvelope -- creates the amplitude envelope for
//      the current line in the input file.
//

void extractAmpEnvelope(fstream& infile, OscParameter& output) {
   static char buffer[1000] = {0};
   int index = 0;
   char ch = 'a';
   char och = 'a';
   char ooch = 'a';

   do {
      ooch = och;
      och = ch;
      if (infile.eof()) {
         cerr << "Error: unexpected end of file when searching for"
              << " amplitude envelope!" << endl;
         exit(1);
      }
      infile.get(ch);
      if (ch == 'd' && och == 'n' && ooch == 'e') return;
   } while (ch != '(');
   buffer[index++] = ch;

   do {
      infile.get(ch);
      buffer[index++] = ch;
   } while (!infile.eof() && ch != ')');
   if (infile.eof()) {
      cerr << "Error: unexpected end of file when searching for"
           << " amplitude envelope!" << endl;
      exit(1);
   }
   buffer[index] = '\0';

   output.setAmpEnv(buffer);
}



//////////////////////////////
//
// findLine -- returns the number tag for the current line being 
//	processed in the file stream.  Returns -1 if no more lines.
//

int findLine(fstream& infile) {
   int output = 0;
   static char buffer[bufferSize];

   while(infile.getline(buffer, bufferSize)) {
      if (strncmp(buffer, "line ", 5) == 0) { 
         strtok(buffer, blanks);
         output = string2int(strtok(NULL, blanks));
         if (output > 2000000000) {
            continue;
         } else {
            return output;
         }
      }
   }
   return -1;  // end of file
}



//////////////////////////////
//
// findVertex -- returns the number tag for the current vertex being 
//	processed in the file stream.  Returns -1 if no more vertices.
//

int findVertex(fstream& infile) {
   int output = 0;
   static char buffer[bufferSize];

   while(infile.getline(buffer, bufferSize)) {
      if (strncmp(buffer, "line ", 5) == 0) { 
         return -1;   // end of vertices list
      }
      if (strncmp(buffer, "vertex ", 7) == 0) {
         strtok(buffer, blanks);
         output = string2int(strtok(NULL, blanks));
         if (output > 2000000000) {
            continue;
         } else {
            return output;
         }
      }
   }
   return -1;  // end of file
}



//////////////////////////////
//
// getDimensions -- gets the window dimensions of the Diagram
//      document, and also gets the number of lines.  Origin
//      is in the upper left corner.
//

void getDimensions(const char* filename, double& x, double& y) {
   char realFilename[1000] = {0};
   strcat(realFilename, filename);
   strcat(realFilename, "/PrintInfo");
   ifstream infile(realFilename);
   if (!infile.is_open()) {
      cerr << "Error: cannot open file: " << realFilename << endl;
      exit(1);
   }

   unsigned char ch, cho, choo, chooo;
   ch = cho = choo = chooo = 0;

   while (!infile.eof()) {
      chooo = choo;
      choo = cho;
      cho = ch;
      infile.get(ch);

      if (ch == 'c' && cho == 'i' && choo == 'i' && chooo == 'i') {
         x = extractNumber(infile) * hardX;
         y = extractNumber(infile);  // this value is not being used
         if (infile.eof()) {
            cerr << "Error: unexpected end of file while reading "
                 << "Diagram dimensions" << endl;
            exit(1);
         }
         return;
      }
   }
}



//////////////////////////////
//
// extractNumber -- for use only with getDimensions.
//

int extractNumber(ifstream& infile) {
   static int status = -1;
   int numDigits;
   unsigned char ch;
   int i;
   int output = 0;

   if (status == -1) {
      infile.get(ch);
      if (ch < 0x80) {
         status = 0;
         return (int)ch;
      } else {
         status = 1;
         numDigits = (ch & 0x0f) + 1;
         for (i=0; i<numDigits; i++) {
            infile.get(ch);
            output += (int)ch * (int)pow(16, i * 2);
         }
         return output;
      }
   }

   if (status == 0) {
      infile.get(ch);
      return (int)ch;
   } else if (status == 1) {
      infile.get(ch);
      while (ch < 0x80) infile.get(ch);
      numDigits = (ch & 0x0f) + 1;
      for (i=0; i<numDigits; i++) {
         infile.get(ch);
         output += (int)ch * (int)pow(16, i * 2);
      }
      return output;
   }

   cerr << "Unexpected error reading Diagram dimensions" << endl;
   exit(1);
}




//////////////////////////////
//
// getLineInfo -- return parameter information for one sinusoid.
//      you give it the file stream and it will extract the next
//      line from it.  It is an error to look for a line when
//      there are no more lines in Diagram file.
//

OscParameter getLineInfo(fstream& infile, Vertices& vertices) {

   static char buffer[bufferSize] = {0}; // temporary storage for input file
   OscParameter output;                  // output of function
   char *buff;                           // temp pointer for words in string

   while(infile.getline(buffer, bufferSize)) {
      // skip to the line after "to" command"
      buff = strtok(buffer, blanks);
      if (strcmp(buff, "to") == 0) {

         // process the frequency envelope
         Collection<doubleXY> freqCoords;
         doubleXY temp;
         freqCoords.setSize(0);
         freqCoords.allowGrowth();
         infile.getline(buffer, bufferSize);
         buff = strtok(buffer, blanks);
         while (isdigit(buff[0])) {
            temp = vertices.get(string2int(buff));
            temp.x *= durationSamples;
            freqCoords.append(temp);
            infile.getline(buffer, bufferSize);
            buff = strtok(buffer, blanks);
         }
         qsort(freqCoords.getBase(), freqCoords.getSize(), sizeof(doubleXY),
            xyComp);
         output.setStart((long)freqCoords[0].x);
         output.setEnd((long)freqCoords[freqCoords.getSize()-1].x);

         {
            strstream freqStream;
            freqStream << yaxis << "(";
            for (int i=0; i<freqCoords.getSize(); i++) {
               freqStream << (long)(freqCoords[i].x - output.getStart());
               freqStream << " ";
               freqStream << freqCoords[i].y;
               if (i != freqCoords.getSize() - 1) {
                  freqStream << "; ";
               }
            }
            freqStream << ")" << ends;
            output.setFreqEnv(freqStream.str());
         }

         // now check for amp envelope, then for local amplitude:
   
         if (strcmp(buff, "layer") != 0) {
            cerr << "Error: expected layer command but got \""
                 << buff << "\" instead." << endl;
            exit(1);
         }
   
         infile.getline(buffer, bufferSize);
         buff = strtok(buffer, blanks);

         // ignore the curved command
         if (strcmp(buff, "curved") == 0) {
            infile.getline(buffer, bufferSize);
            buff = strtok(buffer, blanks);
         }

         if (strcmp(buff, "rtfText") == 0) {
            extractAmpEnvelope(infile, output);


            do {
               infile.getline(buffer, bufferSize);
               buff = strtok(buffer, blanks);
               if (buff == NULL) {
                  infile.getline(buffer, bufferSize);
                  buff = strtok(buffer, blanks);
               }
            } while (!infile.eof() && strncmp(buff, "lineWidth", 9) != 0);
         }
         if (infile.eof()) {
            cerr << "Error: unexpected end of file." << endl;
            exit(1);
         }
         if (strcmp(buff, "lineWidth") != 0) {
            cerr << "Error: expected the lineWidth command but got \""
                 << buff << "\" instead." << endl;
            exit(1);
         }

         buff = strtok(NULL, blanks);
         if (!isdigit(buff[0])) {
            cerr << "Error: expecting a number, but got \""
                 << buff << "\" instead." << endl;
            exit(1);
         }
         output.setAmp(string2double(buff)*0.035); // .035 for cm scaling

         return output;
      }
   }
   cerr << "Error processing line info" << endl;
   exit(1);
}
   


//////////////////////////////
//
// getVertexCoordinates -- returns the vertex coordinates
//      You must guarentee that valid data can be extracted
//      X value is in fractions of the total Duration.
//      Y value is in hertz.
//

doubleXY getVertexCoordinates(fstream& infile) {
   doubleXY output;
   static char buffer[bufferSize];
   char *buff;

   while(infile.getline(buffer, bufferSize)) {
      buff = strtok(buffer, blanks);
      if (strcmp(buff, "location") == 0) {
         output.x = string2double(strtok(NULL, blanks)) / maxX;
         output.y = string2double(strtok(NULL, blanks));

         // mod the y-axis to the primary page row:
         output.y = ((double)output.y/hardY - (int)(output.y/hardY)) * hardY;

         if (yaxis == 'l') {
            output.y = (maxFreq - minFreq) * (hardY - output.y) / hardY + 
               minFreq;
         } else if (yaxis == 'g') {
            output.y = minFreq * pow(maxFreq/minFreq, (hardY - output.y)/hardY);
         } else {
            cerr << "Unknown interpolation type: " << yaxis << endl;
            exit(1);
         }
         return output;
      }
   }
   cerr << "Error: end of file without vertex coordinates" << endl;
   exit(1);
}



//////////////////////////////
//
// inputParameters
//

void inputParameters(const char* tempFilename, OscParameters& p) {
   char filename[1000] = {0};
   strcat(filename, tempFilename);
   strcat(filename, "/DiagramText");

   
   fstream infile;
   infile.open(filename, ios::in);
   if (!infile.is_open()) {
      cerr << "Error: Cannot open file: " << filename << endl;
      exit(1);
   }

   getDimensions(tempFilename, maxX, maxY);
   Vertices vertices;
   readVertices(filename, vertices); 

   int lineNum = 0;
   int index = 0;
   while (lineNum >= 0) {
      lineNum = findLine(infile); 
      if (lineNum < 0) break;
      p.data[index] = getLineInfo(infile, vertices); 
      p.start[index].x = p.data[index].getStart();
      p.start[index].y = index;
      p.end[index].x = p.data[index].getEnd();
      p.end[index].y = index;
      index++;
   }
  
   p.sort();
}



//////////////////////////////
//
// readVertices -- reads in an array of the vertices for
//      processing with lines.
//

void readVertices(char* filename, Vertices& vertices) {
   fstream infile;
   infile.open(filename, ios::in);
   if (!infile.is_open()) {
      cerr << "Error: Cannot open file: " << filename << endl;
      exit(1);
   }

   int vertexTag = 0;
   while (vertexTag >= 0) {
      vertexTag = findVertex(infile);
      if (vertexTag < 0) break;
      vertices.add(vertexTag, getVertexCoordinates(infile));
   }
}



//////////////////////////////
//
// string2double -- converts a string into a float
//

double string2double(const char* aString) {
   strstream tempStream;
   tempStream << aString;
   double output;
   tempStream >> output;
   return output;
}



//////////////////////////////
//
// string2int -- converts a string into an int
//

int string2int(const char* aString) {
   strstream tempStream;
   tempStream << aString;
   int output;
   tempStream >> output;
   return output;
}