package ddf.minim.ugens;

import ddf.minim.UGen;
import ddf.minim.analysis.FFT;

/**
 * Vocoder is a UGen that performs very basic <a href="http://en.wikipedia.org/wiki/Vocoder">vocoding</a>.
 * It works by analyzing the audio input and the modulator input with FFTs and then multiplying 
 * the audio input's spectrum by the modulator's spectrum. 
 * 
 * @example Synthesis/vocoderExample
 * 
 * @author Damien Di Fede
 *
 */
public class Vocoder extends UGen
{
	/**
	 * The audio you want processed by the Vocoder.
	 * If you are going for the classic robot vocals sound,
	 * you would patch the synth to this input, typically
	 * something with a lot of high frequency content, like Waves.SAW.
	 * 
	 * @shortdesc The audio you want processed by the Vocoder.
	 * 
	 * @related Vocoder
	 */
	public UGenInput	audio;

	/**
	 * The signal that will be used to transform the audio input.
	 * If you are going for the classic robot vocals sound,
	 * you would patch the vocals to this input.
	 * 
	 * @shortdesc The signal that will be used to transform the audio input.
	 * 
	 * @related Vocoder
	 */
	public UGenInput	modulator;

	// the window size we use for analysis
	private int			m_windowSize;

	// how many samples should pass between the
	// beginning of each window
	private int			m_windowSpacing;
	// the sample data from audio
	private float[]		m_audioSamples;
	// the sample data from carrier
	private float[]		m_modulatorSamples;
	// our output
	private float[]		m_outputSamples;
	// where we are in our sampling arrays
	private int			m_index;
	// where we are in our output array
	private int			m_outputIndex;

	// sample counter for triggering the next window
	private int			m_triggerCount;
	// the float array we use for constructing our analysis window
	private float[]		m_analysisSamples;
	private float		m_outputScale;

	// used to analyze the audio input
	private FFT			m_audioFFT;
	// used to analyze the modulator input
	private FFT			m_modulatorFFT;

	/**
	 * Constructs a Vocoder.
	 * 
	 * @param windowSize
	 * 			int: the number of sample frames to use for 
	 * 				 each FFT analysis. Smaller window sizes 
	 * 				 will have better performance, but lower
	 * 				 sound quality. the window size must also 
	 * 				 be a power of two, which is a requirement 
	 * 				 for using an FFT.
	 * 
	 * @param windowCount
	 * 			int: the number of overlapping windows to use. 
	 * 				 this must be at least 1 with larger values
	 * 				 causing the analysis windows to overlap 
	 * 				 with each other to a greater degree.
	 * 				 For instance, with a windowSize of 1024 and 
	 * 				 a windowCount of 2, a 1024 sample frame FFT 
	 * 				 will be calculated every 512 sample frames. 
	 * 				 With 3 windows, every 341 samples, and so forth.
	 * 				 More windows generally equates to better quality.
	 * 
	 *  @related Vocoder
	 */
	public Vocoder(int windowSize, int windowCount)
	{
		audio = new UGenInput( InputType.AUDIO );
		modulator = new UGenInput( InputType.AUDIO );

		float overlapPercent = 1.f;
		m_outputScale = 1.f;
		if ( windowCount > 1 )
		{
			overlapPercent = 1.f / (float)windowCount;
			m_outputScale = overlapPercent / 8.f;
		}
		m_windowSize = windowSize;
		m_windowSpacing = (int)( windowSize * overlapPercent );
		int bufferSize = m_windowSize * 2 - m_windowSpacing;
		m_audioSamples = new float[bufferSize];
		m_modulatorSamples = new float[bufferSize];
		m_outputSamples = new float[bufferSize];
		m_analysisSamples = new float[windowSize];
		m_index = 0;
		m_triggerCount = m_windowSize;
		// need to defer creation of the FFT objects until we know our sample
		// rate.
	}

	protected void sampleRateChanged()
	{
		m_audioFFT = new FFT( m_windowSize, sampleRate() );
		m_audioFFT.window( FFT.HAMMING );
		m_modulatorFFT = new FFT( m_windowSize, sampleRate() );
		m_modulatorFFT.window( FFT.HAMMING );
	}

	private void analyze(FFT fft, float[] src)
	{
		// copy the previous windowSize samples into our analysis window
		for ( int i = m_index - m_windowSize, j = 0; i < m_index; ++i, ++j )
		{
			m_analysisSamples[j] = ( i < 0 ) ? src[src.length + i] : src[i];
		}
		fft.forward( m_analysisSamples );
	}

	protected void uGenerate(float[] out)
	{
		m_audioSamples[m_index] = audio.getLastValue();
		m_modulatorSamples[m_index] = modulator.getLastValue();
		++m_index;
		--m_triggerCount;
		if ( m_index == m_audioSamples.length )
		{
			m_index = 0;
		}

		// we reached the end of our window. analyze and synthesize!
		if ( m_triggerCount == 0 )
		{
			analyze( m_audioFFT, m_audioSamples );
			analyze( m_modulatorFFT, m_modulatorSamples );

			for ( int i = 0; i < m_audioFFT.specSize(); ++i )
			{
				m_audioFFT.scaleBand( i, m_modulatorFFT.getBand( i ) );
			}

			// synthesize
			m_audioFFT.inverse( m_analysisSamples );

			// window
			FFT.HAMMING.apply( m_analysisSamples );

			// accumulate
			for ( int a = 0; a < m_windowSize; ++a )
			{
				int outIndex = m_outputIndex + a;
				if ( outIndex >= m_outputSamples.length )
				{
					outIndex -= m_outputSamples.length;
				}
				m_outputSamples[outIndex] += m_analysisSamples[a] * m_outputScale;
			}

			m_triggerCount = m_windowSpacing;
		}

		for ( int i = 0; i < out.length; ++i )
		{
			out[i] = m_outputSamples[m_outputIndex];
		}
		// eat it.
		m_outputSamples[m_outputIndex] = 0.f;
		// next!
		++m_outputIndex;
		if ( m_outputIndex == m_outputSamples.length )
		{
			m_outputIndex = 0;
		}
	}
}
