//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Tue Feb  2 16:49:52 PST 1999
// Last Modified: Sat Mar 13 16:18:43 PST 1999
// Filename:      ...sig/doc/examples/sig/sigcontrol/sloop/sloop.cpp
// Syntax:        C++; synthImprov; sig
//  
// Description:   Create soundfile loops in real-time with optional
//                saving of the mix to a soundfile.
//
// Summary of sound signal connections:
//    soundfiles -> soundloops -> add => scale => speakers -> outfile
// 

/* Keyboard Interface Commands:
      -  / =       = lower/raise volume level

   File Commands:           
      f <x> <file> = load soundfile <file> into memory <x>
      s            = show list of soundfiles in memory
      m            = show memory taken up by soundfiles in memory
      o <file>     = set output filename(will not overwrite old files)
      r            = start/stop recording of output file
   
   Loop Commands: 
      a <x> <y>    = create loop <x> using soundfile number <y> 
      d            = display current loops in memory
      l <x>        = focus on loop <x>
      i            = show information of current loop
      b            = start at beginning of loop
      < / >        = loop backwards / forwards
      t / p        = straight loop / palindrome loop
      q <x>        = fill end of period with <x> number of blank samples
      [ ] / { }    = fine loop end controls/gross loop end controls
      ; ' / : "    = fine loop begin controls/gross loop begin controls
      <space>      = pause/unpause current loop
   
      <esc>        = cancel current input
*/
   
#include "sig.h"
#include "synthImprov.h" 


/*----------------- beginning of improvization algorithms ---------------*/

// function declarations:
void runcommands(int key);
void displayLoopInformation(int loopnumber = -1);

#define MODE_COMMAND     0
#define MODE_FILENAME    1
#define MODE_OUTFILENAME 2
#define MODE_LOOPNUM     3
#define MODE_LOOPADD     4
#define MODE_BLANK       5
int inputMode = MODE_COMMAND;

Array<char> input_buffer;

// sound elements

SoundHeader   header;
LinuxSoundOut speakers(44100, 2);
SoundFileOut *fileout = NULL;
Scale         scale(0.1);
Add           add;

SoundLoop     refer;
#define MAX_SOUNDFILES 25

Collection<SoundFileInMemory*> soundfiles;  // list of soundfile in memory
Collection<SoundLoop*>         soundloops;  // list of soundfile loops
int           focusLoop = -1;               // currently active loop
int           outrecordingQ = 0;            // for recording output file
int           sampleStep    = 10;           // for loop begin/end settings

Action        action;
SigTimer      soundTimer;


double        smoothFactor  = 0.0005;


/*--------------------- maintenance algorithms --------------------------*/

void description(void) {
   cout 
   << "sloop: Interactive sound looping program\n"
   << "\n"
   << "   -  / =       = lower/raise volume level\n"
   << "File Commands:           \n"
   << "   f <x> <file> = load soundfile <file> into memory <x>\n"
   << "   s            = show list of soundfiles in memory\n"
   << "   m            = show memory taken up by soundfiles in memory\n"
   << "   o <file>     = set output filename (will not overwrite old files)\n"
   << "   r            = start/stop recording of output file\n"
   << "\n"
   << "Loop Commands: \n"
   << "   a <x> <y>    = create loop <x> using soundfile number <y> \n"
   << "   d            = display current loops in memory\n"
   << "   l <x>        = focus on loop <x>\n"
   << "   i            = show information of current loop\n"
   << "   b            = start at beginning of loop\n"
   << "   < / >        = loop backwards / forwards\n"
   << "   t / p        = straight loop / palindrome loop\n"
   << "   q <x>        = fill end of period with <x> number of blank samples\n"
   << "   [ ] / { }    = fine loop end controls/gross loop end controls\n"
   << "   ; ' / : \"    = fine loop begin controls/gross loop begin controls\n"
   << "   <space>      = pause/unpause current loop\n"
   << "\n"
   << "   <esc>        = cancel current input\n"
   << endl;
}

void initialization(void) { 

   input_buffer.setSize(0);
   input_buffer.allowGrowth();

   header.setHighMono();

   soundfiles.setSize(MAX_SOUNDFILES);
   for (int i=0; i<MAX_SOUNDFILES; i++) {
      soundfiles[i] = NULL;
   }
   
   soundloops.setSize(30);
   for (int i=0; i<soundloops.getSize(); i++) {
      soundloops[i] = NULL;
   }
   soundloops.allowGrowth(0);
  
   // sound element connections

   scale.connect(add);
   speakers.connect(scale);

   header.setHighMono();
   fileout = new SoundFileOut("test.snd", header);
   fileout->connect(speakers);

   eventIdler.setPeriod(0);
   soundTimer.setPeriod(1);
}


void finishup(void) { 
   if (fileout != NULL) {
      delete fileout;
   }
   
   int i;
   for (i=0; i<soundfiles.getSize(); i++) {
      if (soundfiles[i] != NULL) {
         delete soundfiles[i];
         soundfiles[i] = NULL;
      }
   }

   for (i=0; i<soundloops.getSize(); i++) {
      if (soundloops[i] != NULL) {
         delete soundloops[i];
         soundloops[i] = NULL;
      }
   }

}


/*-------------------- main loop algorithms -----------------------------*/

void mainloopalgorithms(void) { 


   if (soundTimer.expired()) {
      soundTimer.reset();
      action.tick(*fileout, 512);
   }
}


/*-------------------- triggered algorithms -----------------------------*/

void keyboardchar(int key) { 
   if (key == 27) {             // escape key
      switch (inputMode) {
         case MODE_LOOPADD:
         case MODE_LOOPNUM:
         case MODE_FILENAME:
         case MODE_OUTFILENAME:
         case MODE_BLANK:
            cout << "\nCancelled" << endl;
            input_buffer.setSize(0);
            break;
         default:
            input_buffer.setSize(0);
            break;
      }
      inputMode = MODE_COMMAND;
   }

   if (key == '\n') {
      switch (inputMode) {
         case MODE_LOOPADD:
         {
            cout << endl;
            int temp1 = 0;
            int temp2 = 0;
            char* current;
            current = strtok(input_buffer.getBase(), " \n\t");
            if (current == NULL || current[0] == '\0') {
               cout << "Invalid loop addition, canceled" << endl;
               inputMode = MODE_COMMAND;
               input_buffer.setSize(0);
               return;
            } else {
               temp1 = atoi(current) - 1;
            }
          
            current = strtok(NULL, " \n\t");
            if (current == NULL || current[0] == '\0') {
               cout << "Invalid filename addition, canceled" << endl;
               inputMode = MODE_COMMAND;
               input_buffer.setSize(0);
               return;
            } else {
               temp2 = atoi(current) - 1;
            }

            if (temp1 == soundloops.getSize()) {
               soundloops[temp1] = NULL;
            }
            if (soundloops[temp1] == NULL) {
               soundloops[temp1] = new SoundLoop;
            }
            soundloops[temp1]->borrow(*soundfiles[temp2]);
            soundloops[temp1]->reset();
            focusLoop = temp1;
            add.disconnect(*soundloops[temp1]);
            add.connect(*soundloops[temp1]);

            input_buffer.setSize(0);
            inputMode = MODE_COMMAND;
            return;
         }
         case MODE_LOOPNUM:
         {
            cout << endl;
            char* current;
            current = strtok(input_buffer.getBase(), " \n\t");
            if (current == NULL || current[0] == '\0') {
               cout << "Selected Loop is: " << focusLoop+1 << endl;
               input_buffer.setSize(0);
               inputMode = MODE_COMMAND;
               return;
            } else {
               int temp = atoi(current);
               if (temp < 0 || temp > soundloops.getSize()) {
                  cout << "Error: selected loop is out of range." << endl;
                  cout << "Keeping prior loop: " << focusLoop+1 << endl;
               } else {
                  focusLoop = temp-1;
                  cout << "Selected loop is: " << focusLoop+1 << endl;
               }
            }
            inputMode = MODE_COMMAND;
            return;
         }
         case MODE_FILENAME:
         {  
            cout << endl;
            char blank = '\0';
            input_buffer.append(blank);
            char* current;
            current = strtok(input_buffer.getBase(), " \n\t");
            int location = atol(current) - 1;
            if (location < 0 || location >= MAX_SOUNDFILES) {
               cout << "Error: soundfile location out of range." << endl;
               inputMode = MODE_COMMAND;
               return;
            }
            current = strtok(NULL, " \n\t");
            if (soundfiles[location] == NULL) {
               soundfiles[location] = new SoundFileInMemory;
            }
            soundfiles[location]->setFile(current);
            cout << "file " << location + 1 << " set to " 
                 << current << endl;
            input_buffer.setSize(0);
            inputMode = MODE_COMMAND;
            return;
         }
         case MODE_OUTFILENAME:
         {  
            char blank = '\0';
            input_buffer.append(blank);
            char* current;
            current = strtok(input_buffer.getBase(), " \n\t");
            if (fileout != NULL) {
               delete fileout;
            }
            fileout = new SoundFileOut(current, header);
            cout << "Output soundfile set to : " 
//                 << fileout->getFilename() 
                  << "unknown"
                 << endl;
            input_buffer.setSize(0);
            inputMode = MODE_COMMAND;
            return;
         }
            
         case MODE_BLANK:
         {  
            char blank = '\0';
            input_buffer.append(blank);
            int blanks = atoi(input_buffer.getBase());
            if (focusLoop >= 0) {
               soundloops[focusLoop]->setFill(blanks);
            }
            input_buffer.setSize(0);
            inputMode = MODE_COMMAND;
            return;
         }
         
         default:
            inputMode = MODE_COMMAND;
            return;
      }
   }

   switch (inputMode) {
      case MODE_LOOPNUM:               // adding char to loop selection
      case MODE_LOOPADD:               // adding char to loop selection
      case MODE_FILENAME:               // adding char to input filename
      case MODE_OUTFILENAME:            // adding char to output filename
      case MODE_BLANK:                  // adding char to blank count
      {  
         char realkey = (char)key;
         if (input_buffer.getSize() > 0 && (realkey == 127 || realkey == 8)) {
            input_buffer.setSize(input_buffer.getSize()-1);
            cout << "\b \b";
         } else {
            cout << realkey << flush;
            input_buffer.append(realkey);
         }
         return;
      }

      default:
         break;
   }

   runcommands(key);

}


//////////////////////////////
//
// runcommands -- keyboard commands and what they do
//

void runcommands(int key) {
   int i;
   switch (key) {
      case 's':                   // display soundfile list
         for (i=0; i<MAX_SOUNDFILES; i++) {
            if (soundfiles[i] != NULL) {
               cout << "file: " << i+1 << '\t'
                    << soundfiles[i]->getFilename() 
                    << endl;
            }
         }
         break; 

      case 'f':                   // load a soundfile
         cout << "\nEnter (1) sound location and (2) soundfile name: " << flush;
         inputMode = MODE_FILENAME;
         break;

      case 'a':                   // add a loop
         cout << "\Enter (1) the loop number to add/replace and \n"
                  "(2) soundfile num: " << flush;
         inputMode = MODE_LOOPADD;
         break;
    
      case 'l':                   // select a loop
         cout << "\nEnter a loop to focus on: " << flush;
         inputMode = MODE_LOOPNUM;
         break;

      case 'm':                   // display soundfile memory usage
         cout << "using: " << SoundFileInMemory::getMemoryUsage()/1024.0
              << " kbytes for soundfiles" << endl;
         break;

      case '-':                   // lower the volume
      {
         double value;
         value = scale.getScale();
         value *= 0.95;
         cout << "Volume set to: " << value << endl;
         scale.setScale(value);
         break;
      }

      case '=':                   // raise the volume
      {
         double value;
         value = scale.getScale();
         value *= 1.05;
         cout << "Volume set to: " << value << endl;
         scale.setScale(value);
         break;
      }

      case '<':                   // loop backwards
         if (focusLoop >= 0) {
            cout << "Loop " << focusLoop << " backwards " << endl;
            soundloops[focusLoop]->goBackward();
         }
         break;

      case '>':                   // loop forwards
         if (focusLoop >= 0) {
            cout << "Loop " << focusLoop << " forwards " << endl;
            soundloops[focusLoop]->goForward();
         }
         break;

      case 't':                   // loop straight
         if (focusLoop >= 0) {
            cout << "Loop " << focusLoop << " straight " << endl;
            soundloops[focusLoop]->loopStraight();
         }
         break;

      case 'p':                   // loop palindrome
         if (focusLoop >= 0) {
            cout << "Loop " << focusLoop << " palindrome " << endl;
            soundloops[focusLoop]->loopPalindrome();
         }
         break;
 
      case 'b':                   // loop begin
         if (focusLoop >= 0) {
            cout << "Loop " << focusLoop << " beginning " << endl;
            soundloops[focusLoop]->gotoStart();
         }
         break;
 
      case 'i':                   // loop information 
         if (focusLoop >= 0) {
            displayLoopInformation(focusLoop);
         }
         break;
 
      case 'd':                   // all loops information 
         displayLoopInformation();
         break;
 
      case '[':                   // loop end fine control down
         if (focusLoop >= 0) {
            soundloops[focusLoop]->changeEnd(-1);
            cout << "Loop " << focusLoop << " end = " 
                 << soundloops[focusLoop]->getEnd() << endl;
         }
         break;
 
      case ']':                   // loop end fine control up
         if (focusLoop >= 0) {
            soundloops[focusLoop]->changeEnd(1);
            cout << "Loop " << focusLoop << " end = " 
                 << soundloops[focusLoop]->getEnd() << endl;
         }
         break;
 
      case '{':                   // loop end gross control down
         if (focusLoop >= 0) {
            soundloops[focusLoop]->changeEnd(-sampleStep);
            cout << "Loop " << focusLoop << " end = " 
                 << soundloops[focusLoop]->getEnd() << endl;
         }
         break;
 
      case '}':                   // loop end gross control up
         if (focusLoop >= 0) {
            soundloops[focusLoop]->changeEnd(sampleStep);
            cout << "Loop " << focusLoop << " end = " 
                 << soundloops[focusLoop]->getEnd() << endl;
         }
         break;
 
      case ';':                   // loop begin fine control down
         if (focusLoop >= 0) {
            soundloops[focusLoop]->changeBegin(-1);
            cout << "Loop " << focusLoop << " end = " 
                 << soundloops[focusLoop]->getEnd() << endl;
         }
         break;
 
      case '\'':                  // loop begin fine control up
         if (focusLoop >= 0) {
            soundloops[focusLoop]->changeBegin(1);
            cout << "Loop " << focusLoop << " end = " 
                 << soundloops[focusLoop]->getEnd() << endl;
         }
         break;
 
      case ':':                  // loop begin gross control down
         if (focusLoop >= 0) {
            soundloops[focusLoop]->changeBegin(-sampleStep);
            cout << "Loop " << focusLoop << " end = " 
                 << soundloops[focusLoop]->getEnd() << endl;
         }
         break;
 
      case '\"':                 // loop begin gross control up
         if (focusLoop >= 0) {
            soundloops[focusLoop]->changeBegin(sampleStep);
            cout << "Loop " << focusLoop << " end = " 
                 << soundloops[focusLoop]->getEnd() << endl;
         }
         break;

   }
}



//////////////////////////////
//
// displayLoopInformation --
//     default value: loopnumber = -1
//

void displayLoopInformation(int loopnumber) {
   cout << "Loop information goes here" << endl;
}



/*------------------ end improvization algorithms -----------------------*/



// md5sum: d665982661ab5d2a40d2c554bedc60ac sloop.cpp [20050403]