/**
* 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 uwejava.*;
import java.awt.Color;
/**
Support methods for creating PCM wave forms
*/
public class WaveSupport {
// Propagation options for extending a waveform
public static final int
PROP_COPY = 1, // Copy the wave form
PROP_GLIDE = 2, // Glide reflect
PROP_ROTATE = 3; // Rotate
// Maximum amplitude in a rescaled waveform
public static final double
MAX_AMPLITUDE = 32000.0;
// Maximum graph handle number
public static final int
MAX_GRAPH_HANDLE = 8;
// Set of static graphs - ready for reuse
private static Graph []
graphList = new Graph [MAX_GRAPH_HANDLE];
/**
Rescale the wave form so it uses the dynamic range specified
by the given amplitude.
@param waveform - array containing the waveform to be rescaled
@param amplitude - amplitude of the rescaled waveform
*/
public static int [] rescale (int [] waveform, double amplitude) {
// Initialisation
int
numberOfSamples = waveform.length,
largestValue = 0,
smallestValue = 0,
greatestValue = 0;
// Create the empty buffer of scaled waveform
int [] scaledWave = new int [numberOfSamples];
// Scan the sample to find the greatest absolute value
for (int i = 0; i < numberOfSamples; i++) {
int sample = waveform [i];
if (sample > largestValue) {
largestValue = sample;
} // if
if (sample < smallestValue) {
smallestValue = sample;
} // if
// Find the greatest absolute value
greatestValue = largestValue;
if (smallestValue < 0) {
if (-smallestValue > largestValue) {
greatestValue = -smallestValue;
} // if
} // if
} // for
// Calculate the required scaling factor
double scaleFactor = amplitude/greatestValue;
// Rescale the data
for (int i = 0; i < numberOfSamples; i++) {
scaledWave [i] = (int) (waveform [i]*scaleFactor + 0.5);
} // for
// Return the final result
return scaledWave;
} // rescale (int [] waveform, double amplitude)
/**
Rescale the wave form so it uses (most of) the full dynamic range.
@param waveform - array containing the waveform to be rescaled
*/
public static int [] rescale (int [] waveform) {
return rescale (waveform, MAX_AMPLITUDE);
} // rescale (int [])
/**
Method to propagate a waveform to fill a complete array.
It uses the possible propagation options.
@param halfWave - an array containing a half wave
@param desiredSize - the number of samples in the output array
@param option - the propagation option
*/
public static int [] propagate (int [] halfWave,
int desiredSize,
int option)
{
// Create empty output array
int [] outArray = new int [desiredSize];
// Initialisation
int inLength = halfWave.length;
int outPointer = 0;
int inPointer = 0;
// Copy the first block of data
System.arraycopy (halfWave, inPointer, outArray, outPointer, inLength);
outPointer += inLength;
// Copy the second half of the waveform according to the copy option
switch (option) {
case PROP_COPY:
// Just copy the first block again
System.arraycopy (halfWave,
inPointer,
outArray,
outPointer,
inLength);
outPointer += inLength;
break;
case PROP_GLIDE:
// Glide reflection of first block
for (int i = 0; i < inLength; i++) {
outArray [inLength + i] = -halfWave [i];
} // for
outPointer += inLength;
break;
case PROP_ROTATE:
// Invert address and negate the first block
for (int i = 0; i < inLength; i++) {
outArray [inLength + i] = -halfWave [inLength - i - 1];
} // for
outPointer += inLength;
break;
} // switch
// Continue repetitions of full wave. Note that the initial input
// was only a half wave, hence the multiplications by 2
while (outPointer <= desiredSize - inLength*2) {
System.arraycopy (outArray,
inPointer,
outArray,
outPointer,
inLength*2);
outPointer += inLength*2;
} // while
// Fill in the remaining elements with a partial repetition
int j = 0;
for (int i = outPointer; i < desiredSize; i++) {
outArray [i] = outArray [j];
j++;
} // for
// Return the final array
return outArray;
} // propagate ()
/**
Method to apply a rectangular filter to a half wave and
return the resulting filtered output. The method needs
to know how the half wave will be propagated, hence the
option parameter
*/
public static int [] filter (int [] halfWave,
int filterWidth,
int option)
{
// Ensure minimum filter width
if (filterWidth < 1) {
filterWidth = 1;
} // if
// Calculate the lengths of the different arrays
int inLength = halfWave.length;
int workLength = inLength*2*3;
int resultLength = inLength*2;
// Put three full waves in the working array
int [] work = propagate (halfWave, workLength, option);
// Create the output array
int [] result = new int [resultLength];
// Calculate the sum for the first result
int sum = 0;
int firstIndex = inLength*2 - filterWidth/2;
int lastIndex = firstIndex + filterWidth - 1;
for (int i = firstIndex; i <= lastIndex; i++) {
sum += work [i];
} // for i
// Save the sum in the result array
result [0] = sum/filterWidth;
// Calculate the sum across a full wave
for (int i = 1; i < resultLength; i++) {
// Remove the first index from the sum
sum = sum - work [firstIndex];
firstIndex++;
// Increment and add the new last index to the sum
lastIndex++;
sum = sum + work [lastIndex];
// Store the average in the result array
result [i] = sum/filterWidth;
} // for
return result;
} // filter ()
/**
Method to apply an increasing filter across a waveform.
@param halfWave - an array containing a half wave
@param filterWidth - the initial filterWidth
@param filterIncr - number of samples at which filter increments
@param desiredSize - the number of samples in the output array
@param option - the propagation option
*/
public static int [] increasingFilter (int [] halfWave,
int filterWidth,
int filterIncr,
int desiredSize,
int option)
{
// Calculate the lengths of the different arrays
int inLength = halfWave.length;
int fullWaveLength = inLength*2;
int workLength = fullWaveLength*3;
int resultLength = desiredSize;
// Put three full waves in the working array
int [] work = propagate (halfWave, workLength, option);
//$ plotGraph (0, "Working array", "", "x", "y", work, workLength);
// Create the output array
int [] result = new int [resultLength];
// Calculate the sum for the first result
int sum = 0;
int firstIndex = fullWaveLength - filterWidth/2;
int lastIndex = firstIndex + filterWidth - 1;
for (int i = firstIndex; i <= lastIndex; i++) {
sum += work [i];
} // for i
//$ System.out.println (firstIndex + " " + lastIndex + " " + sum);
// Save the sum in the result array
result [0] = sum/filterWidth;
// Calculate the sum across a full wave
int incrCount = 0;
int workFilterWidth = filterWidth;
// Calculate the sum
for (int i = 1; i < resultLength; i++) {
// Is it necessary to increase the filter width?
incrCount++;
if (incrCount >= filterIncr) {
//$ System.out.println ("Incrementing filter");
incrCount = 0;
workFilterWidth++;
} // if
else {
// Remove the first index from the sum
//$ System.out.println ("Subtracting " + firstIndex + "(" + work [firstIndex] + ")");
sum = sum - work [firstIndex];
firstIndex++;
} // else
// Increment and add the new last index to the sum
lastIndex++;
sum = sum + work [lastIndex];
//$ System.out.println ("Adding " + lastIndex + "(" + work [lastIndex] + ")");
// Store the average in the result array
//$ System.out.println ("Storing result in " + i + "(" + sum + ")[" + workFilterWidth + "]");
result [i] = sum/workFilterWidth;
// Ensure that indexes wrap round correctly
if (firstIndex >= fullWaveLength) {
//$ System.out.print ("first " + firstIndex + "(" + work [firstIndex] + ") => ");
firstIndex = firstIndex - fullWaveLength;
//$ System.out.println (firstIndex + "(" + work [firstIndex] + ")");
} // if
if (lastIndex > fullWaveLength) {
//$ System.out.print ("last " + lastIndex + "(" + work [lastIndex] + ") => ");
lastIndex = lastIndex - fullWaveLength;
//$ System.out.println (lastIndex + "(" + work [lastIndex] + ")");
} // if
} // for
return result;
} // increasingFilter ()
/**
Method to change the wavelength waveform using linear interpolation.
The input wave is assumed to be a single cycle.
*/
public static int [] changeWaveLength (int [] inWave,
int newWaveLength)
{
// Working storage
int base, step, baseIndex;
double scaledIndex, offset;
// Calculate the scaling factor
int inLength = inWave.length;
/*
plotGraph (6, "Basic and pitch shifted grains",
"Input Grain", " ", " ",
inWave, 0, inLength);
*/
// Create the output array
int [] outWave = new int [newWaveLength];
// Fill up every point in the output array
for (int i=0; i<newWaveLength; i++) {
// Scaling operation
scaledIndex =
(double) i * (double) inLength / (double) newWaveLength;
baseIndex = (int) scaledIndex;
offset = scaledIndex - baseIndex;
// Calculate the output
base = inWave [baseIndex];
step = inWave [(baseIndex+1)%inLength] - base;
outWave [i] = (int) (base + offset * step);
/*
System.out.print ("i=" + i);
System.out.print (" scaledIndex=" + scaledIndex);
System.out.print (" baseIndex=" + baseIndex);
System.out.print (" offset=" + offset);
System.out.print (" base=" + base);
System.out.print (" step=" + step);
System.out.println ();
*/
} // for
//$ addGraph (6, Graph.BLUE, outWave, outWave.length);
// Return the final result
return outWave;
} // changeWaveLength ()
/**
Method to plot an array as a graph.
@param handle - graph window to create or re-use
@param inTitle - The overall graph title
@param inXTitle - X axis title
@param inYTitle - Y axis title
@param inArray - array to be plotted
@param inElements - number of elements to plot
*/
public synchronized static void plotGraph (int handle,
String inTitle,
String inSubTitle,
String inXTitle,
String inYTitle,
int [] inArray,
int inElements)
{
// Calculate the number of elements to plot
int plotCount = inElements;
if (plotCount > inArray.length) {
plotCount = inArray.length;
} // if
// Ensure that we have a valid handle
if (handle < MAX_GRAPH_HANDLE) {
// Are we re-using the graph again?
if (graphList [handle] != null) {
// Clear out existing graph for re-use
graphList [handle].clearGraph (inTitle, inXTitle, inYTitle);
}
else {
// Create a new graph
graphList [handle] = new Graph (inTitle, inXTitle, inYTitle);
} // else
//$ temp test
graphList [handle].setSubTitle (inSubTitle);
// Set the plot colour to red
graphList [handle].setColour (Graph.RED);
// Plot all the points onto the graph
for (int i = 0; i < plotCount; i++) {
graphList [handle].add (i, inArray [i]);
} // for
// Show the graph - don't exit when closed
graphList [handle].showGraph (false);
} // valid handle number
} // plotGraph ()
/**
Method to plot a section of an array as a graph.
@param handle - graph window to create or re-use
@param inTitle - The overall graph title
@param inXTitle - X axis title
@param inYTitle - Y axis title
@param inArray - array to be plotted
@param inFirst - first element to plot
@param inLast - last element to plot
*/
public synchronized static void plotGraph (int handle,
String inTitle,
String inSubTitle,
String inXTitle,
String inYTitle,
int [] inArray,
int inFirst,
int inLast)
{
// Check that parameters are valid
if (inLast > inArray.length) {
inLast = inArray.length - 1;
} // if
if (inFirst > inLast) {
inFirst = inLast - 24;
} // if
if (inFirst < 0) {
inFirst = 0;
} // if
// Ensure that we have a valid handle
if (handle < MAX_GRAPH_HANDLE) {
// Are we re-using the graph again?
if (graphList [handle] != null) {
// Clear out existing graph for re-use
graphList [handle].clearGraph (inTitle, inXTitle, inYTitle);
}
else {
// Create a new graph
graphList [handle] = new Graph (inTitle, inXTitle, inYTitle);
} // else
//$ temp test
graphList [handle].setSubTitle (inSubTitle);
// Set the plot colour to red
graphList [handle].setColour (Graph.RED);
// Plot all the points onto the graph
for (int i = inFirst; i < inLast; i++) {
graphList [handle].add (i-inFirst, inArray [i]);
} // for
// Show the graph - don't exit when closed
graphList [handle].showGraph (false);
} // valid handle number
} // plotGraph ()
/**
Method to add a further plot to an existing graph.
@param handle - graph window to create or re-use
@param inArray - array to be plotted
@param inElements - number of elements to plot
*/
public synchronized static void addGraph (int handle,
Color inColour,
int [] inArray,
int inElements)
{
// Calculate the number of elements to plot
int plotCount = inElements;
if (plotCount > inArray.length) {
plotCount = inArray.length;
} // if
// Ensure that we have a valid handle
if (handle < MAX_GRAPH_HANDLE) {
// Are we re-using the graph again?
if (graphList [handle] != null) {
// Clear out existing graph for re-use
graphList [handle].nextGraph ();
}
else {
// Create a new graph
graphList [handle] = new Graph ("Untitled Graph", "x", "y");
} // else
// Set the plot colour
graphList [handle].setColour (inColour);
// Plot all the points onto the graph
for (int i = 0; i < plotCount; i++) {
graphList [handle].add (i, inArray [i]);
} // for
// Show the graph - don't exit when closed
graphList [handle].showGraph (false);
} // valid handle number
} // addGraph ()
/**
Method to add an array subset as a further plot to an
existing graph.
@param handle - graph window to create or re-use
@param inArray - array to be plotted
@param inElements - number of elements to plot
*/
public synchronized static void addGraph (int handle,
Color inColour,
int [] inArray,
int inFirst,
int inLast)
{
// Check that parameters are valid
if (inLast > inArray.length) {
inLast = inArray.length - 1;
} // if
if (inFirst > inLast) {
inFirst = inLast - 24;
} // if
if (inFirst < 0) {
inFirst = 0;
} // if
// Ensure that we have a valid handle
if (handle < MAX_GRAPH_HANDLE) {
// Are we re-using the graph again?
if (graphList [handle] != null) {
// Clear out existing graph for re-use
graphList [handle].nextGraph ();
}
else {
// Create a new graph
graphList [handle] = new Graph ("Untitled Graph", "x", "y");
} // else
// Set the plot colour to green
graphList [handle].setColour (inColour);
// Plot all the points onto the graph
for (int i = inFirst; i < inLast; i++) {
graphList [handle].add (i-inFirst, inArray [i]);
} // for
// Show the graph - don't exit when closed
graphList [handle].showGraph (false);
} // valid handle number
} // addGraph ()
/**
Method to plot the data in as a FFT. The x co-ordinates are
frequency bins which are converted to the appropriate frequencies
before being plotted
@param handle - graph window to create or re-use
@param inArray - output from FFT
@param fourierSamples - number of samples in FFT
@param sampleRate - sample rate in samples/sec
@param inElements - number of elements to plot
*/
public synchronized static void plotFourier (int handle,
double [] inArray,
int fourierSamples,
int sampleRate,
int inElements)
{
String
graphTitle = "Fourier Transform",
subTitle = " ",
xTitle = "Hz",
yTitle = "Power";
// Working variables
double freq, logOfHeight;
// Calculate the number of elements to plot
int plotCount = inElements;
if (plotCount > inArray.length) {
plotCount = inArray.length;
} // if
// Ensure that we have a valid handle
if (handle < MAX_GRAPH_HANDLE) {
// Are we re-using the graph again?
if (graphList [handle] != null) {
// Clear out existing graph for re-use
graphList [handle].clearGraph (graphTitle, xTitle, yTitle);
}
else {
// Create a new graph
graphList [handle] = new Graph (graphTitle, xTitle, yTitle);
} // else
graphList [handle].setSubTitle (subTitle);
// Set the plot colour to blue
graphList [handle].setColour (Graph.BLUE);
// Plot all the points onto the graph
for (int i = 0; i < plotCount; i++) {
freq = Fourier.frequencyFromBin (i, fourierSamples, sampleRate);
graphList [handle].add (freq, inArray [i]);
} // for
// Show the graph - don't exit when closed
graphList [handle].showGraph (false);
} // valid handle number
} // plotFourier ()
/**
Static local sine and cosine tables
*/
private static double [] sineTable = new double [360];
private static double [] cosineTable = new double [360];
/**
Local version of sine method (with parameter in degrees)
*/
public final static double localSine (double degrees) {
int idegrees = (int) degrees + 360;
idegrees = idegrees % 360;
return sineTable [idegrees];
} // localSine ()
/**
Local version of cosine method (with parameter in degrees)
*/
public final static double localCosine (double degrees) {
int idegrees = (int) degrees + 360;
idegrees = idegrees % 360;
return cosineTable [idegrees];
} // localCosine ()
/**
Static initialiser for the local sine and cosine lookup tables
*/
static {
for (int i=0; i<360; i++) {
sineTable [i] = Math.sin (2.0*Math.PI*i/360.0);
cosineTable [i] = Math.sin (2.0*Math.PI*i/360.0);
} // for
} // static
} // WaveSupport