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;
}
}
}