import processing.core.*; 
import processing.xml.*; 

import java.io.File; 
import java.awt.FileDialog; 

import java.applet.*; 
import java.awt.Dimension; 
import java.awt.Frame; 
import java.awt.event.MouseEvent; 
import java.awt.event.KeyEvent; 
import java.awt.event.FocusEvent; 
import java.awt.Image; 
import java.io.*; 
import java.net.*; 
import java.text.*; 
import java.util.*; 
import java.util.zip.*; 
import java.util.regex.*; 

public class p5SoundCombiner extends PApplet {


public void setup() {
  WavFile wavFileToTest1 = WavFile.readFromFilePath(getDialogFileUrl("file A"));
  WavFile wavFileToTest2 = WavFile.readFromFilePath(getDialogFileUrl("file B"));
  String folder = getDialogFileUrl("save to folder");
  double[][] waveForm1 = wavFileToTest1.samplesAsDouble();
  double[][] waveForm2 = wavFileToTest2.samplesAsDouble();
  double[][] convolved = convolveFFT(waveForm1, waveForm2, 8);
  convolved = normalizeArray(convolved);
  wavFileToTest1.setSamplesFromDouble(convolved);
  wavFileToTest1.writeToFilePath(folder+"convolved.wav");
  double[][] ringMod = ringMod(waveForm1, waveForm2);
  ringMod = normalizeArray(ringMod);
  wavFileToTest1.setSamplesFromDouble(ringMod);  
  wavFileToTest1.writeToFilePath(folder+"ringMod.wav");  
  double[][] ampMod = ampMod(waveForm1, waveForm2);
  ampMod = normalizeArray(ampMod);
  wavFileToTest1.setSamplesFromDouble(ampMod);  
  wavFileToTest1.writeToFilePath(folder+"ampMod.wav");
}

public void draw() {
}


public double[][][][] performDFT(double[][] input, int windowPower) {

  int nbChannels = input.length;
  int wLength = (int)pow(2, windowPower);

  double[][][][] result = new double[2][nbChannels][][];// value type, channel, partial, part

  for (int c=0 ; c<nbChannels ; c++) {

    int sLength = input[c].length;

    int nbParts = ceil(sLength/wLength);
    int nbHarm = wLength;// for audible part only, put int nbHarm=ceil(wLength/2);

    float[][] sinPart = new float[nbHarm][nbParts];
    float[][] cosPart = new float[nbHarm][nbParts];  
    for (int partial=0;partial<nbHarm;partial++) {
      for (int part=0;part<nbParts;part++) {
        sinPart[partial][part]=0;
        cosPart[partial][part]=0;
        for (int i=0;i<wLength;i++) {
          float phasor=(float)(i+part*wLength)*partial*TWO_PI/wLength;
          sinPart[partial][part]+=input[c][(i+part*wLength)%sLength]*sin(phasor);
          cosPart[partial][part]+=input[c][(i+part*wLength)%sLength]*cos(phasor);
        }
      }
    }

    //getting frequency magnitude and phase
    result[0][c] = new double[nbHarm][nbParts]; // magnitude
    result[1][c] = new double[nbHarm][nbParts]; // phase
    for (int partial=0;partial<nbHarm;partial++) {
      for (int part=0;part<nbParts;part++) {
        // frequency is (float)partial*sampleRate/(float)wLength
        result[0][c][partial][part] = 2.0f*sqrt(sq(sinPart[partial][part])+sq(cosPart[partial][part]))/(float)wLength;
        // value in db is 20.0*log10(magn[partial][part])
        result[1][c][partial][part] = atan2(cosPart[partial][part], sinPart[partial][part]);
      }
    }
  }

  return result;
}

public double[][] resynthDFT(double[][][][] input, int windowPower) {
  int wLength = (int)pow(2, windowPower);
  int nbChannels = input[0].length;
  double[][] sample= new double[nbChannels][];
  for (int c=0 ; c<nbChannels ; c++) {
    double[][] magn = input[0][c];
    double[][] phas = input[1][c];  
    int sLength = wLength*magn[0].length;
    int nbHarm = magn.length;
    sample[c] = new double[sLength];
    for (int i=0;i<sLength;i++) {
      sample[c][i] = 0;
    }
    for (int i=0;i<sLength;i++) {
      int cPart = floor((float)i/(wLength));//which part of the sample are we in
      int cPartS = i%(wLength);//which sample on this part
      double thisSample=0;
      for (int partial=0;partial<nbHarm;partial++) {
        double phasor = (double)cPartS*TWO_PI*partial/wLength+phas[partial][cPart];
        thisSample += Math.sin(phasor)*magn[partial][cPart];
      }
      sample[c][i]=thisSample;
    }
  }
  return sample;
}





public float vrMax(float a, float b, float m) {
  float d1=b-a;
  if (d1>m/2) {
    d1=d1-m;
  }
  if (d1<-m/2) {
    d1=d1+m;
  }
  return d1;
}

public String getDialogFileUrl(String label) {
  Frame frame = new Frame();
  FileDialog fileDialog = new FileDialog(frame, label, FileDialog.LOAD);
  fileDialog.setVisible(true);
  if (fileDialog.getFile()!=null) {
    String filePath = fileDialog.getDirectory() + fileDialog.getFile();
    return filePath;
  }
  return null;
}

public String getDialogFolderUrl(String label) {
  Frame frame = new Frame();
  FileDialog fileDialog = new FileDialog(frame, label, FileDialog.LOAD);
  fileDialog.setVisible(true);
  return fileDialog.getDirectory();
}

public String[] getDialogAllFilesUrl(String label) {
  Frame frame = new Frame();
  FileDialog fileDialog = new FileDialog(frame, label, FileDialog.LOAD);
  fileDialog.setVisible(true);
  return getAllFilesFrom(fileDialog.getDirectory());
}

public String[] getAllFilesFrom(String folderUrl) {
  File folder = new File(folderUrl);
  File[] filesPath = folder.listFiles();
  String[] result = new String[filesPath.length];
  for (int i=0;i<filesPath.length;i++) {
    result[i]=filesPath[i].toString();
  }
  return result;
}

public void renameFile(String urlA, String urlB) {
  File file = new File(sketchPath(urlA));
  File file2 = new File(sketchPath(urlB));  
  boolean success = file.renameTo(file2);
  if (!success) {
    println(urlA + " was not renamed to " + urlB);
  }
}

public void copyDirectory(String urlA, String urlB) throws IOException {
  File srcDir = new File(sketchPath(urlA));
  File dstDir = new File(sketchPath(urlB));  
  try {
    copyDirectoryRec(srcDir, dstDir);
  } 
  catch (IOException e) {
    e.printStackTrace();
  }
}

public void copyDirectoryRec(File srcDir, File dstDir) throws IOException {
  // Copies all files under srcDir to dstDir.
  // If dstDir does not exist, it will be created.
  if (srcDir.isDirectory()) {
    if (!dstDir.exists()) {
      dstDir.mkdir();
    }
    String[] children = srcDir.list();
    for (int i=0; i<children.length; i++) {
      copyDirectoryRec(new File(srcDir, children[i]), new File(dstDir, children[i]));
    }
  } 
  else {
    // This method is implemented in Copying a File
    copyFile(srcDir, dstDir);
  }
}

public void copyFile(File src, File dst) throws IOException {
  // Copies src file to dst file.
  // If the dst file does not exist, it is created
  InputStream in = new FileInputStream(src);
  OutputStream out = new FileOutputStream(dst);

  // Transfer bytes from in to out
  byte[] buf = new byte[1024];
  int len;
  while ( (len = in.read (buf)) > 0) {
    out.write(buf, 0, len);
  }
  in.close();
  out.close();
}


public double[][] normalizeArray(double[][] input) {
  double[][] result = new double[input.length][];
  for (int i=0;i<input.length;i++) {
    result[i] = new double[input[i].length];
  }
  double higherValue = 0;
  for (int i=0;i<input.length;i++) {
    for (int j=0;j<input[i].length;j++) {
      if (abs(input[i][j]) > higherValue) higherValue = abs(input[i][j]);
    }
  }
  for (int i=0;i<input.length;i++) {
    for (int j=0;j<input[i].length;j++) {
      result[i][j] = input[i][j] / higherValue;
    }
  }
  return result;
}

public double[][] invertArray(double[][] input) {
  double[][] inverted = new double[input.length][];
  for (int i=0;i<input.length;i++) {
    inverted[i] = new double[input[i].length];
    for (int j=0;j<input[i].length;j++) {
      inverted[i][j] = input[i][input[i].length-j-1];
    }
  }
  return inverted;
}

public double abs(double value) {
  if (value<0) value *= -1;
  return value;
}

public double[][] simpleIRFilter(double[][] input, float feedback) {
  feedback = sqrt(feedback);
  double[][] output = new double[input.length][];
  for (int i=0;i<input.length;i++) {
    output[i] = new double[input[i].length];
    for (int j=0;j<input[i].length;j++) {
      output[i][j] = input[i][j] * (1-feedback);
      output[i][j] += output[i][max(j-1, 0)] * feedback;
    }
  }
  return output;
}

public double[][] addArrays(double[][] inputA, double[][] inputB) {
  double[][] result = new double[max(inputA.length, inputB.length)][];
  for (int c=0 ; c<result.length ; c++) {
    result[c] = new double[max(inputA[c].length, inputB[c].length)];
    for (int i=0 ; i<result[c].length ; i++) {
      result[c][i] = inputA[c][i%inputA[c].length] + inputB[c][i%inputB[c].length];
    }
  }
  return result;
}

public double[][] invertPhase(double[][] inputA) {
  double[][] result = new double[inputA.length][];
  for (int c=0 ; c<result.length ; c++) {
    result[c] = new double[inputA[c].length];
    for (int i=0 ; i<result[c].length ; i++) {
      result[c][i] = inputA[c][i] * -1;
    }
  }
  return result;
}

public double[][] ringMod(double[][] inputA, double[][] inputB) {
  double[][] result = new double[max(inputA.length, inputB.length)][];
  for (int c=0 ; c<result.length ; c++) {
    result[c] = new double[max(inputA[c].length, inputB[c].length)];
    for (int i=0 ; i<result[c].length ; i++) {
      result[c][i] = inputA[c][i%inputA[c].length] * inputB[c][i%inputB[c].length];
    }
  }
  return result;
}

public double[][] ampMod(double[][] inputA, double[][] inputB) {
  double[][] result = new double[max(inputA.length, inputB.length)][];
  for (int c=0 ; c<result.length ; c++) {
    result[c] = new double[max(inputA[c].length, inputB[c].length)];
    for (int i=0 ; i<result[c].length ; i++) {
      result[c][i] = inputA[c][i%inputA[c].length] * abs(inputB[c][i%inputB[c].length]);
    }
  }
  return result;
}

public double[][] arrayOfSize(double[][] input) {
  double[][] output = new double[input.length][];
  for (int i=0;i<input.length;i++) {
    output[i] = new double[input[i].length];
  }
  return output;
}

public Complex[][][] performFFT(double[][] input, int windowPower) {
  int nbChannels = input.length;
  int wLength = (int)pow(2, windowPower);
  Complex[][][] result = new Complex[nbChannels][][];// [channel][part][partial]
  for (int c=0 ; c<nbChannels ; c++) {
    int sLength = input[c].length;
    int nbParts = ceil(sLength/wLength);
    result[c] = new Complex[nbParts][];
    for (int i=0;i<nbParts;i++) {
      Complex[] thisChunk = new Complex[wLength];
      for (int j = 0 ; j < wLength ; j++) {
        thisChunk[j] = new Complex(input[c][(j+i*wLength)%sLength], 0);
      }
      Complex[] thisFFT = FFT.fft(thisChunk);
      result[c][i] = new Complex[thisFFT.length];
      for (int j=0;j<thisFFT.length;j++) result[c][i][j] = thisFFT[j];
    }
  }
  return result;
}

public double[][] resynthFFT(Complex[][][] input, int windowPower) {
  int wLength = (int)pow(2, windowPower);
  int nbChannels = input.length;
  double[][] sample= new double[nbChannels][];
  for (int c=0 ; c<nbChannels ; c++) {
    int nbParts = input[c].length;
    int sLength = wLength*nbParts;
    int nbHarm = input[c][0].length;
    sample[c] = new double[sLength];
    for (int i=0;i<sLength;i++) {
      sample[c][i] = 0;
    }
    for (int p=0;p<nbParts;p++) {
      Complex[] thisPart = FFT.ifft(input[c][p]);
      for (int i=0;i<thisPart.length;i++) {
        sample[c][p*wLength+i] = thisPart[i].re();
      }
    }
  }
  return sample;
}

public double[][][][] shiftDFT(double[][][][] input, int amount) {
  double[][][][] result = new double[input.length][][][];
  for (int v=0;v<input.length;v++) {
    result[v] = new double[input[v].length][][];
    for (int c=0;c<input[v].length;c++) {
      result[v][c] = new double[input[v][c].length][];
      for (int h=0;h<input[v][c].length;h++) {
        result[v][c][h] = new double[input[v][c][h].length];
        for (int p=0;p<input[v][c][h].length;p++) {
          result[v][c][h][p] = input[v][c][(h+amount)%input[v][c].length][p];
        }
      }
    }
  }
  return result;
}

public double[][] convolveFFT(double[][] inputA, double[][] inputB, int windowPower) {
  int nbChannels = max(inputA.length, inputB.length);
  int wLength = (int)pow(2, windowPower);
  double[][] result = new double[nbChannels][];// [channel][sample]
  for (int c=0 ; c<nbChannels ; c++) {
    int sLengthA = inputA[c%inputA.length].length;
    int sLengthB = inputB[c%inputB.length].length;
    int sLength = max(sLengthA, sLengthB);
    int nbParts = ceil(sLength/wLength);
    result[c] = new double[nbParts*wLength];
    for (int i=0;i<nbParts;i++) {
      Complex[] thisChunkA = new Complex[wLength];
      Complex[] thisChunkB = new Complex[wLength];      
      for (int j = 0 ; j < wLength ; j++) {
        thisChunkA[j] = new Complex(inputA[c][(j+i*wLength)%sLengthA], 0);
        thisChunkB[j] = new Complex(inputB[c][(j+i*wLength)%sLengthB], 0);
      }
      Complex[] thisFFT = FFT.convolve(thisChunkA, thisChunkB);
      for (int j=0 ; j < wLength ; j++) result[c][i*wLength+j] = thisFFT[j*2].re();
    }
  }
  return result;
}

  static public void main(String args[]) {
    PApplet.main(new String[] { "--bgcolor=#D4D0C8", "p5SoundCombiner" });
  }
}
