package kr.util.sample;

import java.io.File;

import kr.gui.AudioPlayController;
import kr.util.audio.AudioClip;
import kr.util.audio.MemAudioClip;
import kr.util.audio.MonoAudioClip;

public class ExtractVocals {
	public static void main(String [] argv)
	{
		SampleManager sm = new SampleManager();
		
		AudioClip ac = sm.getData(new File(argv[0]));
		
		MonoAudioClip lPlusR = new MonoAudioClip(false);
		MonoAudioClip lMinusR = new MonoAudioClip(true);
		
		lPlusR.setSource(ac);
		lMinusR.setSource(ac);
		
		System.out.println("converting data for l + r");
		double [][] fftLPlusR = convertData(lPlusR);
		System.out.println("performing fft for l + r");
		performFFT(fftLPlusR[0], fftLPlusR[1]);
		
		System.out.println("converting data for l - r");
		double [][] fftLMinusR = convertData(lMinusR);
		System.out.println("performing fft for l - r");
		performFFT(fftLMinusR[0], fftLMinusR[1]);
		
		System.out.println("extracting vocals");
		extractVocalFreqs(fftLPlusR, fftLMinusR);
		
		
		//makePhaseless(fftLPlusR);

		System.out.println("converting back");
		performFFT(fftLPlusR[0], fftLPlusR[1]);


		System.out.println("converting to bytes");
		byte [] data = convertToBytes(fftLPlusR[0], true);
		
		AudioPlayController apc;
		MemAudioClip mac = new MemAudioClip(1,48000, 8);
		mac.setInternalArray(data);
		
		apc = new AudioPlayController();
		apc.setAudioOut(lMinusR);
		System.out.println("playing subtracted vocals");
		apc.run();
		System.out.println("done playing");

		apc = new AudioPlayController();
		apc.setAudioOut(mac);
		System.out.println("playing changed");
		apc.run();
		System.out.println("done playing");

		apc = new AudioPlayController();
		apc.setAudioOut(lPlusR);
		System.out.println("playing original");
		apc.run();
		System.out.println("done playing");

		
System.exit(0);
		
		
	}

	private static void adjustPhase(double[][] dat, double radians) {
		/*for(int i = 0; i < dat[0].length; i++)
		{
			double length = Math.sqrt(dat[0][i] * dat[0][i] + dat[1][i] * dat[1][i]);
			double currRadians = Math.atan2(dat[1][i], dat[0][i]); //which is y and which is x?
			dat[0][i] = 
			dat[1][i] = 0;
		}*/
		System.out.println("TODO: adjust phase");
	}

	private static byte[] convertToBytes(double[] data, boolean backwards) {
		double max = 0;
		
		byte [] out = new byte[data.length];

		
		
		for(int i = 0; i < data.length; i++)
		{
			max = Math.max(Math.abs(data[i]),max);
			out[backwards ? (data.length - i - 1) : i] = (byte)Math.floor(data[i] / max * 127);
			max = max * .99999;//use forgettful normalization
		}
		
		return out;
	}

	private static void extractVocalFreqs(double[][] fftLPlusR, double[][] fftLMinusR) 
	{
		for(int i = 0; i < fftLPlusR[0].length; i++)
		{
			double length = Math.sqrt(fftLPlusR[0][i] * fftLPlusR[0][i] + fftLPlusR[1][i] * fftLPlusR[1][i]);
			double newLength = length - Math.sqrt(fftLMinusR[0][i] * fftLMinusR[0][i] + fftLMinusR[1][i] * fftLMinusR[1][i]);
			if(newLength < 0) newLength = 0;
			
			fftLPlusR[0][i] *= newLength*newLength/length/length; 
			fftLPlusR[1][i] *= newLength*newLength/length/length; 
		}
	}

	private static void performFFT(double[] re, double[]im) {
		int size = re.length;
	    double scale = Math.sqrt(1.0/(double)size);
	    int i,j;
	    double tempr,tempi;
	    for (i=j=0; i<size; ++i) {
	        if (j>=i) {
	            tempr = re[j]*scale;
	            tempi = im[j]*scale;
	            re[j] = re[i]*scale;
	            im[j] = im[i]*scale;
	            re[i] = tempr;
	            im[i] = tempi;
	        }
	        int m = size>>1;
	        while (m>=1 && j>=m) {
	            j -= m;
	            m>>=1;
	        }
	        j += m;
	    }

	    int imax = 1,istep = 2,m;
	    double w,wr,wi,tr,ti,delta;
	    while (imax<size) {
	    	System.out.println("imax is "+imax+" size is "+size);
	        delta = Math.PI/imax;
	        for (m=0; m<imax; ++m) {
	            w = m*delta;
	            wr = Math.cos(w);
	            wi = Math.sin(w);
	            for (i=m; i<size; i+=istep) {
	                j = i+imax;
	                tr = wr*re[j]-wi*im[j];
	                ti = wr*im[j]+wi*re[j];
	                re[j] = re[i] - tr;
	                im[j] = im[i] - ti;
	                re[i] += tr;
	                im[i] += ti;
	            }
	        }
	        imax<<=1;
	        istep<<=1;
	    }
	}
	
	private static final int HACK_MAX_LENGTH = 2097152;

	private static double[][] convertData(AudioClip ac) {
		byte [] data = new byte[4096];
		
		int length = Math.min(ac.length(), HACK_MAX_LENGTH);
		
		//fft length must be a power of 2
		int fftLength = 1 <<(int)Math.ceil(Math.log(length)/ Math.log(2));
		
		System.out.println("data length is "+length+", fft length is "+fftLength);
		
		double [][] fftData = new double[2][fftLength];
		
		for(int i = 0; i < length; i += data.length )
		{
			ac.copyTo(i, Math.min(data.length, length-i), data, 0);
			
			for(int j = 0; j < Math.min(data.length, length-i); j++)
			{
				fftData[0][i+j] = data[j]/128.;
			}
		}
		
		return fftData;
	}
	
}
