class Oscillator {
  float speed; //in milliseconds
  int spawnMillis;
  int pauseMillis;
  int beatCount = 0;
  int[] possibleModes;
  int mode;

  //int function; //function to be called by bang-oscillators
  float position; // between 0 and TWO_PI
  float value; // is returned, constrained between "low" and "high"
  float low = -1.0;
  float high = 1.0;
  float minBPM, maxBPM;
  boolean playing, pause, valInvert;

  Oscillator(float bpm_, int[] osciModes_) {
    minBPM = 2;
    maxBPM = 5;
      
    bpm(bpm_);
    possibleModes = osciModes_;
    mode = possibleModes[int(random(possibleModes.length))];
    play();
    
  }

  void amplitude(float low_, float high_) {
    low = low_;
    high = high_;
  }
  void frequency(float hertz_) {
    speed = 1000/hertz_;
  }
  void bpm(float bpm_) {
    speed = int(60 / bpm_ * 1000);
  }

  void play() {
    if (pause) {
      float pauseTime = millis() - pauseMillis;
      spawnMillis += pauseTime;
      pause = false;
    } else if (!playing) {
      spawnMillis = millis();
    }
    playing = true;
  }
  void pause() {
    if (playing) {
      pauseMillis = millis();
      pause = true;
      playing = false;
    }
  }
  void stop() {
    beatCount = 0;
    playing = false;
    value = 0;
  }

  boolean beat = false;
  void update() {
    if (playing) {
      while (millis() - spawnMillis > speed*beatCount) {
        beat = true;
        beatCount++;
        if (random(1) > .5) { 
          mode = possibleModes[int(random(possibleModes.length))];
          bpm(random(minBPM, maxBPM));
        }
      }

      float millisToBeat = millis() - spawnMillis - (speed*beatCount);
      position = map(-millisToBeat, speed, 0, 0, TWO_PI); //position in time between last beat and next beat

      switch(mode) {
        case(SINE):
        value = sin(position);
        break;
        case(SAWTOOTH):
        value = ((position % TWO_PI)-PI)/PI;
        break;
        case(SAWTOOTHINVERSE):
        value = (((position % TWO_PI)-PI)/PI)*-1;
        break;
        case(SQUAR):
        value = sin(position > PI ? 1 : -1);
        break;
        case(TRIANG):
        value = position-PI < 0 ? ((position % TWO_PI)-PI)/PI : (((position % TWO_PI)-PI)/PI)*-1;
        value+=0.5;
        value*=2;
        if (value == 3.0) value = -1;
        break;
        case(TAN):
        value = tan(position);
        break;
        case(TANINVERSE):
        value = tan(position)*-1;
        break;
        case(RAMP):
        value = sin(position) > 0 ? ((position % TWO_PI)-PI)/PI : 0;
        value+=0.5;
        value*=2;
        if (value == 3.0) value = -1;
        break;
        case(RAMPINVERSE):
        value = sin(position) > 0 ? ((position % TWO_PI)-PI)/PI : 0;
        value+=0.5;
        value*=2;
        if (value == 3.0) value = -1;
        value = value*-1;
        break;
        case(RAMPUPDOWN):
        if (sin(position) <= -1.6666666666) value = -1.6666666666;
        else if (sin(position) < 1.6666666666 && sin(position) > -1.6666666666) value = sin(position);
        else if (sin(position) >= 1.6666666666) value = 1.6666666666;
        value *= 1.5;
        break;
        case(RANDOM):
        //value = random(-1,1);
        break;
      default:
        break;
      }
      if (mode != RANDOM) {
        value = constrain(value, -1.0, 1.0);
        value = map(value, -1.0, 1.0, low, high);
      } else if (beat && mode == RANDOM) {
        beat = false;
          value = random(low, high);
      }
    }
  }
}
