/**
* Copyright (c) 2005 - Bob Lang (http://www.cems.uwe.ac.uk/~lrlang/)
*
* http://www.frinika.com
*
* This file is part of Frinika.
*
* Frinika is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* Frinika 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with Frinika; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.frinika.contrib.boblang;
import com.frinika.synth.Oscillator;
/**
Bezier Wave 3.2
<p>An object of this class is a synthesizer for a single note played on
the keyboard.
@author Bob Lang
@version 7 April 2003
*/
public class BezierSynth {
// Constants imported from other classes
public static final int
MAX_AMPLITUDE = BezierParamsV3_5.MAX_AMPLITUDE,
MAX_PITCH = BezierParamsV3_5.MAX_PITCH,
HIGH_PITCH = BezierParamsV3_5.HIGH_PITCH,
LOW_PITCH = BezierParamsV3_5.LOW_PITCH,
AMP_STEADY = BezierParamsV3_5.AMP_STEADY,
AMP_RISE_FALL = BezierParamsV3_5.AMP_RISE_FALL,
AMP_RISING = BezierParamsV3_5.AMP_RISING,
AMP_FALLING = BezierParamsV3_5.AMP_FALLING;
// The X and Y centre points are measured in absolute "pixel" positions.
private int
x2centre, // X centre position of upper control point
y2centre, // Y centre position of upper control point
x3centre, // X centre position of lower control point
y3centre; // Y centre position of lower control point
// Amplitudes are measured in absolute "pixel" positions
private int
x2amplitude, // Max X amplitude of upper control point
y2amplitude, // Max Y amplitude of upper control point
x3amplitude, // Max X amplitude of lower control point
y3amplitude; // Max Y amplitude of lower control point
// Amplitude envelopes
private int
x2envType, // Upper X amplitude envelope type
y2envType, // Upper Y amplitude envelope type
x3envType, // Lower X amplitude envelope type
y3envType; // Lower Y amplitude envelope type
// Amplitude envelope times in millisecs
private int
x2envTime, // Upper X envelope time
y2envTime, // Upper Y envelope time
x3envTime, // Lower X envelope time
y3envTime; // Lower Y envelope time
// Angular velocities are measured in absolute degrees/second
private double
x2AngVel, // SHM X frequency of upper control point
y2AngVel, // SHM Y frequency of upper control point
x3AngVel, // SHM X frequency of lower control point
y3AngVel; // SHM Y frequency of lower control point
// Initial phase values, stored in degrees
private double
x2Phase, // X upper frequency phase offset
y2Phase, // Y upper frequency phase offset
x3Phase, // X lower frequency phase offset
y3Phase; // Y lower frequency phase offset
// Other useful values
private double
frequency; // Frequency of generated wave in Hz
private int
sampleRate, // Sample rate
wavelength; // Number of X points for whole wave
/**
Constructor to create a new Bezier wave of the specified frequency.
*/
public BezierSynth (int inMidiPitch, BezierParams inParams) {
// Working storage
double f;
// Save the sample rate
sampleRate = inParams.getSampleRate();
// Calculate the fundamental frequency and the wavelength of the note
frequency = Oscillator.getFrequency (inMidiPitch);
// Wavelength is limited to an integer number of samples
wavelength = inParams.wavelengthFromFrequency (frequency);
// Get actual frequency from integer wave length (not nominal)
frequency = inParams.frequencyFromWavelength (wavelength);
// **** Transfer each parameter, making any necessary scale changes ****
// Position and amplitude of the control points
x2centre = inParams.getUpperCentreX()*wavelength/100;
y2centre = inParams.getUpperCentreY()*MAX_AMPLITUDE/100;
x3centre = inParams.getLowerCentreX()*wavelength/100;
y3centre = inParams.getLowerCentreY()*MAX_AMPLITUDE/100;
x2amplitude = inParams.getUpperAmplX()*wavelength/100;
y2amplitude = inParams.getUpperAmplY()*MAX_AMPLITUDE/100;
x3amplitude = inParams.getLowerAmplX()*wavelength/100;
y3amplitude = inParams.getLowerAmplY()*MAX_AMPLITUDE/100;
x2envType = inParams.getUpperEnvX();
y2envType = inParams.getUpperEnvY();
x3envType = inParams.getLowerEnvX();
y3envType = inParams.getLowerEnvY();
x2envTime = inParams.getUpperXEnvTime();
y2envTime = inParams.getUpperYEnvTime();
x3envTime = inParams.getLowerXEnvTime();
y3envTime = inParams.getLowerYEnvTime();
// Calculate the angular velocities of each control point
f = frequency*inParams.getUpperRelFreqX() + inParams.getUpperAbsFreqX();
x2AngVel = 360.0*f;
f = frequency*inParams.getUpperRelFreqY() + inParams.getUpperAbsFreqY();
y2AngVel = 360.0*f;
f = frequency*inParams.getLowerRelFreqX() + inParams.getLowerAbsFreqX();
x3AngVel = 360.0*f;
f = frequency*inParams.getLowerRelFreqY() + inParams.getLowerAbsFreqY();
y3AngVel = 360.0*f;
// Save the initial phases (in degrees)
x2Phase = inParams.getUpperPhaseX();
y2Phase = inParams.getUpperPhaseY();
x3Phase = inParams.getLowerPhaseX();
y3Phase = inParams.getLowerPhaseY();
// printParams ();
} // BezierSynth ()
/**
Diagnostic print of the Bezier parameters
*/
public void printParams () {
System.out.println ("Freq= " + frequency + " wavelength=" + wavelength);
System.out.println ("upper centre = " + x2centre + "," + y2centre);
System.out.println ("lower centre = " + x3centre + "," + y3centre);
System.out.println ("upper ampl: x= " + x2amplitude + " y= " + y2amplitude);
System.out.println ("lower ampl: x= " + x3amplitude + " y= " + y3amplitude);
System.out.println ("upper avel: x= " + x2AngVel + " y=" + y2AngVel);
System.out.println ("lower avel: x= " + x3AngVel + " y=" + y3AngVel);
} // printParams ()
/**
Calculate the value of the sound sample with the given index number
*/
public final int getSample (int sampleNumber) {
// Four control points to define the Bezier curve
double x1, x2, x3, x4, y1, y2, y3, y4;
// Control terms for the Bezier curve and the "t" parameter
double ax, bx, cx, ay, by, cy, t;
// Actual time from the start of the waveform
double time = (double) sampleNumber/(double) sampleRate;
// Calculate the current amplitudes of the control points
double x2amp = getAmplitude (x2amplitude, x2envType, x2envTime, time);
double y2amp = getAmplitude (y2amplitude, y2envType, y2envTime, time);
double x3amp = getAmplitude (x3amplitude, x3envType, x3envTime, time);
double y3amp = getAmplitude (y3amplitude, y3envType, y3envTime, time);
// Calculate the x and y positions of the control points
// First control point is the start of the wave
x1 = 0.0;
y1 = 0.0;
// Fourth control point is the end of the wave
x4 = wavelength;
y4 = 0.0;
// Second (or upper) control point
x2 = x2centre
+ x2amp*WaveSupport.localCosine (x2AngVel*time + x2Phase);
y2 = y2centre + y2amp*WaveSupport.localSine (y2AngVel*time + y2Phase);
// Third (or lower) control point
x3 = x3centre
+ x3amp*WaveSupport.localCosine (x3AngVel*time + x3Phase);
y3 = y3centre + y3amp*WaveSupport.localSine (y3AngVel*time + y3Phase);
/*
System.out.println ("Sample=" + sampleNumber + " Time=" + time);
System.out.print (" Upper point = " + x2 + "," + y2);
System.out.println (" Lower point = " + x3 + "," + y3);
*/
// Calculate the control terms used in the cubic equations
cx = 3.0*(x2 - x1);
cy = 3.0*(y2 - y1);
bx = 3.0*(x3 - x2 - x2 + x1);
by = 3.0*(y3 - y2 - y2 + y1);
ax = x4 - 3.0*(x3 - x2) - x1;
ay = y4 - 3.0*(y3 - y2) - y1;
// Find the value of the "t" parameter corresponding to this sample number
t = findT (ax, bx, cx, x1, sampleNumber);
// Calculate and return the sample value
double sample = ay*t*t*t + by*t*t + cy*t + y1;
// System.out.println (sample);
return (int) sample;
} // getSample ()
/**
<P>Method to calculate the "t" parameter from the sample number.
This method uses Newton's iterative technique to find the value
of "t" which corresponds to the desired Bezier x value.
<P>The method uses Newton's algorithm and in cases where the wave
curves back on itself, this algorithm may either loop indefinitely
of find a value of t outside its valid range of 0..1. In such cases,
the method returns an arbitrary value of zero.
*/
private double findT (double a,
double b,
double c,
double x1,
int sampleNumber)
{
// Local variables
int maxLoops = 10;
double t, d, x;
int loopCount = 0;
// Calculate index within the current wave cycle
int index = sampleNumber%wavelength;
// First approximation to the t parameter
t = (double) index/wavelength;
// Newton's iteration method for successive approximation
do {
x = a*t*t*t + b*t*t + c*t + x1 - index;
d = 3*a*t*t + 2*b*t + c;
t = t - x/d;
loopCount++;
} while (Math.abs (x) > 0.0001 && loopCount < maxLoops);
// Fudge a zero return value if no true value found
if (loopCount >= maxLoops || t < 0.0 || t > 1.0) {
t = 0.0;
} // if
// Return the final approximated value of t
return t;
} // findT ()
/**
Find the amplitude of the control point from the time and type
*/
private double getAmplitude (double inAmplitude,
int inEnvType,
int inEnvTimeMillis,
double time)
{
// Create local storage
double factor = 1.0;
double timeValue = (double) inEnvTimeMillis/1000.0;
// Select appropriate processing for each envelope type
switch (inEnvType) {
case AMP_STEADY:
// Steady amplitude - factor is unity
factor = 1.0;
break;
case AMP_RISE_FALL:
// Rise and fall envelope
// Test for rising part..
if (time < timeValue) {
factor = time/timeValue;
}
// Test for falling part
else if (time < 2.0*timeValue) {
factor = 1.0 - (time - timeValue)/timeValue;
}
// Envelope finished
else {
factor = 0.0;
} // else
break;
case AMP_RISING:
// Rising envelope
// Test for rising part
if (time < timeValue) {
factor = time/timeValue;
}
// Else constant full amplitude
else {
factor = 1.0;
} // else
break;
case AMP_FALLING:
// Falling amplitude
// Test for falling part
if (time < timeValue) {
factor = 1.0 - time/timeValue;
}
// Else constant zero amplitude
else {
factor = 0.0;
} // else
break;
} // case
// Calculate the final amplitude
return inAmplitude*factor;
} // getAmplitude ()
} // BezierSynth