package ddf.minim.ugens;
import java.util.Arrays;
import ddf.minim.UGen;
/**
* <p>
* An Oscil is a UGen that generates audio by oscillating over a Waveform
* at a particular frequency. For instance, if you were to create this Oscil:
* </p>
* <pre>Oscil testTone = new Oscil( 440, 1, Waves.SINE );</pre>
* <p>
* When patched to an AudioOuput, it would generate a continuous sine wave tone
* at 440 Hz and would sound like a test tone.
* This frequency also happens to be the same as the pitch played
* by the lead oboist in a orchestra when they tune up at the beginning of a concert.
* </p>
* <p>
* However, rather than give Oscil a fixed, or limited, set of sounds it
* can generate, instead it simply oscillates over a generic Waveform object.
* Waveform is simply an <em>interface</em> that declares a value method, which
* is used by Oscil to determine what value it should output at any given moment
* in time. Generally, you will use predefined Waveforms from the Waves class,
* or generated Waveforms using the WavetableGenerator class. However, there's
* no particular reason you couldn't define your own classes that implement
* the Waveform interface.
* </p>
* <p>
* Another abstraction the Oscil UGen makes use of is the Frequency class.
* This class allows you to define a frequency in terms of pitch, midi note,
* or hertz. This is often quite useful when writing musical scores with code.
* For instance, we could use the Frequency class when creating an Oscil that
* will sound the same as the example above:
* </p>
* <pre>Oscil testTone = new Oscil( Frequency.ofPitch("A4"), 1, Waves.SINE );</pre>
*
* @example Basics/SynthesizeSound
*
* @related UGen
* @related Waveform
* @related Waves
* @related WavetableGenerator
* @related Frequency
*
* @author Damien Di Fede, Anderson Mills
*
*/
public class Oscil extends UGen
{
/**
* Patch to this to control the amplitude of the oscillator with another
* UGen.
*
* @example Synthesis/oscilEnvExample
*
* @related Oscil
*/
public UGenInput amplitude;
/**
* Patch to this to control the frequency of the oscillator with another
* UGen.
*
* @example Synthesis/frequencyModulation
*
* @related Oscil
*/
public UGenInput frequency;
/**
* Patch to this to control the phase of the oscillator with another UGen.
*
* @example Synthesis/oscilPhaseExample
*
* @related Oscil
*/
public UGenInput phase;
/**
* Patch to this to control the DC offset of the Oscil with another UGen.
* This is useful when using an Oscil as a modulator.
*
* @example Synthesis/frequencyModulation
*
* @related Oscil
*/
public UGenInput offset;
// the waveform we will oscillate over
private Waveform wave;
// 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;
// constructors
/**
* Constructs an Oscil UGen, given frequency in Hz, amplitude, and a waveform
*
* @param frequencyInHertz
* float: the frequency this Oscil should oscillate at
* @param amplitude
* float: the amplitude of this Oscil.
* @param waveform
* Waveform: the waveform this Oscil will oscillate over
*
* @related Waveform
*/
public Oscil(float frequencyInHertz, float amplitude, Waveform waveform)
{
this( Frequency.ofHertz( frequencyInHertz ), amplitude, waveform );
}
/**
* Constructs an Oscil UGen given frequency in Hz and amplitude. This
* oscillator uses a sine wave.
*
* @param frequencyInHertz
* float: the frequency this Oscil should oscillate at
* @param amplitude
* float: the amplitude of this Oscil.
*/
public Oscil(float frequencyInHertz, float amplitude)
{
this( Frequency.ofHertz( frequencyInHertz ), amplitude );
}
/**
* Constructs an Oscil UGen given a Frequency and amplitude. This oscillator
* uses a sine wave.
*
* @param frequency
* Frequency: the frequency this Oscil should oscillate at.
* @param amplitude
* float: the amplitude of this Oscil.
*/
// shortcut for building a sine wave
public Oscil(Frequency frequency, float amplitude)
{
this( frequency, amplitude, Waves.SINE );
}
/**
* Constructs an Oscil UGen given a Frequency, amplitude, and a waveform
*
* @param frequency
* Frequency: the frequency this Oscil should oscillate at.
* @param amplitude
* float: the amplitude of this Oscil.
* @param waveform
* Waveform: the waveform this Oscil will oscillate over
*
* @related Frequency
* @related Waveform
*/
public Oscil(Frequency frequency, float amplitude, Waveform waveform)
{
super();
this.amplitude = new UGenInput( InputType.CONTROL );
this.amplitude.setLastValue( amplitude );
this.frequency = new UGenInput( InputType.CONTROL );
this.frequency.setLastValue( frequency.asHz() );
phase = new UGenInput( InputType.CONTROL );
phase.setLastValue( 0.f );
offset = new UGenInput( InputType.CONTROL );
offset.setLastValue( 0.f );
wave = waveform;
step = 0f;
oneOverSampleRate = 1.f;
}
/**
* This routine will be called any time the sample rate changes.
*/
protected void sampleRateChanged()
{
oneOverSampleRate = 1 / sampleRate();
// don't call updateStepSize because it checks for frequency change
stepSize = frequency.getLastValue() * oneOverSampleRate;
prevFreq = frequency.getLastValue();
}
// updates our step size based on the current frequency
private void updateStepSize()
{
float currFreq = frequency.getLastValue();
if ( prevFreq != currFreq )
{
stepSize = currFreq * oneOverSampleRate;
prevFreq = currFreq;
}
}
/**
* Sets the frequency of this Oscil. You might want to do this to change the
* frequency of this Oscil in response to a button press or something. For
* controlling frequency continuously over time you will usually want to use
* the frequency input.
*
* @shortdesc Sets the frequency of this Oscil.
*
* @param hz
* the frequency, in Hertz, to set this Oscil to
*
* @example Basics/SynthesizeSound
*
* @related frequency
* @related Frequency
* @related Oscil
*/
public void setFrequency(float hz)
{
frequency.setLastValue( hz );
updateStepSize();
}
/**
* Sets the frequency of this Oscil. You might want to do this to change the
* frequency of this Oscil in response to a button press or something. For
* controlling frequency continuously over time you will usually want to use
* the frequency input.
*
* @shortdesc Sets the frequency of this Oscil.
*
* @param newFreq
* the Frequency to set this Oscil to
*
* @example Basics/SynthesizeSound
*
* @related frequency
* @related Frequency
* @related Oscil
*/
public void setFrequency(Frequency newFreq)
{
frequency.setLastValue( newFreq.asHz() );
updateStepSize();
}
/**
* Sets the amplitude of this Oscil. You might want to do this to change the
* amplitude of this Oscil in response to a button press or something. For
* controlling amplitude continuously over time you will usually want to use
* the amplitude input.
*
* @shortdesc Sets the amplitude of this Oscil.
*
* @param newAmp
* amplitude to set this Oscil to
*
* @example Basics/SynthesizeSound
*
* @related amplitude
* @related Oscil
*/
public void setAmplitude(float newAmp)
{
amplitude.setLastValue( newAmp );
}
/**
* Set the amount that the phase will be offset by. Oscil steps its time
* from 0 to 1, which means that the phase is also normalized. However, it
* still makes sense to set the phase to greater than 1 or even to a
* negative number.
*
* @shortdesc Set the amount that the phase will be offset by.
*
* @param newPhase
* float: the phase offset value
*
* @related phase
* @related Oscil
*/
public void setPhase(float newPhase)
{
phase.setLastValue( newPhase );
}
/**
* Changes the Waveform used by this Oscil.
*
* @param theWaveform
* the new Waveform to use
*
* @example Basics/SynthesizeSound
*
* @related Waveform
* @related Oscil
*/
public void setWaveform(Waveform theWaveform)
{
wave = theWaveform;
}
/**
* Returns the Waveform currently being used by this Oscil.
*
* @return a Waveform
*
* @example Basics/SynthesizeSound
*
* @related Waveform
* @related Oscil
*/
public Waveform getWaveform()
{
return wave;
}
/**
* Resets the time-step used by the Oscil to be equal to the current
* phase input value. You will typically use this when starting a new note with an
* Oscil that you have already used so that the waveform will begin sounding
* at the beginning of its period, which will typically be a zero-crossing.
* In other words, use this to prevent clicks when starting Oscils that have
* been used before.
*
* @shortdesc Resets the time-step used by the Oscil to be equal to the current
* phase input value.
*
* @example Synthesis/oscilPhaseExample
*
* @related Oscil
*/
public void reset()
{
step = phase.getLastValue();
}
@Override
protected void uGenerate(float[] channels)
{
// start with our base amplitude
float outAmp = amplitude.getLastValue();
// temporary step location with phase offset.
float tmpStep = step + phase.getLastValue();
// don't be less than zero
if ( tmpStep < 0.f )
{
tmpStep -= (int)tmpStep - 1f;
}
// don't exceed 1.
// we don't use Math.floor because that involves casting up
// to a double and then back to a float.
if ( tmpStep > 1.0f )
{
tmpStep -= (int)tmpStep;
}
// calculate the sample value
float sample = outAmp * wave.value( tmpStep ) + offset.getLastValue();
Arrays.fill( channels, sample );
// update our step size.
// this will check to make sure the frequency has changed.
updateStepSize();
// increase time
// NOT THIS FROM BEFORE: step += stepSize + fPhase;
step += stepSize;
// don't be less than zero
if ( step < 0.f )
{
step -= (int)step - 1f;
}
// don't exceed 1.
// we don't use Math.floor because that involves casting up
// to a double and then back to a float.
if ( step > 1.0f )
{
step -= (int)step;
}
}
}