package ddf.minim;
import java.util.ArrayList;
import java.util.Arrays;
/**
* The UGen class is an abstract class which provides the basis for all
* UGens in Minim. UGen is short for Unit Generator, which is simply something
* that either generates a sample value, or transforms the sample value produced by
* another UGen. Since everything is a UGen, there is a common interface for
* patching things together. For instance, you might have a line of code that
* looks like this:
*
* <pre>osc.patch( filter ).patch( adsr ).patch( output );</pre>
*
* You can read this code left to right. It says that the output of an Oscil
* should be sent through a filter (perhaps a LowPass) and the output of the
* filter should be sent through an ADSR envelope, which should then be sent to
* an AudioOutput. It's incredibly clear what the signal path is and it can
* be stated concisely.
* <p>
* UGens might also have UGenInputs. Oscil, for example, has a UGenInput called
* <code>frequency</code>. UGenInputs can be patched to, just like UGens, which
* means you might have a line of code like this:
*
* <pre>line.patch( osc.frequency );</pre>
*
* This says that a Line UGen should control the value of the Oscil's frequency.
* You may have created a Line that changes it's value from 440 to 880 over 2
* seconds. The audible result, when you call <code>activate()</code> on the Line,
* is that the Oscil will sweep upwards in frequency and then hold there until you activate the
* Line again. All of this control happens on a sample-by-sample basis, which
* means (hopefully) no clicks and pops.
*
* For a list of all UGens included with Minim, see the <a href="index_ugens.html">UGens package doc</a>.
*
* @example Basics/SynthesizeSound
*
* @author Damien Di Fede, Anderson Mills
*/
public abstract class UGen
{
/**
* This enum is used to specify the InputType of the UGenInput.
* An AUDIO UGenInput will have a last values array that conforms
* to the channel count of the UGen that owns it, whereas a CONTROL
* UGenInput will always have only one channel.
*
* @author Anderson Mills
* @nosuperclasses
*/
// jam3: enum is automatically static so it can't be in the nested class
public enum InputType
{
CONTROL, AUDIO
};
// ddf: UGen class members are before the UGenInput definition because the
// UGenInput class
// refers to some of these. I think it's clearer to see these before reading
// the
// UGenInput code.
// list of UGenInputs connected to this UGen
private ArrayList<UGenInput> m_allInputs;
// last values generated by this UGen
private float[] m_lastValues;
// m_sampleRate of this UGen
private float m_sampleRate;
// number of outputs connected to this UGen
private int m_nOutputs;
// counter for the m_currentTick with respect to the number of Outputs
private int m_currentTick;
/**
* A UGenInput represents parameter of the UGen that can be
* controlled by other UGens by patching to it. When not patched,
* a UGenInput produces a constant value, which can be changed at
* any time by calling setLastValue.
* <p>
* A UGenInput will have an InputType of either AUDIO or CONTROL.
* An AUDIO input will always have the same number of channels
* as the owning UGen, in other words the length of the array
* returned by getLastValues will have a length equal to
* channel count. A CONTROL input will always have one channel
* and its value can be conveniently queried by calling getLastValue().
*
* @example Basics/PatchingAnInput
* @author Anderson Mills
*
*/
public final class UGenInput
{
private UGen m_incoming;
private InputType m_inputType;
private float[] m_lastValues;
/**
* Create a UGenInput with a particular type.
*
* @param type the InputType of this UGenInput
*/
public UGenInput(InputType type)
{
m_inputType = type;
m_allInputs.add( this );
// assume one channel. good for controls and mono audio.
m_lastValues = new float[1];
}
/**
* Create a UGenInput of the specified type with an initial value.
*
* @param type the InputType of this UGenInput
* @param value the initial float value used for all last values
*/
public UGenInput( InputType type, float value )
{
m_inputType = type;
m_allInputs.add( this );
m_lastValues = new float[1];
m_lastValues[0] = value;
}
/**
* Set the number of channels this input should generate.
* This will be called by the owning UGen if this input
* is an AUDIO input.
*
* @param numberOfChannels
* how many channels this input should generate
*/
public void setChannelCount(int numberOfChannels)
{
if ( m_lastValues.length != numberOfChannels )
{
// make sure we keep the value we already had when
// our channel count changes.
float val = m_lastValues.length > 0 ? m_lastValues[0] : 0;
m_lastValues = new float[numberOfChannels];
Arrays.fill(m_lastValues, val);
}
// make sure our incoming UGen knows about this
if ( m_incoming != null )
{
m_incoming.setChannelCount( numberOfChannels );
}
}
/**
* Returns how many channels this UGenInput generates.
*
* @return int: how many channels this input generates
*/
public int channelCount()
{
return m_lastValues.length;
}
/**
* Returns the InputType of this UGenInput.
*
* @return InputType: either AUDIO or CONTROL
*/
public InputType getInputType()
{
return m_inputType;
}
/**
* The outer UGen is the UGen that owns this input.
* For instance, calling this on the frequency UGenInput
* member of an Oscil will return the Oscil.
*
* @return the UGen that owns this UGenInput
*/
public UGen getOuterUGen()
{
return UGen.this;
}
/**
* The incoming UGen is the UGen that is patched to
* this UGenInput. When this input is ticked, it
* will tick the incoming UGen and store the result
* in its last values.
*
* @return the UGen that is patched to this UGenInput
*/
public UGen getIncomingUGen()
{
return m_incoming;
}
/**
* This method is called when a UGen is patched to this input.
* Typically you will not call this method directly,
* use UGen's patch method instead.
*
* @param in
* the UGen being patched to this input
*/
public void setIncomingUGen(UGen in)
{
m_incoming = in;
if ( m_incoming != null )
{
m_incoming.setChannelCount( m_lastValues.length );
}
}
/**
* Returns true if a UGen is patched to this UGenInput.
*
* @return true if a UGen is patched to this UGenInput
*/
public boolean isPatched()
{
return ( m_incoming != null );
}
/**
* Access the last values generated by this input.
*
* @return float[]: the last values generated by this input
*/
public float[] getLastValues()
{
return m_lastValues;
}
/**
* Returns the first value in the array of last values. This is meant to
* make code that gets values from CONTROL inputs easier to read.
*
* @shortdesc Returns the first value in the array of last values.
*
* @return float: the last value generated by this input
*/
// TODO (ddf) change these two to getValue and setValue?
public float getLastValue()
{
return m_lastValues[0];
}
/**
* <p>
* Sets all values in the last values array to the provided value. If
* you want to set last values in the different channels of this input
* to different values, you should use getLastValues to do so. For
* example:
* </p>
* <pre>
* ugen.anInput.getLastValues()[0] = 1.f;
* ugen.anInput.getLastValues()[1] = 0.f;
* </pre>
*
* @shortdesc Sets all values in the last values array to the provided value.
*
* @param value
* float: the value to set all last values to
*/
public void setLastValue(float value)
{
for ( int i = 0; i < m_lastValues.length; ++i )
{
m_lastValues[i] = value;
}
}
// this will be called by the owning UGen *only* when something is
// patched to this input.
void tick()
{
if ( m_incoming != null )
{
m_incoming.tick( m_lastValues );
}
}
/**
* @return the InputType as a string (for debugging)
*/
public String getInputTypeAsString()
{
String typeLabel = null;
switch ( m_inputType )
{
case AUDIO:
typeLabel = "AUDIO";
break;
case CONTROL:
typeLabel = "CONTROL";
break;
}
return typeLabel;
}
/**
* Print information about this UGenInput (for debugging)
*/
public void printInput()
{
Minim.debug( "UGenInput: " + " signal = " + getInputTypeAsString() + " " + ( m_incoming != null ) );
}
} // ends the UGenInput inner class
/**
* Constructor for a UGen.
*/
protected UGen()
{
m_allInputs = new ArrayList<UGenInput>();
m_lastValues = new float[0];
m_nOutputs = 0;
m_currentTick = 0;
}
/**
* Send the output of this UGen to another UGen, UGenInput, or AudioOutput.
* For instance, if an Oscil is patched to an AudioOutput, you will hear
* the sound it generates. If a FilePlayer is patched to a Delay, then the
* delay effect will be applied to the sound generated by the FilePlayer.
*
* @shortdesc Send the output of this UGen to another UGen, UGenInput, or AudioOutput.
*
* @example Basics/PatchingAnInput
*
* @param connectToUGen
* The UGen to patch to.
* @return When patching to a UGen or UGenInput, the UGen being patched to is returned
* so that you can chain patch calls. For example:
*
* <pre>
* sine.patch( gain ).patch( out );
* </pre>
*/
// ddf: this is final because we never want people to override it.
public final UGen patch(UGen connectToUGen)
{
setSampleRate( connectToUGen.m_sampleRate );
// jam3: connecting to a UGen is the same as connecting to it's first
// input
connectToUGen.addInput( this );
// TODO jam3: m_nOutputs should only increase when this chain will be
// ticked!
m_nOutputs += 1;
Minim.debug( "m_nOutputs = " + m_nOutputs );
return connectToUGen;
}
/**
* Connect the output of this UGen to a specific UGenInput of a UGen.
*
* @param connectToInput
* The UGenInput to patch to.
* @return the UGen that owns connectToInput
*/
public final UGen patch(UGenInput connectToInput)
{
setSampleRate( connectToInput.getOuterUGen().m_sampleRate );
connectToInput.setIncomingUGen( this );
// TODO jam3: m_nOutputs should only increase when this chain will be
// ticked!
m_nOutputs += 1;
Minim.debug( "m_nOutputs = " + m_nOutputs );
return connectToInput.getOuterUGen();
}
/**
* Patch the output of this UGen to the provided AudioOuput. Doing so will
* immediately result in this UGen and all UGens patched into it to begin
* generating audio.
*
* @param audioOutput
* The AudioOutput you want to connect this UGen to.
*/
public final void patch(AudioOutput audioOutput)
{
Minim.debug( "Patching " + this + " to the output " + audioOutput + "." );
setSampleRate( audioOutput.sampleRate() );
setChannelCount( audioOutput.getFormat().getChannels() );
patch( audioOutput.bus );
}
/**
* If you want to do something other than the default behavior when your
* UGen is patched to, you can override this method in your derived class.
* Summer, for instance, keeps a list of all the UGens that have been
* patched to it, so that it can tick them and sum the results when it
* uGenerates.
*
* @param input the UGen to add as an input
*/
// ddf: Protected because users of UGens should never call this directly.
// Sub-classes can override this to control what happens when something
// is patched to them. See the Summer class.
protected void addInput(UGen input)
{
// jam3: This default behavior is that the incoming signal will be added
// to the first input in the m_allInputs list.
Minim.debug( "UGen addInput called." );
// TODO change input checking to an Exception?
if ( m_allInputs.size() > 0 )
{
Minim.debug( "Initializing default input on something" );
this.m_allInputs.get( 0 ).setIncomingUGen( input );
}
else
{
System.err.println( "Trying to connect to UGen with no default input." );
}
}
/**
* Unpatch this UGen from an AudioOutput or other UGen.
* This causes this UGen and all UGens patched into it to stop generating audio
* if they are not patched to an AudioOuput somewhere else in the chain.
*
* @shortdesc Unpatch this UGen from an AudioOutput or other UGen.
*
* @param audioOutput
* The AudioOutput this UGen should be disconnected from.
*/
public final void unpatch( AudioOutput audioOutput )
{
Minim.debug( "Unpatching " + this + " from the output " + audioOutput + "." );
unpatch( audioOutput.bus );
}
/**
* Remove this UGen as an input of fromUGen.
*
* @param fromUGen
* The UGen to unpatch from.
*
*/
public final void unpatch( UGen fromUGen )
{
fromUGen.removeInput( this );
// TODO m_nOutputs needs to be updated as the converse of patch above.
m_nOutputs -= 1;
Minim.debug( "m_nOutputs = " + m_nOutputs );
}
/**
* When a UGen is unpatched from this UGen, removeInput is called.
* If you've written an UGen subclass that needs to know when this
* happens or has special handling of input removal, you can override
* this method. See the implementation of Summer for an example
* of why you might need to do this.
*
* @param input
* the UGen to remove as an input to this UGen
*/
// This currently does nothing, but is overridden in Summer.
protected void removeInput(UGen input)
{
Minim.debug( "UGen removeInput called." );
// see if any of our ugen inputs currently have input as the incoming ugen
// set their incoming ugen to null if that's the case
for ( int i = 0; i < m_allInputs.size(); i++ )
{
if ( m_allInputs.get( i ).getIncomingUGen() == input )
{
this.m_allInputs.get( i ).setIncomingUGen( null );
}
}
}
/**
* Generates one sample frame for this UGen.
*
* @param channels
* An array that represents one sample frame. To generate a mono
* signal, pass an array of length 1, if stereo an array of
* length 2, and so on. How a UGen deals with multi-channel sound
* will be implementation dependent.
*/
public final void tick(float[] channels)
{
if ( m_nOutputs > 0 )
{
// only tick once per sampleframe when multiple outputs
m_currentTick = ( m_currentTick + 1 ) % ( m_nOutputs );
}
if ( 0 == m_currentTick )
{
for ( int i = 0; i < m_allInputs.size(); ++i )
{
m_allInputs.get( i ).tick();
}
// and then uGenerate for this UGen
uGenerate( channels );
for( int i = 0; i < channels.length && i < m_lastValues.length; ++i )
{
m_lastValues[i] = channels[i];
}
}
else
{
for( int i = 0; i < channels.length && i < m_lastValues.length; ++i )
{
channels[i] = m_lastValues[i];
}
}
}
/**
* Implement this method when you extend UGen. It will be called when your
* UGen needs to generate one sample frame of audio. It is expected that you
* will assign values to the array and <em>not</em> simply modify the
* existing values. In the case where you write a UGen that takes audio
* input and modifies it, the pattern to follow is to have the first
* UGenInput you create be your audio input and then in uGenerate you will
* use the <code>getLastValues</code> method of your audio UGenInput to
* retrieve the audio you want to modify, which you will then modify however
* you need to, assigning the result to the values in <code>channels</code>.
*
* @param channels
* an array representing one sample frame.
*/
protected abstract void uGenerate(float[] channels);
/**
* Return the last values generated by this UGen. This will most often be
* used by sub-classes when pulling data from their inputs.
*
* @return float[]: array containing the most recent sample frame this UGen generated
*/
public final float[] getLastValues()
{
return m_lastValues;
}
/**
* Returns the sample rate of this UGen.
*
* @return float: the current sample rate of this UGen
*/
public final float sampleRate()
{
return m_sampleRate;
}
/**
* Override this method in your derived class to receive a notification when
* the sample rate of your UGen has changed. You might need to do this to
* recalculate sample rate dependent values, such as the step size for an
* oscillator.
*
*/
protected void sampleRateChanged()
{
// default implementation does nothing.
}
/**
* Set the sample rate for this UGen.
*
* @param newSampleRate
* float, the sample rate this UGen should generate at.
*/
// ddf: changed this to public because Summer needs to be able to call it
// on all of its UGens when it has its sample rate set by being connected
// to an AudioOuput. Realized it's not actually a big deal for people to
// set the sample rate of any UGen they create whenever they want. In fact,
// could actually make total sense to want to do this with something playing
// back a chunk of audio loaded from disk. Made this final because it should
// never be overridden. If sub-classes need to know about sample rate
// changes
// the should override sampleRateChanged()
public final void setSampleRate(float newSampleRate)
{
if ( m_sampleRate != newSampleRate )
{
m_sampleRate = newSampleRate;
sampleRateChanged();
// these are guaranteed to have an incoming UGen
// if one doesn't it's probably a bug!
for ( int i = 0; i < m_allInputs.size(); ++i )
{
UGen inputIncoming = m_allInputs.get( i ).getIncomingUGen();
if ( inputIncoming != null )
{
inputIncoming.setSampleRate( newSampleRate );
}
}
}
}
/**
* Let this UGen know how many channels of audio you will be asking it for.
* This will be called automatically when a UGen is patched to an AudioOuput
* and propagated to all UGenInputs of type AUDIO.
*
* @shortdesc Let this UGen know how many channels of audio you will be asking it for.
*
* @param numberOfChannels
* how many channels of audio you will be generating with this UGen
*/
public void setChannelCount(int numberOfChannels)
{
for ( int i = 0; i < m_allInputs.size(); ++i )
{
UGenInput input = m_allInputs.get( i );
if ( input.getInputType() == InputType.AUDIO )
{
input.setChannelCount( numberOfChannels );
}
}
if ( m_lastValues.length != numberOfChannels )
{
m_lastValues = new float[numberOfChannels];
channelCountChanged();
}
}
/**
* Returns the number of channels this UGen has been configured to generate.
*
* @return int: how many channels of audio this UGen will generate
*/
public int channelCount() { return m_lastValues.length; }
/**
* This method is only called when setChannelCount results in the channel count
* of this UGen actually changing. Override this function in
* sub-classes of UGen if you need to reconfigure things
* when the channel count changes.
*/
protected void channelCountChanged() {}
/**
* Prints all inputs connected to this UGen (for debugging)
*/
public void printInputs()
{
for ( int i = 0; i < m_allInputs.size(); i++ )
{
Minim.debug( "m_allInputs " + i + " " );
if ( m_allInputs.get( i ) == null )
{
Minim.debug( "null" );
}
else
{
m_allInputs.get( i ).printInput();
}
}
}
protected UGenInput addControl()
{
return new UGenInput( InputType.CONTROL );
}
protected UGenInput addControl( float initialValue )
{
return new UGenInput( InputType.CONTROL, initialValue );
}
protected UGenInput addAudio()
{
return new UGenInput( InputType.AUDIO );
}
}