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;
import kr.util.audio.SingleChannelAudioClip;

public class ExtractVocals2 {
	public static void main(String [] argv) throws Exception
	{
		int windowSize = 4096;
		
		SampleManager sm = new SampleManager();
		
		MemAudioClip ac = new MemAudioClip(2, 48000, 16);
		
		SoundRetrievalManager.fillAudioClip(new File(argv[0]), ac);
		

		SingleChannelAudioClip left = new SingleChannelAudioClip(0);
		SingleChannelAudioClip right = new SingleChannelAudioClip(1);
		
		left.setSource(ac);
		right.setSource(ac);
		
		MemAudioClip mac = new MemAudioClip(1,48000, 16);
		
		double [][] lastFftCenter = null;
		
		for(int i = 0; i < left.sizeInFrames(); i += windowSize/4)
		{
		
			//System.out.println("converting data for l");
			double [][] fftLeft = convertData(left, i , windowSize);
			//System.out.println("performing fft for l");
			performFFT(fftLeft[0], fftLeft[1]);
			
			//System.out.println("converting data for r");
			double [][] fftRight = convertData(right, i ,windowSize);
			//System.out.println("performing fft for r");
			performFFT(fftRight[0], fftRight[1]);
			
			
			//System.out.println("extracting vocals");
			double [][] fftCenter = extractVocalFreqs(fftLeft, fftRight);
			
			//makePhaseless(fftLPlusR);
	
			//System.out.println("converting back");
			performFFT(fftCenter[0], fftCenter[1]);
			
			//reverse fft
			for(int k = 0; k < 2; k++)
			{
				for(int j = 0; j < fftCenter[k].length; j++)
				{	
					double t = fftCenter[k][j];
					fftCenter[k][j] = fftCenter[k][fftCenter[k].length-j-1];
					fftCenter[k][fftCenter[k].length-j-1] = t;
				}
			}
			
			if(lastFftCenter!= null) 
			{
				for(int j = 0; j < windowSize*3/4; j++)
				{
					if(Double.isInfinite(fftCenter[0][j]))
						System.out.println("infinity 3");
							
					double combined = lastFftCenter[0][j + windowSize/4] + fftCenter[0][j];
					lastFftCenter[0][j + windowSize/4] = fftCenter[0][j] = combined;
				}
				//System.out.println("converting to bytes");
				byte [] data = convertToBytes(lastFftCenter[0], false);
				mac.append(data,0,windowSize/4 * 2);
			}
	
			lastFftCenter = fftCenter;
			
		}
		
		byte [] data = ac.getInternalArray();
		
		for(int i = 0; i < mac.length(); i++)
		{
			data[i * 2] -= mac.byteAt(i);
			data[i * 2+1] -= mac.byteAt(i);
		}
		
		AudioPlayController apc;
		
		apc = new AudioPlayController();
		apc.setAudioOut(mac);
		System.out.println("playing vocals");
		apc.run();
		System.out.println("done playing");
		
		MonoAudioClip mono = new MonoAudioClip(true);
		mono.setSource(ac);
		apc = new AudioPlayController();
		apc.setAudioOut(mono);
		System.out.println("playing background");
		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 double max = 0;

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

		
		
		for(int i = 0; i < data.length; i++)
		{
			if(!Double.isInfinite(Math.abs(data[i]))) //HACK
				max = Math.max(Math.abs(data[i]),max);
			else
				max = max+1;
			
			int val = (int) Math.floor(data[i] / max * 32767);
			
			int index = backwards ? (data.length - i - 1) * 2 : i * 2;
			
			out[index] = (byte)(val & 0xff);
			out[index+1] = (byte)(val >> 8);
			max = max * .99999;//use forgettful normalization
		}
		
		//System.out.println("max is "+max);
		
		return out;
	}

	private static double [][] extractVocalFreqs(double[][] fftL, double[][] fftR) 
	{
		double [][] center = new double[2][fftL[0].length];
		for(int i = 0; i < fftL[0].length; i++)
		{
			double lR, lI, rR, rI; //left real, left imaginary, right real, right imaginary
			double alpha, dot, clR, clI, crR, crI, cR, cI;
			double ldotl, rdotr;
			double A, B, C, D;
			
			lR = fftL[0][i];
			lI = fftL[1][i];
			rR = fftR[0][i];
			rI = fftR[1][i];
			
			// (r1 + i1) ( r2 + i2) = r1 * r2 + i1 * r2 + r1 * i2 - i1 * i2
			double re = lR*rR - lI * rI;
			double im = lR*rI + lI * rR;
		
			

//			dot = lR*rR+lI*rI;
//			ldotl = lR*lR+lI*lI;
//			rdotr = rR*rR+rI*rI;
//			alpha = 1.0/(ldotl+0.0000001f);
//
//			clR = lR * alpha;
//			clI = lI * alpha;
//
//			alpha = 1./(rdotr+0.0000001f);
//
//			crR = rR * alpha;
//			crI = rI * alpha;
//
//			cR = clR + crR;
//			cI = clI + crI;
//
//			A = cR*cR + cI*cI;
//			B = -cR*(lR+rR)-cI*(lI+rI);
//			C = dot;
//			D = B*B-4*A*C;
//
//			if (D>=0.0F && A>0.00000001f) {
//				alpha = (-B-(float)Math.sqrt(D))/(2*A);
//
//				cR*=alpha;
//				cI*=alpha;
//			} else
//				cR = cI = 0.0f;
			
			center[0][i]	= re;
			center[1][i] = im;
		}
		
		return center;
	}

	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;
	    }
	}
	
	/**
	 * Window size must be a power of two
	 */
	private static double[][] convertData(AudioClip ac, int index, int windowSize) {
	
		int length = ac.sizeInFrames();
				
		double [][] fftData = new double[2][windowSize];
		double twopi_over_n = Math.PI * 2 / windowSize;
		double scalefac = 1.0 / windowSize;
		
		for(int j = 0; j < Math.min(windowSize, length-index); j++)
		{
			int val = ac.frameVal(index + j, 0);
			if(val < -32767)
				val = val - 0/1;
			fftData[0][j] = val/32767. * .5 * (1 - Math.cos(twopi_over_n * (j + .5)));
		}
		
		return fftData;
	}
	
}
