package ddf.minim.ugens;
import ddf.minim.UGen;
//An envelope follower implementation I found on the internets: http://www.musicdsp.org/showone.php?id=97
/**
* An EnvelopeFollower will analyze the audio coming into it and output a value that reflects
* the volume level of that audio. It is similar to what AudioBuffer's level method provides,
* but has the advantage of being able to be inserted into the signal chain anywhere.
*
* You may find that you are only interested in the output value of the EnvelopeFollower for
* visualization purposes, in which case you can use a Sink UGen to tick the EnvelopeFollower
* without generating any sound. The following example demonstrates this technique.
*
* It's important to note that EnvelopeFollower will convert the incoming signal to mono
* before processing it, which means that the output will be the same on all channels.
* At this time there is not an easy way work around this limitation.
*
* We still consider EnveloperFollower to be a bit experimental, so YMMV.
*
* @example Synthesis/envelopeFollowerExample
*
* @related UGen
*
*/
public class EnvelopeFollower extends UGen
{
/**
* Where incoming audio is patched.
*
* @related EnvelopeFollower
* @related UGen.UGenInput
*/
public UGenInput audio;
// attack and release time in seconds
private float m_attack;
private float m_release;
// coefficients for our envelope following algorithm
private float m_ga, m_gr;
// for collecting a buffer to calculate the envelope value
private float[] m_buffer;
// to keep track of how full our buffer is.
// when it fills all the way up, we calculate a value
// and then go back to filling.
private int m_bufferCount;
// the current value of the envelope
private float m_envelope;
// the previous value of the envelope
private float m_prevEnvelope;
/**
* Construct an EnvelopeFollower.
*
* @param attackInSeconds
* float: how many seconds the follower should take to ramp up to a higher value
* @param releaseInSeconds
* float: how many seconds the follower should take to ramp down to a lower value
* @param bufferSize
* int: how many samples should be analyzed at once. smaller buffers will make
* the follower more responsive.
*/
public EnvelopeFollower( float attackInSeconds, float releaseInSeconds, int bufferSize )
{
m_attack = attackInSeconds;
m_release = releaseInSeconds;
m_buffer = new float[bufferSize];
m_bufferCount = 0;
m_envelope = 0.f;
m_prevEnvelope = 0.f;
audio = new UGenInput( InputType.AUDIO );
}
protected void sampleRateChanged()
{
m_ga = (float)Math.exp( -1 / (sampleRate() * m_attack) );
m_gr = (float)Math.exp( -1 / (sampleRate() * m_release) );
}
protected void uGenerate( float[] out )
{
// mono-ize the signal
float signal = 0;
float[] lastValues = audio.getLastValues();
for(int i = 0; i < lastValues.length; ++i)
{
signal += lastValues[i] / lastValues.length;
}
m_buffer[m_bufferCount++] = signal;
// full buffer, find the envelope value
if ( m_bufferCount == m_buffer.length )
{
m_prevEnvelope = m_envelope;
m_envelope = 0.f;
for(int i = 0; i < m_buffer.length; ++i )
{
float envIn = Math.abs( m_buffer[i] );
if ( m_envelope < envIn )
{
m_envelope *= m_ga;
m_envelope += (1-m_ga)*envIn;
}
else
{
m_envelope *= m_gr;
m_envelope += (1-m_gr)*envIn;
}
}
m_bufferCount = 0;
}
// lerp between previous value and current value
float outEnv = m_prevEnvelope + (m_envelope - m_prevEnvelope) * ( (float)m_bufferCount / (float)m_buffer.length );
for (int i = 0; i < out.length; i++)
{
out[i] = outEnv;
}
}
}