//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Mon Oct 26 17:00:30 PST 1998
// Last Modified: Thurs 30 Oct 1998
// Last Modified: 22 Nov 1998 (changed Nidaq class to be multi channel input)
// Filename:      ...sig/doc/examples/improv/synthImprov/position1/position1.cpp
// Syntax:        C++; synthImprov 2.0; (Windows 95 only)
//  
// Description: plays notes according to 1D input data coming from NIDAQ card.
//

#include "synthImprov.h"    /* basic MIDI I/O interface with MIDI synthesizer */
#include "Nidaq.h"          /* interface with NIDAQ Card(Windows 95 only)    */


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

// global variables:
Nidaq       sensor;          // NIDAQ card interface for sensor
int         channel = 0;     // the NIDAQ channel to listen to
Voice       voice;           // for MIDI output (auto handles note off messages)

double      min = 10.0;      // miniumum value from NIDAQ, adapts
double      max = -10.0;     // maximum value from NIDAQ, adapts

int         keyno;           // MIDI keynumber to play
int         velocity;        // MIDI attack velocity
int         minDur = 20;     // minimum duration of output notes

SigTimer    displayTimer;    // for displaying positions on the screen
int         display = 0;     // boolean for display sensor data on screen


// non-interface function declarations:
void makeNote(void);


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

//////////////////////////////
//
// description -- this function is called by the improv interface 
//     whenever a capital "D" is pressed on the computer keyboard.
//     Put a description of the program and how to use it here.
//

void description(void) {
   cout << '\n';
   printboxtop();
   pcl("POSITION: by Craig Stuart Sapp <craig@ccrma.stanford.edu> 26 Oct 1998");
   pcl("version 22 November 1998");
   printintermediateline();
   psl(" NIDAQ device initially on channel 0. Initial data range is");
   psl(" 2.4 to 2.5, but range adapts.  So go through whole range of sensor");
   psl(" to set the software range.");
   psl(" Commands:");
   psl(" \"0\"-\"9\" = select NIDAQ analog input channel");
   psl(" \"d\" = toggle sensor display.");
   printboxbottom();
} 



//////////////////////////////
//
// initialization -- this function is called by the improv
//     intervace once at the start of the program.  Put items
//     here which need to be initialized at the beginning of 
//     the program.
//

void initialization(void) { 
   sensor.initialize(options.argv()); // start CVIRTE stuff for NIDAQ card
   sensor.setPollPeriod(1);      // check for new data every 1 millisecond   
   sensor.setFrameSize(1);       // data transfer size from NIDAQ card
   sensor.setModeLatest();       // just look at most recent data in buffer
   sensor.setSrate(500);         // set NIDAQ sampling rate to X Hz
   sensor.activateAllChannels(); // turn on all channels for sampling 
   cout << "starting data aquisition ... " << flush;
   sensor.start();              // start aquiring data from NIDAQ card
   cout << "ready." << endl;

   voice.setPort(synth.getOutputPort());   // specify output port of voice
   voice.setChannel(0);         // specify output chan of voice

   displayTimer.setPeriod(200); // display position every X milliseconds
}



//////////////////////////////
//
// finishup -- this function is called by the improv interface
//     whenever the function is exited.  Put items here which
//     need to be taken care of when the program is finished.
//

void finishup(void) { }


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


//////////////////////////////
//
// mainloopalgorithms -- this function is called by the improv interface
//   continuously while the program is running.  The variable t_time 
//   which stores the current time is set just before this function is 
//   called and remains constant while in this functions.
//

void mainloopalgorithms(void) { 
   sensor.checkPoll();   // see if it is time to check for new data frame
   makeNote();           // check if it is time to play a MIDI note

   if (display && displayTimer.expired()) {
      displayTimer.reset();
      cout << "\rmin = " << min << "\tmax = " << max << "\tcurrent = "
           << sensor[channel][0] << "\t\t" << flush;
   }
}



//////////////////////////////
//
// makeNote -- plays a new note whenever sensor detects
//     a new note value, but limit the minimum time between
//     notes.

void makeNote(void) {
   // if the duration of the previous note is long enough and
   // the sensor is not at its min or max value, then play a note
   // if the interval distance has been traversed since the last
   // note which was played.
   if (t_time - voice.getOnTime() > minDur) {

      // adjust the range of the sensor values if necessary:
      if (sensor[channel][0] > max)   max = sensor[channel][0];
      if (sensor[channel][0] < min)   min = sensor[channel][0];
 
      // keyno is the note to be played.  It is in the range from 0 to 127
      keyno = 127 - (int)((sensor[channel][0] - min) / (max - min) * 127);
   
      // limit the range of keyno to valid range, in case max/min not correct
      if (keyno < 0)  keyno = 0;
      if (keyno > 127)  keyno = 127;

      if (keyno < 2 || keyno > 125) { // turn off notes out of sensor range
         voice.off();
      } else {
         velocity = 120;
         voice.play(keyno, velocity);
      }
   }
}


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

///////////////////////////////
//
// keyboardchar -- this function is called by the improv interface
//     whenever a key is pressed on the computer keyboard.
//     Put commands here which will be executed when a key is
//     pressed on the computer keyboard.
//

void keyboardchar(int key) { 

   // select a NIDAQ channel if the key is a number from 0 to 9:
   if (isdigit(key)) {
      cout << "\nresetting data aquisition ... " << flush;
      channel = key - '0';
      cout << "done." << endl;
      cout << "Channel " << key - '0' << " selected" << endl;
      return;
   }

   switch (key) {
      case 'd':          // toggle display of sensor data on screen
         display = !display;
         break;
   }
}


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


// md5sum: 3a695dbb5cacad35e71d160268d345ec position1.cpp [20050403]