/*
* Copyright (c) 2007 by Damien Di Fede <ddf@compartmental.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as published
* by the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package ddf.minim.signals;
import processing.core.PApplet;
import ddf.minim.AudioSignal;
import ddf.minim.Minim;
/**
* <code>Oscillator</code> is an implementation of an <code>AudioSignal</code>
* that handles most of the work associated with an oscillatory signal like a
* sine wave. To create your own oscillator you must extend
* <code>Oscillator</code> and implement the {@link #value(float) value}
* method. <code>Oscillator</code> will call this method every time it needs
* to sample your waveform. The number passed to the method is an offset from
* the beginning of the waveform's period and should be used to sample your
* waveform at that point.
*
* @author Damien Di Fede
*
*/
public abstract class Oscillator implements AudioSignal
{
/** The float value of 2*PI. Provided as a convenience for subclasses. */
protected static final float TWO_PI = (float) (2 * Math.PI);
/** The current frequency of the oscillator. */
private float freq;
/** The frequency to transition to. */
private float newFreq;
/** The sample rate of the oscillator. */
private float srate;
/** The current amplitude of the oscillator. */
private float amp;
/** The amplitude to transition to. */
private float newAmp;
/** The current position in the waveform's period. */
private float step;
/**
* The amount to increment step between calls to the <code>value</code>
* method.
*/
private float stepSize;
/** The portamento state. */
private boolean port;
/** The portamento speed in milliseconds. */
private float portSpeed; // in milliseconds
/**
* The amount to increment or decrement <code>freq</code> during the
* transition to <code>newFreq</code>.
*/
private float portStep;
/** The current pan position. */
private float pan;
/** The pan position to transition to. */
private float newPan;
/**
* The amount to scale the left channel's amplitude to achieve the current pan
* setting.
*/
private float leftScale;
/**
* The amount to scale the right channel's amplitude to achieve the current
* pan setting.
*/
private float rightScale;
/**
* Constructs an Oscillator with the requested frequency, amplitude and sample
* rate.
*
* @param frequency
* the frequency of the Oscillator
* @param amplitude
* the amplitude of the Oscillator
* @param sampleRate
* the sample rate of the Oscillator
*/
public Oscillator(float frequency, float amplitude, float sampleRate)
{
freq = frequency;
newFreq = freq;
amp = amplitude;
newAmp = amp;
srate = sampleRate;
step = 0;
stepSize = 1 / (float) sampleRate;
port = false;
portStep = 0.01f;
pan = 0;
newPan = 0;
leftScale = rightScale = 1;
}
public final float sampleRate()
{
return srate;
}
/**
* Sets the frequency of the Oscillator in Hz. If portamento is on, the
* frequency of the Oscillator will transition from the current frequency to
* <code>f</code>.
*
* @param f
* the new frequency of the Oscillator
*/
public final void setFreq(float f)
{
newFreq = f;
// we want to step from freq to new newFreq in portSpeed milliseconds
// first off, we want to divide the difference between the two freqs
// by the number of milliseconds it's supposed to take to get there
float msStep = (newFreq - freq) / portSpeed;
// but since freq is incremented at every sample, we need to divide
// again by the number of samples per millisecond
float spms = srate / 1000;
portStep = msStep / spms;
}
/**
* Returns the current frequency.
*
* @return the current frequency
*/
public final float frequency()
{
return freq;
}
/**
* Set the amplitude of the Oscillator, range is [0, 1].
*
* @param a
* the new amplitude, it will be constrained to [0, 1]
*/
public final void setAmp(float a)
{
newAmp = PApplet.constrain(a, 0, 1);
}
/**
* Returns the current amplitude.
*
* @return the current amplitude
*/
public final float amplitude()
{
return amp;
}
/**
* Set the pan of the Oscillator, range is [-1, 1].
*
* @param p -
* the new pan value, it will be constrained to [-1, 1]
*/
public final void setPan(float p)
{
newPan = PApplet.constrain(p, -1, 1);
}
/**
* Returns the current pan value.
*
* @return the current pan value
*/
public final float pan()
{
return pan;
}
/**
* Sets how many milliseconds it should take to transition from one frequency
* to another when setting a new frequency.
*
* @param millis
* the length of the portamento
*/
public final void portamento(int millis)
{
if (millis <= 0)
{
Minim
.error("Oscillator.portamento: The portamento speed must be greater than zero.");
}
port = true;
portSpeed = millis;
}
/**
* Turns off portamento.
*
*/
public final void noPortamento()
{
port = false;
}
public final void generate(float[] signal)
{
if (port && freq != newFreq)
{
for (int i = 0; i < signal.length; i++)
{
signal[i] = amp * value(step);
if (Math.abs(freq - newFreq) < 0.1f)
freq = newFreq;
else
freq += portStep;
monoStep();
}
}
else if (freq != newFreq)
{
for (int i = 0; i < signal.length / 2; i++)
{
float fadeOut = PApplet.map(i, 0, signal.length / 2, amp, 0);
signal[i] = fadeOut * value(step);
monoStep();
}
freq = newFreq;
for (int i = signal.length / 2; i < signal.length; i++)
{
float fadeIn = PApplet.map(i, signal.length / 2, signal.length, 0, amp);
signal[i] = fadeIn * value(step);
monoStep();
}
}
else
{
for (int i = 0; i < signal.length; i++)
{
signal[i] = amp * value(step);
monoStep();
}
}
}
public final void generate(float[] left, float[] right)
{
if (port && freq != newFreq)
{
for (int i = 0; i < left.length; i++)
{
left[i] = leftScale * amp * value(step);
right[i] = rightScale * amp * value(step);
if (Math.abs(freq - newFreq) < 0.1f)
freq = newFreq;
else
freq += portStep;
stereoStep();
}
}
else if (freq != newFreq)
{
for (int i = 0; i < left.length / 2; i++)
{
float fadeOut = PApplet.map(i, 0, left.length / 2, amp, 0);
left[i] = leftScale * fadeOut * value(step);
right[i] = rightScale * fadeOut * value(step);
stereoStep();
}
freq = newFreq;
for (int i = left.length / 2; i < left.length; i++)
{
float fadeIn = PApplet.map(i, left.length / 2, left.length, 0, amp);
left[i] = leftScale * fadeIn * value(step);
right[i] = rightScale * fadeIn * value(step);
stereoStep();
}
}
else
{
for (int i = 0; i < left.length; i++)
{
left[i] = leftScale * amp * value(step);
right[i] = rightScale * amp * value(step);
stereoStep();
}
}
}
private void monoStep()
{
stepStep();
stepAmp();
}
private void stereoStep()
{
stepStep();
stepAmp();
calcLRScale();
stepPan();
}
private void stepStep()
{
step += stepSize;
if (step > period()) step %= period();
}
private void calcLRScale()
{
if (pan <= 0)
{
rightScale = PApplet.map(pan, -1, 0, 0, 1);
leftScale = 1;
}
if (pan >= 0)
{
leftScale = PApplet.map(pan, 0, 1, 1, 0);
rightScale = 1;
}
if (pan == 0)
{
leftScale = rightScale = 1;
}
}
private static float panAmpStep = 0.0001f;
private void stepPan()
{
if (pan != newPan)
{
if (pan < newPan)
pan += panAmpStep;
else
pan -= panAmpStep;
if (Math.abs(pan - newPan) < panAmpStep) pan = newPan;
}
}
private void stepAmp()
{
if (amp != newAmp)
{
if (amp < newAmp)
amp += panAmpStep;
else
amp -= panAmpStep;
if (Math.abs(amp - newAmp) < panAmpStep) pan = newPan;
}
}
/**
* Returns the period of the waveform (the inverse of the frequency).
*
* @return the period of the waveform
*/
public final float period()
{
return 1 / freq;
}
/**
* Returns the value of the waveform at <code>step</code>. To take
* advantage of all of the work that <code>Oscillator</code> does, you can
* create your own periodic waveforms by extending <code>Oscillator</code>
* and implementing this function. All of the oscillators included with Minim
* were created in this way.
*
* @param step
* an offset from the beginning of the waveform's period
* @return the value of the waveform at <code>step</code>
*/
protected abstract float value(float step);
}