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