package ddf.minim.ugens;
/**
* Waves provides some already constructed Wavetables for common waveforms, as
* well as methods for constructing some basic waveforms with non-standard
* parameters. For instance, you can use the QUARTERPULSE member if you want a
* typical "thin" square wave sound, but you might want a square wave with a 60%
* duty cycle instead, which you can create by passing 0.6f to the square
* method. Methods exist for generating basic waves with multiple harmonics,
* basic waves with different duty cycles, and noise.
*
* @example Synthesis/waveformExample
*
* @related Wavetable
* @related WavetableGenerator
* @related Oscil
*
* @author Nicolas Brix, Anderson Mills
*/
public class Waves
{
// private constructor so it doesn't show up in documentation
// and so that people can't make instances of this class, which is all
// static methods
private Waves()
{
}
/**
* standard size for a Wavetable from Waves
*/
private static int tableSize = 8192;
private static int tSby2 = tableSize / 2;
private static int tSby4 = tableSize / 4;
// Perfect waveforms
/**
* A pure sine wave.
*
* @example Basics/SynthesizeSound
*
* @related Waves
* @related Wavetable
* @related Waveform
*/
public final static Wavetable SINE = WavetableGenerator.gen10(
tableSize,
new float[] { 1 } );
/**
* A perfect sawtooth wave.
*
* @example Basics/SynthesizeSound
*
* @related Waves
* @related Wavetable
* @related Waveform
*/
public final static Wavetable SAW = WavetableGenerator.gen7(
tableSize,
new float[] { 0,-1, 1, 0 },
new int[] { tSby2, 0, tableSize - tSby2 } );
/**
* A perfect phasor wave going from 0 to 1.
*
* @related Waves
* @related Wavetable
* @related Waveform
*/
public final static Wavetable PHASOR = WavetableGenerator.gen7( tableSize,
new float[] { 0, 1 },
new int[] { tableSize } );
/**
* A perfect square wave with a 50% duty cycle.
*
* @example Basics/SynthesizeSound
*
* @related Waves
* @related Wavetable
* @related Waveform
*/
public final static Wavetable SQUARE = WavetableGenerator.gen7(
tableSize,
new float[] { -1, -1, 1, 1 },
new int[] { tSby2, 0, tableSize - tSby2 } );
/**
* A perfect triangle wave.
*
* @example Basics/SynthesizeSound
*
* @related Waves
* @related Wavetable
* @related Waveform
*/
public final static Wavetable TRIANGLE = WavetableGenerator.gen7(
tableSize,
new float[] { 0, 1, -1, 0 },
new int[] { tSby4, tSby2, tableSize - tSby2 - tSby4 } );
/**
* A perfect square wave with a 25% duty cycle.
*
* @example Basics/SynthesizeSound
*
* @related Waves
* @related Wavetable
* @related Waveform
*/
public final static Wavetable QUARTERPULSE = WavetableGenerator.gen7(
tableSize,
new float[] { -1, -1, 1, 1 },
new int[] { tSby4, 0, tableSize - tSby4 } );
/**
* Builds an approximation of a perfect sawtooth wave by summing together
* harmonically related sine waves.
*
* @param numberOfHarmonics
* int: the number of harmonics to use in the approximation. 1 harmonic
* will simply generate a sine wave. The greater the number of
* harmonics used, the closer to a pure saw wave the approximation will be.
*
* @return a Wavetable
*
* @related Waves
* @related Wavetable
* @related Waveform
*/
public static Wavetable sawh(int numberOfHarmonics)
{
float[] content = new float[numberOfHarmonics];
for ( int i = 0; i < numberOfHarmonics; i++ )
{
content[i] = (float)( ( -2 ) / ( ( i + 1 ) * Math.PI ) * Math.pow( -1, i + 1 ) );
}
return WavetableGenerator.gen10( tableSize, content );
}
/**
* Constructs a perfect sawtooth wave with the specified duty cycle.
*
* @param dutyCycle
* float: a sawtooth wave with a duty cycle of 0.5 will be
* a perfect sawtooth wave that smoothly changes from 1 to -1
* with a zero-crossing in the middle. By changing the duty
* cycle, you change how much of the sawtooth is below zero.
* So, a duty cycle of 0.2 would result in 20 percent of the
* sawtooth below zero and the rest above. Duty cycle will
* be clamped to [0,1].
*
* @return Wavetable
*
* @related Waves
* @related Wavetable
* @related Waveform
*/
public static Wavetable saw(float dutyCycle)
{
dutyCycle = Math.max( 0, Math.min( dutyCycle, 1 ) );
int a = (int)( tableSize * dutyCycle );
return WavetableGenerator.gen7( tableSize, new float[] { 0, -1, 1, 0 }, new int[] { a, 0, tableSize - a } );
}
/**
* Builds an approximation of a perfect square wave by summing together
* harmonically related sine waves.
*
* @param numberOfHarmonics
* int: the number of harmonics to use in the approximation. 1 harmonic
* will simply generate a sine wave. The greater the number of
* harmonics used, the closer to a pure saw wave the approximation will be.
*
* @return a Wavetable
*
* @related Waves
* @related Wavetable
* @related Waveform
*/
public static Wavetable squareh(int numberOfHarmonics)
{
float[] content = new float[numberOfHarmonics + 1];
for ( int i = 0; i < numberOfHarmonics; i += 2 )
{
content[i] = (float)1 / ( i + 1 );
content[i + 1] = 0;
}
return WavetableGenerator.gen10( tableSize, content );
}
/**
* Constructs a perfect square wave with the specified duty cycle.
*
* @param dutyCycle
* float: a square wave with a duty cycle of 0.5 will be
* a perfect square wave that is 1 half the time and -1 the other half.
* By changing the duty cycle, you change how much of the square
* is below zero. So, a duty cycle of 0.2 would result in 20 percent of the
* square below zero and the rest above. Duty cycle will
* be clamped to [0,1].
*
* @return Wavetable
*
* @related Waves
* @related Wavetable
* @related Waveform
*/
public static Wavetable square(float dutyCycle)
{// same as pulse
return pulse( dutyCycle );
}
/**
* Constructs a perfect square wave with the specified duty cycle.
*
* @param dutyCycle
* float: a square wave with a duty cycle of 0.5 will be
* a perfect square wave that is 1 half the time and -1 the other half.
* By changing the duty cycle, you change how much of the square
* is below zero. So, a duty cycle of 0.2 would result in 20 percent of the
* square below zero and the rest above. Duty cycle will
* be clamped to [0,1].
*
* @return Wavetable
*
* @related Waves
* @related Wavetable
* @related Waveform
*/
public static Wavetable pulse(float dutyCycle)
{
dutyCycle = Math.max( 0, Math.min( dutyCycle, 1 ) );
return WavetableGenerator.gen7( tableSize,
new float[] { -1, -1, 1, 1 },
new int[] { (int)( dutyCycle * tableSize ), 0, tableSize - (int)( dutyCycle * tableSize ) } );
}
/**
* Builds an approximation of a perfect triangle wave by summing together
* harmonically related sine waves.
*
* @param numberOfHarmonics
* int: the number of harmonics to use in the approximation. 1 harmonic
* will simply generate a sine wave. The greater the number of
* harmonics used, the closer to a pure saw wave the approximation will be.
*
* @return a Wavetable
*
* @related Waves
* @related Wavetable
* @related Waveform
*/
public static Wavetable triangleh(int numberOfHarmonics)
{
float[] content = new float[numberOfHarmonics + 1];
for ( int i = 0; i < numberOfHarmonics; i += 2 )
{
content[i] = (float)( Math.pow( -1, i / 2 ) * 8 / Math.PI / Math.PI / Math.pow( i + 1, 2 ) );
content[i + 1] = 0;
}
return WavetableGenerator.gen10( tableSize, content );
}
/**
* Constructs a perfect triangle wave with the specified duty cycle.
*
* @param dutyCycle
* float: a triangle wave with a duty cycle of 0.5 will be
* a perfect triangle wave that is 1 half the time and -1 the other half.
* By changing the duty cycle, you change how much of the triangle
* is below zero. So, a duty cycle of 0.2 would result in 20 percent of the
* triangle below zero and the rest above. Duty cycle will
* be clamped to [0,1].
*
* @return Wavetable
*
* @related Waves
* @related Wavetable
* @related Waveform
*/
public static Wavetable triangle(float dutyCycle)
{
dutyCycle = Math.max( 0, Math.min( dutyCycle, 1 ) );
int a = (int)( tableSize * dutyCycle * 0.5 );
return WavetableGenerator.gen7( tableSize,
new float[] { 0, -1, 0, 1, 0 }, new int[] { a, a, tSby2 - a, tableSize - tSby2 - a } );
}
// TODO a dutycycled sine wavetable : i think a new warp() method in
// Wavetable would be the best
/**
* Constructs a waveform by summing together the first numberOfHarmonics
* in the harmonic series with randomly chosen amplitudes. This often
* sounds like an organ.
*
* @param numberOfHarmonics
* int: the number of harmonics to use when generating the wave
*
* @return a Wavetable
*
* @related Waves
* @related Wavetable
* @related Waveform
*/
public static Wavetable randomNHarms(int numberOfHarmonics)
{
float[] harmAmps = new float[numberOfHarmonics];
for ( int i = 0; i < numberOfHarmonics; i++ )
{
harmAmps[i] = (float)Math.random() * 2 - 1;
}
Wavetable builtWave = WavetableGenerator.gen10( tableSize, harmAmps );
builtWave.normalize();
return builtWave;
}
/**
* Constructs a waveform by summing together the first odd numberOfHarmonics
* in the harmonic series (1, 3, 5, etc) with randomly chosen amplitudes.
* This often sounds like an organ with a band pass filter on it.
*
* @param numberOfHarmonics
* int: the number of odd harmonics to use when generating the wave
*
* @return a Wavetable
*
* @related Waves
* @related Wavetable
* @related Waveform
*/
public static Wavetable randomNOddHarms(int numberOfHarmonics)
{
float[] harmAmps = new float[numberOfHarmonics * 2];
for ( int i = 0; i < numberOfHarmonics; i += 1 )
{
harmAmps[i * 2] = (float)Math.random() * 2 - 1;
harmAmps[i * 2 + 1] = 0.0f;
}
Wavetable builtWave = WavetableGenerator.gen10( tableSize, harmAmps );
builtWave.normalize();
return builtWave;
}
/**
* Constructs a Wavetable of randomly generated noise.
*
* @return a Wavetable
*
* @related Waves
* @related Wavetable
* @related Waveform
*/
public static Wavetable randomNoise()
{
float[] builtArray = new float[tableSize];
for ( int i = 0; i < builtArray.length; i++ )
{
builtArray[i] = (float)Math.random() * 2 - 1;
}
Wavetable builtWave = new Wavetable( builtArray );
builtWave.normalize();
return builtWave;
}
/**
* Generates a Wavetable by adding any number of Waveforms, each scaled by an amplitude.
*
* Calling this method might look like:
* <code>
* Wavetable wave = Wavetable.add( new float[] { 0.8f, 0.2f }, Waves.SINE, Waves.SAW );
* </code>
* or:
* <code>
* Wavetable wave = Wavetable.add( new float[] { 0.2f, 0.3f, 0.5f }, Waves.SINE, Waves.SQUARE, Waves.sawh( 6 ) );
* </code>
*
* In other words, the number of elements in the amplitude array
* must match the number of Waveform arguments provided.
*
* @shortdesc Generates a Wavetable by adding any number of Waveforms, each scaled by an amplitude.
*
* @param amps
* float[]: an array of amplitudes used to scale the matching Waveform argument
* when adding it into the final Wavetable.
* @param waves
* Waveform vararg: The Waveforms to be added together. The number of Waveforms
* passed in as arguments much match the length of the amps array.
*
* @example Synthesis/waveformExample
*
* @return a Wavetable
*
* @related Waves
* @related Waveform
* @related Wavetable
*/
public static Wavetable add(float[] amps, Waveform... waves)
{
if ( amps.length != waves.length )
{
System.out.println( "add() : amplitude array size must match the number of waveforms!" );
return null;
}
float[] accumulate = new float[tableSize];
for ( int i = 0; i < waves.length; i++ )
{
for ( int j = 0; j < tableSize; j++ )
{
float lu = (float)j / tableSize;
accumulate[j] += waves[i].value( lu ) * amps[i];
}
}
return new Wavetable( accumulate );
}
}