#include "ofApp.h"

//--------------------------------------------------------------
//----------------------  Params -------------------------------
//--------------------------------------------------------------
Params param;        //Definition of global variable

void Params::setup() {
    eCenter = ofPoint( ofGetWidth() / 2, ofGetHeight() / 2 );
    eRad = 20;
    velRad = 100;
    lifeTime = 2.0;
    
    rotate = 100;
    force = 300;
    spinning = 1000;
    friction = 0.05;
}

//--------------------------------------------------------------
//----------------------  Particle  ----------------------------
//--------------------------------------------------------------
Particle::Particle() {
    live = false;
}

//--------------------------------------------------------------
ofPoint randomPointInCircle( float maxRad ){
    ofPoint pnt;
    float rad = ofRandom( 0, maxRad );
    float angle = ofRandom( 0, M_TWO_PI );
    pnt.x = cos( angle ) * rad;
    pnt.y = sin( angle ) * rad;
    return pnt;
}

//--------------------------------------------------------------
void Particle::setup() {
    pos = param.eCenter + randomPointInCircle( param.eRad );
    vel = randomPointInCircle( param.velRad );
    time = 0;
    lifeTime = param.lifeTime;
    live = true;
}

//--------------------------------------------------------------
void Particle::update( float dt ){
    if ( live ) {
        //Rotate vel
        vel.rotate( 0, 0, param.rotate * dt );
        
        ofPoint acc;         //Acceleration
        ofPoint delta = pos - param.eCenter;
        float len = delta.length();
        if ( ofInRange( len, 0, param.eRad ) ) {
            delta.normalize();
            
            //Attraction/repulsion force
            acc += delta * param.force;
            
            //Spinning force
            acc.x += -delta.y * param.spinning;
            acc.y += delta.x * param.spinning;
        }
        vel += acc * dt;            //Euler method
        vel *= ( 1 - param.friction );  //Friction
        
        //Update pos
        pos += vel * dt;    //Euler method
        
        //Update time and check if particle should die
        time += dt;
        if ( time >= lifeTime ) {
            live = false;   //Particle is now considered as died
        }
    }
}

//--------------------------------------------------------------
void Particle::draw(float scaledVol){
    if ( live ) {
        //Compute size
        float size = ofMap( 
                           fabs(time - lifeTime/2), 0, lifeTime/2, 3, 1 );
        
        //Compute color
        ofColor color = ofColor::red;
        float hue = ofMap( time, 0, lifeTime, 255, 0 );
        color.setHue( hue );
        ofSetColor( color );
        
        ofCircle( pos, size );  //Draw particle
    }
}


//--------------------------------------------------------------
//----------------------   App   -------------------------------
//--------------------------------------------------------------
void ofApp::setup(){
    ofSetFullscreen(true);
    ofSetVerticalSync(true);
    ofSetCircleResolution(80);
    
    AA.setup();
    prevPitch = 0;
    
    // 0 output channels,
    // 2 input channels
    // 44100 samples per second
    // 256 samples per buffer
    // 4 num buffers (latency)
    
    soundStream.listDevices();
    
    //if you want to set a different device id
    //soundStream.setDeviceID(0); //bear in mind the device id corresponds to all audio devices, including  input-only and output-only devices.
    
    multiplyer = ofGetWidth() / 256.0;
    
    
    left = new float[BUFFER_SIZE];
    right = new float[BUFFER_SIZE];
    volHistory.assign(400, 0.0);
    
    bufferCounter	= 0;
    drawCounter		= 0;
    smoothedVol     = 0.0;
    scaledVol		= 0.0;
    
    soundStream.setup(this, 0, 2, 44100, BUFFER_SIZE, 4);
    
    for (int i = 0; i < NUM_WINDOWS; i++) {
        for (int j = 0; j < BUFFER_SIZE/2; j++) {
            freq[i][j] = 0;
        }
    }
    
    //PARTICLES
    //Allocate drawing buffer
    int w = ofGetWidth();
    int h = ofGetHeight();
    fbo.allocate( w, h, GL_RGB32F_ARB );
    
    //Fill buffer with white color
    fbo.begin();
        ofBackground(0, 0, 0);
    fbo.end();
    
    //Set up parameters
    param.setup();		//Global parameters
    history = 0.9;
    bornRate = 3000;
    
    bornCount = 0;
    time0 = ofGetElapsedTimef();
    
    
    ofBackground(0, 0, 0);
    MAX_RAD = (ofGetHeight() / 2) - 10;
    MIN_RAD = 20;
    toContract = 150;
    lastNoiseFrame = 0;
    
    //spectrogram
    fftSize = 512;
    fftHopsize = fftSize/2;
    audiofft = new accFFT(fftSize, 1);//type 1 is fft with double precision
    fftInput = new double[fftSize];
    fftOutput = new fft_complex_double[fftSize];//float[bufferSize][2];
    fftWindow = new double[fftSize];
    set_window_hanning(fftWindow, fftSize);
    
}

//--------------------------------------------------------------
void ofApp::update(){
    //lets scale the vol up to a 0-1 range
    scaledVol = ofMap(smoothedVol, 0.0, 0.17, 0.0, 1.0, true);
    
    //lets record the volume into an array
    volHistory.push_back( scaledVol );
    
    //if we are bigger the the size we want to record - lets drop the oldest value
    if( volHistory.size() >= 400 ){
        volHistory.erase(volHistory.begin(), volHistory.begin()+1);
    }
    
    //PARTICLES
    //Compute dt
    float time = ofGetElapsedTimef();
    float dt = ofClamp( time - time0, 0, 0.1 );
    time0 = time;
    
    //Delete inactive particles
    int i=0;
    while (i < p.size()) {
        if ( !p[i].live ) {
            p.erase( p.begin() + i );
        }
        else {
            i++;
        }
    }
    
    //Born new particles
    bornCount += dt * bornRate;      //Update bornCount value
    if ( bornCount >= 1 ) {          //It's time to born particle(s)
        int bornN = int( bornCount );//How many born
        bornCount -= bornN;          //Correct bornCount value
        for (int i=0; i<bornN; i++) {
            Particle newP;
            newP.setup();            //Start a new particle
            p.push_back( newP );     //Add this particle to array
        }
    }
    
    //Update the particles
    for (int i=0; i<p.size(); i++) {
        p[i].update( dt );
    }
    
}

//--------------------------------------------------------------
void ofApp::draw(){
    if (scaledVol > 0.15) {
        lastNoiseFrame = drawCounter;
        if (param.eRad < MAX_RAD) {
            param.eRad += 2;
        } else {
            param.velRad = 4000;
        }
    } else if ((drawCounter - lastNoiseFrame >= toContract) && param.eRad > MIN_RAD && param.eRad < MAX_RAD) {
        param.eRad--;
    }
    
    //Change rotation based on pitch
    float currPitch = AA.pitch;
    float dPitch = currPitch - prevPitch;
    prevPitch = currPitch;
    float newRot = (currPitch / 5.0f);
    if (dPitch != 0.0f) newRot = newRot  * (dPitch / -dPitch);
    if (newRot < -1000.0f) {
        newRot = -1000.0f;
    } else if (newRot > 1000.0f) {
        newRot = 1000.0f;
    }
    param.rotate = newRot;
        
    //PARTICLES
    //1. Drawing to buffer
    fbo.begin();
    
    //Draw semi-transparent white rectangle
    //to slightly clearing a buffer (depends on history value)
    
    ofEnableAlphaBlending();         //Enable transparency
    
    float alpha = (1-history) * 255;
    ofSetColor( 0, 0, 0, alpha );
    ofFill();
    ofRect( 0, 0, ofGetWidth(), ofGetHeight() );
    ofDisableAlphaBlending();        //Disable transparency
    
    //Draw the particles
    ofFill();
    for (int i=0; i<p.size(); i++) {
        p[i].draw(smoothedVol);
    }
    
    fbo.end();
    
    //2. Draw buffer on the screen
    ofSetColor( 255, 255, 255 );
    fbo.draw( 0, 0 );
    
    
    ofSetColor(225);
    ofNoFill();
    
    // draw the left channel:
    ofPushStyle();
        ofPushMatrix();
        ofTranslate(0, 100, 0);
        
        ofSetColor(225);
    
        ofSetLineWidth(1);
    
        ofSetColor(56, 235, 128);
        ofSetLineWidth(3);
        
        ofBeginShape(); //Draws the audio line itself
        for (unsigned int i = 0; i < BUFFER_SIZE; i++){
            ofVertex(i*multiplyer, left[i]*200.0f);
        }
        ofEndShape(false);
        
        ofPopMatrix();
    ofPopStyle();
    
    //FFT
    drawSpectrogram();
    
    drawCounter++;
    
}

//--------------------------------------------------------------
void ofApp::audioIn(float * input, int bufferSize, int nChannels){
    
    float curVol = 0.0;
    
    // samples are "interleaved"
    int numCounted = 0;
    
    //lets go through each sample and calculate the root mean square which is a rough way to calculate volume
    for (int i = 0; i < bufferSize; i++){
        left[i]		= input[i*2]*0.5;
        right[i]	= input[i*2+1]*0.5;
        
        addToFFT(left[i]);
        
        curVol += left[i] * left[i];
        curVol += right[i] * right[i];
        numCounted+=2;
    }
    
    //this is how we get the mean of rms :)
    curVol /= (float)numCounted;
    
    // this is how we get the root of rms :)
    curVol = sqrt( curVol );
    
    smoothedVol *= 0.93;
    smoothedVol += 0.07 * curVol;
    
    AA.processAudio(left, bufferSize);
    
    bufferCounter++;
    
}

//--------------------------------------------------------------
void ofApp::keyPressed  (int key){
    if( key == 's' ){
        soundStream.start();
    }
    
    if( key == 'e' ){
        soundStream.stop();
    }
    
    if (key == 'r') { //reset
        param.eRad = 20;
        param.velRad = 100;
        param.rotate = 100;
        param.force = 300;
        param.spinning = 1000;
        prevPitch = 0;
    }
}

//--------------------------------------------------------------
void ofApp :: set_window_hanning(double* windowBuffer, const int framesize){
    double N;		// variable to store framesize minus 1
    
    N = (double) (framesize-1);	// framesize minus 1
    
    // Hanning window calculation
    for (int n = 0;n < framesize;n++)
    {
        windowBuffer[n] = 0.5*(1-cos(2*pi*(n/N)));
    }
}

//--------------------------------------------------------------
void ofApp::drawSpectrogram(){
    int spectrogramWidth = 400;
    
    int minIndex = max(0, (int)(magnitudeSpectrum.size() - spectrogramWidth));
    int maxIndex = minIndex+spectrogramWidth;
    int screenHeight = ofGetHeight();
    
    float spectrogramHeight = 20.0;//bufferSize;
    float binWidth = (float)ofGetWidth()/(float)spectrogramWidth;
    float binHeight = (200.0f)/spectrogramHeight;
    
    for (int i = minIndex; i < min((int)magnitudeSpectrum.size(), maxIndex ); i++){
        for (int y = 0; y < spectrogramHeight; y++){
            ofSetColor(magnitudeSpectrum[i][y]*50.0, 0, 0);
            ofRect((i-minIndex)*binWidth, screenHeight - (y+1)*binHeight, binWidth, binHeight);
        }
    }
}

//--------------------------------------------------------------
void ofApp::addToFFT(float sample){
    //window
    fftSampleBuffer.push_back(sample);
    
    if (fftSampleBuffer.size() == fftSize){
        setFFTinput();
        //do FFT using Accelerate
        audiofft->forward_FFT_d(fftInput, fftOutput);//Result);//&fftRealResult[0], &fftImagResult[0]);
        
        DoubleVector v;
        for (int i = 0; i < BUFFER_SIZE; i++){
            v.push_back(sqrt(fftOutput[i][0]*fftOutput[i][0] + fftOutput[i][1]*fftOutput[i][1]));
        }
        magnitudeSpectrum.push_back(v);
        
        //now remove many samples
        for (int removeIndex = 0; removeIndex < fftHopsize; removeIndex++){
            fftSampleBuffer.erase(fftSampleBuffer.begin());//just need first to go each time
        }
    }
}

//--------------------------------------------------------------
void ofApp::setFFTinput(){
    for (int i = 0; i < fftSize; i++){
        fftInput[i] = fftSampleBuffer[i] * fftWindow[i];
    }
}

//--------------------------------------------------------------
void ofApp::keyReleased(int key){
    
}

//--------------------------------------------------------------
void ofApp::mouseMoved(int x, int y ){
    
}

//--------------------------------------------------------------
void ofApp::mouseDragged(int x, int y, int button){
    
}

//--------------------------------------------------------------
void ofApp::mousePressed(int x, int y, int button){
    
}

//--------------------------------------------------------------
void ofApp::mouseReleased(int x, int y, int button){
    
}

//--------------------------------------------------------------
void ofApp::windowResized(int w, int h){
    multiplyer = w / 256.0;
    //Re-orient the center of the particles/portal
    param.eCenter = ofPoint( ofGetWidth() / 2, ofGetHeight() / 2 );
}

//--------------------------------------------------------------
void ofApp::gotMessage(ofMessage msg){
    
}

//--------------------------------------------------------------
void ofApp::dragEvent(ofDragInfo dragInfo){ 
    
}

