package ddf.minim.ugens;
import ddf.minim.UGen;
/**
* A Flanger is a specialized kind of delay that uses an LFO (low frequency
* oscillator) to vary the amount of delay applied to each sample. This causes a
* sweeping frequency kind of sound as the signal reinforces or cancels itself
* in various ways. In particular the peaks and notches created in the frequency
* spectrum are related to each other in a linear harmonic series. This causes
* the spectrum to look like a comb.
* <p>
* Inputs for the Flanger are:
* <ul>
* <li>delay (in milliseconds): the minimum amount of delay applied to an incoming sample</li>
* <li>rate (in Hz): the frequency of the LFO</li>
* <li>depth (in milliseconds): the maximum amount of delay added onto delay by the LFO</li>
* <li>feedback: how much of delayed signal should be fed back into the effect</li>
* <li>dry: how much of the uneffected input should be included in the output</li>
* <li>wet: how much of the effected signal should be included in the output</li>
* </ul>
* <p>
* A more thorough description can be found on wikipedia:
* http://en.wikipedia.org/wiki/Flanging
* <p>
*
* @author Damien Di Fede
*
* @example Synthesis/flangerExample
*
* @related UGen
*/
public class Flanger extends UGen
{
/**
* Where the input goes.
*
* @example Synthesis/flangerExample
*
* @related Flanger
* @related UGen.UGenInput
*/
public UGenInput audio;
/**
* How much does the flanger delay the incoming signal. Used as the low
* value of the modulated delay amount.
*
* @example Synthesis/flangerExample
*
* @related Flanger
* @related UGen.UGenInput
*/
public UGenInput delay;
/**
* The frequency of the LFO applied to the delay.
*
* @example Synthesis/flangerExample
*
* @related Flanger
* @related UGen.UGenInput
*/
public UGenInput rate;
/**
* How many milliseconds the LFO increases the delay by at the maximum.
*
* @example Synthesis/flangerExample
*
* @related Flanger
* @related UGen.UGenInput
*/
public UGenInput depth;
/**
* How much of the flanged signal is fed back into the effect.
*
* @example Synthesis/flangerExample
*
* @related Flanger
* @related UGen.UGenInput
*/
public UGenInput feedback;
/**
* How much of the dry signal is added to the output.
*
* @example Synthesis/flangerExample
*
* @related Flanger
* @related UGen.UGenInput
*/
public UGenInput dry;
/**
* How much of the flanged signal is added to the output.
*
* @example Synthesis/flangerExample
*
* @related Flanger
* @related UGen.UGenInput
*/
public UGenInput wet;
private float[] delayBuffer;
private int outputFrame;
private int bufferFrameLength;
// ////////////
// LFO
// ////////////
// where we will sample our waveform, moves between [0,1]
private float step;
// the step size we will use to advance our step
private float stepSize;
// what was our frequency from the last time we updated our step size
// stashed so that we don't do more math than necessary
private float prevFreq;
// 1 / sampleRate, which is used to calculate stepSize
private float oneOverSampleRate;
/**
* Construct a Flanger by specifying all initial values.
*
* @param delayLength
* float: the minimum delay applied to incoming samples (in milliseconds)
* @param lfoRate
* float: the frequency of the the LFO
* @param delayDepth
* float: the maximum amount added to the delay by the LFO (in milliseconds)
* @param feedbackAmplitude
* float: the amount of the flanged signal fed back into the effect
* @param dryAmplitude
* float: the amount of incoming signal added to the output
* @param wetAmplitude
* float: the amount of the flanged signal added to the output
*/
public Flanger(float delayLength, float lfoRate, float delayDepth,
float feedbackAmplitude, float dryAmplitude, float wetAmplitude)
{
audio = addAudio();
delay = addControl( delayLength );
rate = addControl( lfoRate );
depth = addControl( delayDepth );
feedback = addControl( feedbackAmplitude );
dry = addControl( dryAmplitude );
wet = addControl( wetAmplitude );
}
private void resetBuffer()
{
int sampleCount = (int)( 100 * sampleRate() / 1000 );
delayBuffer = new float[sampleCount * audio.channelCount()];
outputFrame = 0;
bufferFrameLength = sampleCount;
}
// clamps rate for us
private float getRate()
{
float r = rate.getLastValue();
return r > 0.001f ? r : 0.001f;
}
protected void sampleRateChanged()
{
resetBuffer();
oneOverSampleRate = 1 / sampleRate();
// don't call updateStepSize because it checks for frequency change
stepSize = getRate() * oneOverSampleRate;
prevFreq = getRate();
// start at the lowest value
step = 0.25f;
}
// updates our step size based on the current frequency
private void updateStepSize()
{
float currFreq = getRate();
if ( prevFreq != currFreq )
{
stepSize = currFreq * oneOverSampleRate;
prevFreq = currFreq;
}
}
protected void channelCountChanged()
{
resetBuffer();
}
protected void uGenerate(float[] out)
{
// generate lfo value
float lfo = Waves.SINE.value( step );
// modulate the delay amount using the lfo value.
// we always modulate tp a max of 5ms above the input delay.
float dep = depth.getLastValue() * 0.5f;
float delMS = delay.getLastValue() + ( lfo * dep + dep );
// how many sample frames is that?
int delFrame = (int)( delMS * sampleRate() / 1000 );
for ( int i = 0; i < out.length; ++i )
{
int outputIndex = outputFrame * audio.channelCount() + i;
float inSample = audio.getLastValues()[i];
float wetSample = delayBuffer[outputIndex];
// figure out where we need to place the delayed sample in our ring
// buffer
int delIndex = ( ( outputFrame + delFrame ) * audio.channelCount() + i )
% delayBuffer.length;
delayBuffer[delIndex] = inSample + wetSample
* feedback.getLastValue();
// the output sample is in plus wet, each scaled by amplitude inputs
out[i] = inSample * dry.getLastValue() + wetSample
* wet.getLastValue();
}
// next output frame
++outputFrame;
if ( outputFrame == bufferFrameLength )
{
outputFrame = 0;
}
updateStepSize();
// step the LFO
step += stepSize;
if ( step > 1 )
{
step -= 1;
}
}
}