package jass.generators; import jass.engine.*; import java.io.*; /** Vibration model of object with 1 mode Changes in freq damping and gains are linearly changed over one buffer rather than abruptly @author Kees van den Doel (kvdoel@cs.ubc.ca) */ public class SingleMode extends InOut { /** Sampling rate in Hertz. */ public float srate; /** xi factor for rising bubbles as explained in paper */ protected float xi = 0.1f; /* f = f_base(1 + s *t) xi = 0.1 s = xi * d s = (srate/bufferSize)/riseFactor riseFactor = (srate/bufferSize)/(xi*d); */ private boolean isRising = true; private int k_rise = 0; private float rise_factor;// (float)(10000/bufferSize); /** Cutoff excitation pulse strength above which a bubble is considered rising */ protected float cutoffRiseExcitation = 1.f; /** The transfer function of a reson filter is H(z) = 1/(1-twoRCosTheta/z + R2/z*z). */ protected float twoRCosTheta; protected float R2; /** Reson filter gain. */ protected float ampR; /** Cached values. */ protected float c_i; /** modal parameters */ protected float f=100,f_rise = 100, f_base = 100,d=1,a=1; /** Temp storage */ protected float[] tmpBuf = null; /** State of filter */ protected float yt_1, yt_2; // 0.001 works to prevent strange underflow (??) bug static final float eps = 0.001f; /** Constructor for derived classes to call super @param bufferSize Buffer size used for real-time rendering. */ public SingleMode(int bufferSize) { super(bufferSize); } /** Create and initialize, but don't set any modal parameters. @param srate sampling rate in Hertz. @param bufferSize Buffer size used for real-time rendering. */ public SingleMode(float srate,int bufferSize) { super(bufferSize); this.srate = srate; tmpBuf = new float[bufferSize]; } /** Create and initialize with provided modal data. @param f freq. @param d damping @param a gain @param srate sampling rate in Hertz. @param bufferSize Buffer size used for real-time rendering. */ public SingleMode(float f,float d,float a,float srate,int bufferSize) { super(bufferSize); this.srate = srate; this.f = f; this.f_base = f; this.d=d; this.a=a; computeResonCoeff(); computeRiseFactor(); tmpBuf = new float[bufferSize]; } private void computeRiseFactor() { rise_factor = (float)((srate/bufferSize)/(xi*d)); } /** Set xi value for rising bubbles @param x xi value (0.1 is normal) */ public void setXi(float x) { this.xi = x; computeRiseFactor(); } /** Set xi value for rising bubbles @param x excitation value above which bubble is considered rising */ public void setRiseCutoffExcitation(float x) { this.cutoffRiseExcitation = x; } /** Add a Source. Overrides Sink interface implementation from InOut. Allow only one Source. @param s Source to add. */ public Object addSource(Source s) throws SinkIsFullException { if(sourceContainer.size() > 0) { throw new SinkIsFullException(); } else { sourceContainer.addElement(s); } return null; } /** Compute the reson coefficients from the modal model parameters. Cache values used in {@link #setLocation}. */ public void computeResonCoeff() { float tmp_r = (float)(Math.exp(-this.d/srate)); R2 = tmp_r*tmp_r; twoRCosTheta = (float)(2*Math.cos(2*Math.PI*this.f/srate)*tmp_r); c_i = (float)(Math.sin(2*Math.PI*this.f/srate)*tmp_r); ampR = c_i * this.a; } /** Compute the next buffer and store in member float[] buf. */ protected void computeBuffer() { computeModalFilterBank(this.buf, srcBuffers[0], getBufferSize()); } /** Set damping @param d damping scale. */ public void setDamping(float d) { this.d = d; computeRiseFactor(); computeResonCoeff(); } /** Set freq @param f freq. */ public void setFreq(float f) { this.f = f; this.f_base = f; computeResonCoeff(); } /** Set gain @param a gain */ public void setGain(float a) { this.a = a; computeResonCoeff(); } /** Apply external force[] and compute response through bank of modal filters. Interpolate filter parameters over the buffer. c[k]=k*(c_new-c_old)/nsamples + c_old @param output user provided output buffer. @param force input force. @param nsamples number of samples to compute. */ protected void computeModalFilterBank(float[] output, float[] force, int nsamples) { // filter parameters are: // twoRCosTheta[nf]; R2[nf]; ampR[nf] boolean isnul = true; float impulse = 0; for(int k=0;k<nsamples;k++) { output[k] = 0; if((impulse=Math.abs(force[k]))>=eps) { isnul = false; k_rise = 0; this.f = this.f_base; computeResonCoeff(); if(impulse>cutoffRiseExcitation) { isRising = true; } else { isRising = false; } } } if(isRising) { this.f = (float)(f_base*(1.+(++k_rise)/rise_factor)); computeResonCoeff(); } if(isnul) { if(Math.abs(yt_1) >= eps || Math.abs(yt_2) >= eps) { isnul = false; } } if(isnul) { return; } float tmp_yt_1 = yt_1; float tmp_yt_2 = yt_2; float tmp_twoRCosTheta = twoRCosTheta; float tmp_R2 = R2; float tmp_ampR = ampR; for(int k=0;k<nsamples;k++) { // optimize by taking */ out ofthe loop float ynew = tmp_twoRCosTheta*tmp_yt_1 - tmp_R2 * tmp_yt_2 + tmp_ampR * force[k]; tmp_yt_2 = tmp_yt_1; tmp_yt_1 = ynew; output[k] += ynew; } yt_1 = tmp_yt_1; yt_2 = tmp_yt_2; } }