/* * Copyright 2006, United States Government as represented by the Administrator * for the National Aeronautics and Space Administration. No copyright is * claimed in the United States under Title 17, U.S. Code. All Other Rights * Reserved. */ package gov.nasa.ial.mde.sound; import javax.sound.sampled.AudioFormat; /** * A Multi-Waveform Generator. * * @author Dr. Robert Shelton * @version 1.0 * @since 1.0 */ public class MultiWave extends Generator { private int numFrequencies = 0; private int[] whichWaveForm; private float[] frequencies; private float[] amplitudes; private double[] phases; private double[] deltaPhases; private int numActive = 0; private boolean[] isActive; private float frameRate; /* parameters to support transient events such as ``dings'' */ private double transientDuration = 0.0; // transient duration in seconds private int transientNumFrames = 0; // length of transient in frames private int transientFrameCount = 0; private double lambda; // decay constant in inverse seconds private double chirpFactor; private float chirpFrequency = 4000f; // Hz private int chirpDuration = 150; // milliseconds private double dingFreq = 0.0; // fundamental frequency of ding private static double DECAY_MULTIPLE = 3.0; // number of factors of 1/e in ding interval /* parameter to support addition of white noise to indicate negative values */ private double noiseCoefficient = 0.0; // coefficient of white noise /* Settings for use in the case of variable waveforms */ private double[] defaultMixingCoefficients = { 1.0, 0.0, 0.0, 0.0 }; private double[][] mixingCoefficients; private double highCutoff = 300.0, midCutoff = 220.0, lowCutoff = 100.0; /** A constant with the value 2 * Pi. */ public static final double TWO_PI = 2.0 * Math.PI; /** * Constant indicating a pure sine wave. */ public static final int SINE = 0; /** * Constant indicating a sawtooth waveform. */ public static final int SAW = 1; /** * Constant indicating a triangular waveform. */ public static final int TRIANGLE = 2; /** * Constant indicating a square wave. */ public static final int SQUARE = 3; /** * Constant indicating a waveform whose shape varies with pitch. */ public static final int VARIABLE = 4; /** * The waveform cross correlations. * * <p>0,0:SINE x SINE * <p>1,0:SAW x SINE, 1,1:SAW x SAW * <p>2,0:TRIANGLE x SINE, 2,1:TRIANGLE x SAW, 2,2:TRIANGLE x TRIANGLE * <p>3,0:SQUARE x SINE, 3,1:SQUARE x SAW, 3,2:SQUARE x TRIANGLE, 3,3:SQUARE x SQUARE */ public static final float[][] CROSS_CORRELATIONS = { { 0.5f }, // SINE x SINE { (float)(1.0 / Math.PI), (float) (1.0 / 3.0) }, // SAW x SINE, SAW x SAW { (float)(4.0 / (Math.PI * Math.PI)), 0.25f, (float) (1.0 / 3.0) }, // TRIANGLE x SINE, // TRIANGLE x SAW, // TRIANGLE x TRIANGLE { (float)(2.0 / Math.PI), 0.5f, 0.5f, 1.0f } }; // SQUARE x SINE, // SQUARE x SAW, // SQUARE x TRIANGLE, // SQUARE x SQUARE private static final int SINE_TABLE_SIZE = 2500; private float[] sineTable = new float[MultiWave.SINE_TABLE_SIZE + 1]; /** * Creates an instance of <code>MultiWave</code> with the specified audio * format, amplitude, and number of waveforms. * * @param f the audio-format to use. * @param a the amplitude/volume level. * @param n the number of waveforms. */ public MultiWave(AudioFormat f, float a, int n) { super(f, a); frameRate = getFormat().getFrameRate(); amplitudes = new float[numFrequencies = n]; frequencies = new float[n]; whichWaveForm = new int[n]; phases = new double[n]; deltaPhases = new double[n]; isActive = new boolean[n]; mixingCoefficients = new double[n][MultiWave.VARIABLE]; for (int i = 0; i < n; i++) { isActive[i] = false; phases[i] = 0.0; deltaPhases[i] = 0.0; for (int j = MultiWave.SINE; j < MultiWave.VARIABLE; j++) { mixingCoefficients[i][j] = defaultMixingCoefficients[j]; } } // end for i initializeSineTable(); } // end MultiWave /** * Convince method for calculating the power for a given set of coefficients. * @param coefficients the waveform coefficients. * @return the power. */ public static float power(float[] coefficients) { float p = 0f; for (int i = MultiWave.SINE; i < MultiWave.VARIABLE; i++) { p += (MultiWave.CROSS_CORRELATIONS[i][i] * coefficients[i] * coefficients[i]); for (int j = MultiWave.SINE; j < i; j++) { p += (2.0f * MultiWave.CROSS_CORRELATIONS[i][j] * coefficients[i] * coefficients[j]); } } // end for i return p; } // end power /** * Set the noise coefficient for the waveform. * * @param noiseCoefficient the amount of noise to add to the waveform. */ public void setNoise(double noiseCoefficient) { this.noiseCoefficient = noiseCoefficient; } /** * Activates a voice with a given waveform with a default amplitude of 1 * * @param which The designation of which voice is to be activated * @param freq The desired frequency * @param wf Which waveform, one of MultiWave.SINE, MultiWave.SAW, * MultiWave.TRIANGLE, MultiWave.SQUARE, or MultiWave.VARIABLE */ public void activate(int which, float freq, int wf) { activate(1f, which, freq, wf); } // end activate /** * Activates a voice with a given waveform * * @param a The desired amplitude * @param which The designation of which voice is to be activated * @param freq The desired frequency * @param wf Which waveform, one of MultiWave.SINE, MultiWave.SAW, * MultiWave.TRIANGLE, MultiWave.SQUARE, or MultiWave.VARIABLE */ public void activate(float a, int which, float freq, int wf) { if (wf == MultiWave.VARIABLE) if (freq > highCutoff) { mixingCoefficients[which][MultiWave.SINE] = 1.0; mixingCoefficients[which][MultiWave.SAW] = mixingCoefficients[which][MultiWave.SQUARE] = mixingCoefficients[which][MultiWave.TRIANGLE] = 0.0; } else if (freq < lowCutoff) { mixingCoefficients[which][MultiWave.SAW] = 0.5; mixingCoefficients[which][MultiWave.TRIANGLE] = 1.0; mixingCoefficients[which][MultiWave.SINE] = mixingCoefficients[which][MultiWave.SQUARE] = 0.0; } else { mixingCoefficients[which][MultiWave.SINE] = (freq - lowCutoff)/(highCutoff - lowCutoff); mixingCoefficients[which][MultiWave.TRIANGLE] = 1.0 - mixingCoefficients[which][MultiWave.SINE]; if (freq < midCutoff) { mixingCoefficients[which][MultiWave.SAW] = 0.5*(midCutoff - freq)/(midCutoff - lowCutoff); } else mixingCoefficients[which][MultiWave.SAW] = 0.0; } if (!isActive[which]) { numActive++; isActive[which] = true; } // end if frequencies[which] = freq; amplitudes[which] = a; whichWaveForm[which] = wf; deltaPhases[which] = TWO_PI * freq / frameRate; } // end activate /** * Deactivate the specified waveform. * * @param which the waveform index to deactivate. */ public void deactivate(int which) { if (isActive[which]) { isActive[which] = false; numActive--; } // end if } // end deactivate /** * Deactivate all the waveforms. */ public void deactivateAll() { for (int i = 0; i < numFrequencies; i++) isActive[i] = false; numActive = 0; } // end deactivateAll /** * Returns the number of active waveforms. * * @return the number of active waveforms. */ public int getNumActive() { return numActive; } // end getNumActive /** * Sets the Ding frequency and duration. * * @param freq the frequency of the ding. * @param duration the duration of the ding in milliseconds. */ public void ding(float freq, int duration) { ding(freq, duration, 0.0); } // end ding /** * Sets the Ding frequency, duration, and chirp-factor. * * @param freq the frequency of the ding. * @param durationInMillisecs the duration of the ding in milliseconds. * @param cf the cirp factor. */ private void ding(float freq, int durationInMillisecs, double cf) { this.dingFreq = freq; this.chirpFactor = cf; transientFrameCount = transientNumFrames = (int) Math.rint(frameRate * (transientDuration = durationInMillisecs / 1000.0)); this.lambda = MultiWave.DECAY_MULTIPLE / transientDuration; } /** * Chirp going up. */ public void chirpUp() { ding(chirpFrequency, chirpDuration, 1.0 / 0.15); } // end chirpUp /** * Chirp doing down. */ public void chirpDown() { ding(chirpFrequency, chirpDuration, -1.0 / 0.15); } // end chirpDown /** * Returns the next sample in the waveform. * * @return the next sample in the waveform. */ public float nextFloat() { int i, n = 0; float r = 0.0f; for (i = 0; i < numFrequencies; i++) if (isActive[i]) { r += (amplitudes[i] * waveForm(phases[i], whichWaveForm[i], i)); phases[i] += deltaPhases[i]; while (phases[i] > TWO_PI) phases[i] -= TWO_PI; n++; } // end if if (transientFrameCount > 0) { double t = (transientNumFrames - transientFrameCount--) / frameRate; double u = TWO_PI * t * dingFreq * Math.pow(2.0, t * chirpFactor); while (u >= TWO_PI) u -= TWO_PI; n++; r += (Math.exp(-lambda * t) * waveForm(u, MultiWave.SQUARE, -1)); } if (noiseCoefficient != 0.0) { r += noiseCoefficient * (2.0 * Math.random() - 1.0); return (n > 0) ? r / (float) Math.sqrt(n) : r; } return (n > 0) ? r / (float) Math.sqrt(n) : 0f; } // end nextFloat private void initializeSineTable() { double d = 2.0 * Math.PI / MultiWave.SINE_TABLE_SIZE; double x = 0.0; for (int i = 0; i < MultiWave.SINE_TABLE_SIZE; i++) { sineTable[i] = (float) Math.sin(x); x += d; } // end for i sineTable[MultiWave.SINE_TABLE_SIZE] = 0f; } // end initializeSineTable private float sine(double x) { double w = 0.5 * x / Math.PI; double f = w - Math.floor(w); double y = f * MultiWave.SINE_TABLE_SIZE; double n = Math.floor(y); double beta = y - n, alpha = 1.0 - beta; return (float) (alpha * sineTable[(int) n] + beta * sineTable[1 + (int) n]); } // end sine /** * Returns the value of the specified waveform at the specified x value and * coefficient index. * * @param x the x-value of the waveform. * @param wf Which waveform, one of MultiWave.SINE, MultiWave.SAW, * MultiWave.TRIANGLE, MultiWave.SQUARE, or MultiWave.VARIABLE * @param fNum the index to the coefficients values to use. * @return the waveform value given x. * @see gov.nasa.ial.mde.sound.MultiWave#SINE * @see gov.nasa.ial.mde.sound.MultiWave#SAW * @see gov.nasa.ial.mde.sound.MultiWave#TRIANGLE * @see gov.nasa.ial.mde.sound.MultiWave#SQUARE * @see gov.nasa.ial.mde.sound.MultiWave#VARIABLE */ public float waveForm(double x, int wf, int fNum) { switch (wf) { case MultiWave.SINE: // return (float)Math.sin(x); return sine(x); case MultiWave.SAW: return (float) (x / Math.PI - 1f); case MultiWave.TRIANGLE: return (float) (1.0 - 2.0 * Math.abs(x - Math.PI) / Math.PI); case MultiWave.SQUARE: return (x < Math.PI) ? 1f : -1f; case MultiWave.VARIABLE: { double r = 0.0, s = 0.0; for (int i = MultiWave.SINE; i < MultiWave.VARIABLE; i++) { r += mixingCoefficients[fNum][i] * waveForm(x, i, -1); s += (mixingCoefficients[fNum][i] * mixingCoefficients[fNum][i]); } return (float) (r / Math.sqrt(s)); } default: throw new RuntimeException(); } // end switch } // end waveForm /** * Returns the chirp duration. * * @return the chirp duration in milliseconds. */ public int getChirpDuration() { return chirpDuration; } /** * Sets the chirp duration. * * @param chirpDuration the chirp duration in milliseconds. */ public void setChirpDuration(int chirpDuration) { this.chirpDuration = chirpDuration; } /** * Returns the chirp frequency. * * @return the chirp frequency. */ public float getChirpFrequency() { return chirpFrequency; } /** * Sets the chirp frequency. * * @param chirpFrequency the chirp frequency. */ public void setChirpFrequency(float chirpFrequency) { this.chirpFrequency = chirpFrequency; } } // end class MultiWave