import processing.core.*; import processing.opengl.*; import ddf.minim.analysis.*; import ddf.minim.*; import damkjer.ocd.*; import java.applet.*; import java.awt.*; import java.awt.image.*; import java.awt.event.*; import java.io.*; import java.net.*; import java.text.*; import java.util.*; import java.util.zip.*; public class sound_visualization_3d_circle extends PApplet {






/*
 \u201cAssignment 1A 
 Revisualizing sound: Building a meaningful equalizer from minim.  Sound has been visualized in a 
 number of ways from waves to bad ipod equalizers. How would you know it was sound? How would 
 you explain it to someone who's deaf. What is purpose of visualizing sound differently? Could you think 
 of it as drawing with your voice?\u201d
 ---
 As a drawing tool: sound volume is really changes in pressure. 
 An airbrush or paintbrush change the thickness of their strokes based on pressure. 
 Can the drawing tool use sound amplitude in place of physical pressure?
 
 How about opacity? Use the same technique? Have to hum lightly to make any mark at all? 
 
 What about color? Map a range of frequencies to a spectrum of color? 
 Human voice, piano, etc? This is less useful in a noisy environment with many overlapping sounds.
 
 */

//For Minim:
AudioInput in;
FFT fftLog;

// For the camera:
Camera camera1;
Camera camera2; // follow camera?

// this will vary based on the system input level
// seems to be 0-1 floating point
float maxInputLevel = 1.0f;

// flags to control the drawing
boolean drawing = true;
boolean drawBackground = true; // toggle the background
boolean displayFrequencyIndex = false; // debugging to display the loudest frequency
boolean mouseControl = false; // toggle for mouse drawing control
boolean longLines = false; // use the amplitude for the horizontal increment or not
boolean displayHelp = false; // add overlays with spectrum and key commands
boolean drawColor = true; // map the spectrum to color

// enable the user to alter the spectrum range on the fly
float spectrumClickRange[] = new float[2];

// set the number of "bins" for the FFT
int bins = 1024;

float drawX, drawY; // to record the drawing locations
float verticalStep = 20;
float horizontalStep = 1;

// limit the range of bins to sample, arbitrarily
int range[] = {
  3, 27};

// store all the hostory in an array
int historySize = 2048;  // test starting length (12800 just about fills a 512x512 screen)
float history[];
int historyColor[];
int historyIndex = 0; // store the current position in the array

PImage helpImg;

public void setup() {
  // set up the stage size:
  size(512,512,OPENGL);

  // going to rework the navigation using the Camera object
  // camera1 = new Camera(this, sceneCenter[0],-sceneCenter[1],sceneCenter[2],1,1000);
  camera1 = new Camera(this,200);
  camera2 = new Camera(this,200);

  // make the history span the entire window.
  //historySize = int(width * (height/verticalStep));
  history = new float[historySize]; 
  historyColor = new int[historySize];

  //start Minim:
  Minim.start(this);

  //use a live input:
  in = Minim.getLineIn(Minim.MONO, bins, 44100);

  // create an FFT object that has a time-domain buffer the same size as jingle's sample buffer
  // note that this needs to be a power of two 
  // and that it means the size of the spectrum will be 512. 
  fftLog = new FFT(in.bufferSize(), in.sampleRate());
  // calculate averages based on a miminum octave width of 22 Hz
  // split each octave into three bands
  // this should result in 30 averages
  fftLog.logAverages(22, 3);


  noStroke();
  smooth();
  // noCursor();

  background(0);
  colorMode(RGB,255,255,255);

  helpImg = loadImage("help.png");
}


public void draw() {
  // perform a forward FFT on the samples in jingle's mix buffer
  // note that if jingle were a MONO file, this would be the same as using jingle.left or jingle.right
  fftLog.forward(in.left);


  // going for simple at first, just redraw the background partially transparent
  fill(0,50);
  noStroke();

  if(drawBackground){
    background(20); // no ghosting
    // rect(0,0,width,height);
  }

  // lights();
  smooth();
  camera1.feed();

  // Minim:
  float liveVolume = in.left.level();
  //println("line in level: " + liveVolume);                    // for debugging

  // Adjust the brush size, but map the values to a sane range
  float brushDiameter = map(liveVolume,0,maxInputLevel,0,50);

  // store the current value
  history[historyIndex] = brushDiameter;

  // Opacity/color using loudest frequency.
  int currentColor = (int)map(getLoudestFrequency(range[0],range[1]),range[0],range[1],0,255);
  historyColor[historyIndex] = color(currentColor,1,1,150);

  // only draw if the flag is set true:
  if(drawing) {

    // reset the starting location since
    // we're now drawing all the samples every frame:
    drawX = 0;
    drawY = 20; // some initial padding

    // adjust the length of the samples
    int hLength = history.length;
    verticalStep = getMaxHistoryValue(history.length);

    if(autoRotate && !mousePressed) camera1.circle(PI/1024);

    //loop through the history:
    for(int j = 0;j<hLength;j++){
      int index = (historyIndex+j)%history.length;

      // draw the mark
      float nextX = drawX;
      float nextY = drawY;

      /*
      // redo the drawing to get away fromm the grid...maybe a sphere or circle?
       if(drawX > width){
       nextY = drawY + verticalStep;
       drawY = nextY;
       drawX = 0;
       }
       */

      /*
       if(drawY > height){
       drawY = 0; 
       nextY = 0;
       // don't wrap around again, just exit.
       break;
       }
       */

      // derive the color characteristics
      colorMode(HSB,255,1,1); 
      if(!drawColor){
        fill(204,hue(historyColor[index]));
        strokeWeight(history[index]);
        stroke(204,hue(historyColor[index]));
      } 
      else {
        strokeWeight(history[index]);
        stroke(historyColor[index]);
        fill(historyColor[index]); 
      }

      float increment = horizontalStep;
      if(longLines) {
        // increment = (255 - opacity)/10; // this is inversed, low pitches are longer lines
        increment = hue(historyColor[index]) / PApplet.parseFloat(5);
      }

      nextX = drawX + increment;

      // here's fun with 3D
      pushMatrix();

      // center the drawing a bit
      // translate(-width/2,-height/2);

      //grid
      //translate(drawX+increment,drawY,history[index]*0.5);

      // Spiral:
      // need to incluse a decay for the radius
      float decay = map(j,0,hLength,.08f,1.0f);
      float r = 100.0f * decay;
      float a = map(j,0,hLength,0,10*TWO_PI);

      translate(sin(a)*r,cos(a)*r,history[index]*.75f);

      // elipse
      /*
       decay = 1;
       float r = 100.0;
       float a = map(j,0,hLength,0,TWO_PI-radians(10));
       translate(sin(a)*r*decay,cos(a)*r*decay,history[index]*0.5);
       */

      // keep each item orientated along the path:
      rotateZ(-a);



      noStroke();

      if(longLines) {
        box(increment);
      } 
      else {
        box(horizontalStep,history[index],history[index]);
      }

      popMatrix();

      drawX = nextX;
      drawY = nextY;
    }
  }
  historyIndex = (historyIndex+(history.length-1))%history.length;


  // draw any overlay stuff
  if(displayHelp) {
    pushMatrix();
    translate(-width/2,-height/2,0);
    drawBackground = true;
    drawSpectrum();

    // display the help image
    image(helpImg,10,10);
    popMatrix();
  } 
  else {
    //noCursor(); 
  }


}

public int getLoudestFrequency(int minBin, int maxBin) {
  // this will loop through the FFT spectrum and will return the index for the bin with the loudest values
  // could this be used to also get the amplitude?

  float loudestValue = 0.0f;
  int loudestIndex = 0;

  int low = constrain(minBin, 0,fftLog.avgSize());
  low = constrain(low, 0, maxBin);
  int high = constrain(maxBin, low + 1, fftLog.avgSize());

  for ( int i = low; i < high; i++){ 
    float currentValue = fftLog.getAvg(i);

    // compare the values
    if(currentValue > loudestValue){
      loudestValue = currentValue;
      loudestIndex = i; 
    }
  }

  if(displayFrequencyIndex){
    println("loudestIndex: " + loudestIndex);
  }
  return loudestIndex;
}


public void drawSpectrum() {
  // draw the spectrum across the bottom of the screen 
  // colorMode(RGB,255);
  strokeWeight(1);
  stroke(120,1,1,150); // green line, HSV

  fill(120,1,1,150);

  float xStep = width/PApplet.parseFloat(fftLog.avgSize());
  int maxVal = 1024;

  for ( int i = 1; i < fftLog.avgSize(); i++){ 
    float currentVal = fftLog.getAvg(i);
    float prevVal =  fftLog.getAvg(i-1);
    float xPos1 = map(i,0,fftLog.avgSize(),0,width);
    float xPos0 = xPos1-xStep;
    float yPos1 = height - 10 - map(currentVal,0,maxVal,0,height-10);
    float yPos0 = height - 10 - map(prevVal,0,maxVal,0,height-10);

    line(xPos0,yPos0,xPos1,yPos1);
  }

  if(mousePressed){
    int clickedBin = (int) map(mouseX,0,width,0,fftLog.avgSize());
    //println("Selected bin: " + clickedBin); 
    stroke(255,150);
    fill(255,15);
    rectMode(CORNERS);
    rect(map(spectrumClickRange[0],0,fftLog.avgSize(),0,width),-1,mouseX,height+1);
  }
}


public void stop(){   
  in.close();   
  super.stop();   
} 


// TODO: Create a class for the historical spectrum data.
class SpectrumHistory {

  // Constructor
  SpectrumHistory(int _size) {
  }

  // return the size of the spectrum history
  public int length() {
    return 0;
  }

  // return the maximum value of all samples in the data
  public float getMaxValue() {
    return 0.0f;
  }

  // return the value at a specific location
  public float getValue(int time) {
    return 0.0f;
  }
}

public float getMaxHistoryValue(int index) {
  float loudestValue = 0.0f;

  for ( int i = 0; i < index; i++){ 
    float currentValue = history[i];

    // compare the values
    if(currentValue > loudestValue){
      loudestValue = currentValue;
    }
  }

  return loudestValue;
}

boolean autoRotate = false;

public void cameraControlPress() {
  // this was used previously, before using the Camera object
}

public void cameraControlDrag() {
  if(keyPressed && key == CODED){
    switch(keyCode) {
    case SHIFT:

      camera1.look(radians(mouseX - pmouseX) / 2.0f, radians(mouseY - pmouseY) / 2.0f);

      break;
    case ALT:
      camera1.dolly(-(mouseY - pmouseY));
      break;
    case 157: //(Apple/Command key)

      camera1.tumble(radians(mouseX - pmouseX), radians(mouseY - pmouseY));
      break;
    } 

    // println("keyCode: "+keyCode);
  }
  else {
    camera1.track(-(mouseX - pmouseX), -(mouseY - pmouseY));
  }
}

public void keyPressed() {
  switch(key){
  case ' ':
    // toggle the drawing flag - to lift the pen from the surface, virtually
    drawing = !drawing;
    break;
  case 'b':
  case 'B':
    // toggle the background flag
    drawBackground = !drawBackground;
    break;
  case 'f':
  case 'F':
    // toggle the frequency index display
    displayFrequencyIndex = !displayFrequencyIndex;
    break;
  case 'm':
  case 'M':
    // toggle mouse control
    mouseControl = !mouseControl;
    break;
  case 'l':
  case 'L':
    // toggle long lines
    longLines = !longLines;
    break;
  case 'h':
  case 'H':
    // toggle the help screen
    //displayHelp = !displayHelp;
    break;
  case 'c':
  case 'C':
    // toggle color drawing
    drawColor = !drawColor;
    break;
  case 'a':
  case 'A':
    // toggle autorotate
    autoRotate = !autoRotate;
    break;
    case 'r':
    case 'R':
    //recenter camera
    camera1.jump(width/2,height/2,-50);
      camera1.aim(0, 0, 0);
    
  } 
}

public void mousePressed(){
  if(displayHelp){
    // if in the help mode, store a new value for the spectrum range
    int clickedBin = (int) map(mouseX,0,width,0,fftLog.avgSize());
    spectrumClickRange[0] = clickedBin;
  } 
  else {
    cameraControlPress(); 
  }


  println("history index: " + historyIndex);
}

public void mouseReleased() {
  if(displayHelp){
    int clickedBin = (int) map(mouseX,0,width,0,fftLog.avgSize());
    spectrumClickRange[1] = clickedBin;

    // reset the range variable
    // make sure to keep the min-max correct
    int minVal = (int)min(spectrumClickRange);
    int maxVal = (int)max(spectrumClickRange);

    // also within bounds of the spectrum array
    minVal = max(0,minVal);
    maxVal = min(maxVal,bins);

    range[0] = minVal;
    range[1] = maxVal;

    println("spectrum range (bins): " + range[0] + " - " + range[1]);
  } 
  else {
    //cameraControlRelease();
  }
}

public void mouseDragged() {
  if(!displayHelp){
    cameraControlDrag();
  }
}

  static public void main(String args[]) {     PApplet.main(new String[] { "sound_visualization_3d_circle" });  }}