package jass.generators; import jass.engine.*; import java.io.*; import java.util.*; /** Vibration model of object, capable of playing sound. @author Kees van den Doel (kvdoel@cs.ubc.ca) */ public class ModalObject extends InOut { /** Sampling rate in Hertz. */ public float srate; /** Modal data. */ public ModalModel modalModel; /** Associate a Source with a ModalObject.Contact. */ private Hashtable<Source,Contact> source_contact = new Hashtable<Source,Contact>(); /** Represents contact with location in barycentric coordinates. */ public class Contact { /** State of contact */ public boolean isOn = false; /** Current barycentric location points. */ public int p1=0,p2=0,p3=0; /** Current barycentric coordinates of location. */ public float b1=1,b2=0,b3=0; /** Reson filter gain vector. */ public float[] ampR = null; /** Constructor, allocates nmodes. @param af audio force of Contact. @param nf number of modes. */ public Contact() { ampR = new float[modalModel.nf]; } /** Turn on. */ public void start() { isOn = true; } /** Turn off. */ public void stop() { isOn = false; } /** Compute the gain coefficients from the modal model parameters at point p, given inside triangle of point p1,p2,p3, with barycentric coordinated b1,b2,b3 @param p1 location index 1. @param p2 location index 2. @param p3 location index 3. @param b1 barycentric coordinate 1. @param b2 barycentric coordinate 2. @param b3 barycentric coordinate 3. */ public void setLocation(int p1, int p2, int p3, float b1, float b2, float b3) { this.p1 = p1; this.p2 = p2; this.p3 = p3; this.b1 = b1; this.b2 = b2; this.b3 = b3; computeLocation(); } /** Compute gains of contact */ public void computeLocation() { for(int i=0;i<modalModel.nf;i++) { ampR[i] = modalModel.ascale * c_i[i] * (b1* modalModel.a[p1][i] + b2* modalModel.a[p2][i] + b3* modalModel.a[p3][i]); } } }; /** Used a a temp buffer in inner loop to store array of gains for all contacts. */ private float[] contactAmpTemp; /** Location vector of Contact objects. */ Vector<Contact> contactVector = new Vector<Contact>(); /** State of filters. */ private float [] yt_1, yt_2; /** The transfer function of a reson filter is H(z) = 1/(1-twoRCosTheta/z + R2/z*z). */ private float[] R2; /** The transfer function of a reson filter is H(z) = 1/(1-twoRCosTheta/z + R2/z*z). */ private float[] twoRCosTheta; /** Cached values. */ private float[] c_i; /** Add a Source. Implements Sink interface. Must take into account that may add source while running, so have to set time. @param s Source to add. */ public synchronized Object addSource(Source s) throws SinkIsFullException { sourceContainer.addElement(s); s.setTime(getTime()); Contact c = new Contact(); contactVector.addElement(c); c.computeLocation(); source_contact.put(s,c); // allocate temp storage associated with this source contactAmpTemp = new float[contactVector.size()]; return c; } /** Remove a Source. Implements Sink interface. @param s Source to remove. */ public synchronized void removeSource(Source s) { sourceContainer.removeElement(s); //System.out.println("ncontacts="+contactVector.size()); Contact c = source_contact.get(s); source_contact.remove(s); //System.out.println("removeSource("+s+"), contact="+c); contactVector.removeElement(c); //System.out.println("ncontacts="+contactVector.size()); } /** Scale dampings. @param d damping scale. */ public void setDamping(float dscale) { modalModel.dscale = dscale; computeFilter(); } /** Scale frequencies. @param fscale frequency scale. */ public void setFrequencyScale(float fscale) { modalModel.fscale = fscale; computeFilter(); } /** Create and initialize, but don't set any modal parameters. @param srate sampling rate in Hertz. @param nf number of modes. @param np number of locations. @param bufferSize Buffer size used for real-time rendering. */ public ModalObject(float srate,int nf,int np,int bufferSize) { super(bufferSize); this.srate = srate; modalModel = new ModalModel(nf,np); allocate(nf,np); } /** Create and initialize with provided modal data. @param m modal model to load. @param srate sampling rate in Hertz. @param bufferSize Buffer size used for real-time rendering. */ public ModalObject(ModalModel m,float srate,int bufferSize) { super(bufferSize); this.srate = srate; modalModel = m; allocate(modalModel.nf,modalModel.np); computeFilter(); } /** Reduce number of modes used. @param nf number of modes to use. */ public void setNf(int nf) { if(nf < modalModel.nf) { modalModel.nfUsed = nf; } } /** Allocate data. @param nf number of modes. @param np number of locations. */ private void allocate(int nf,int np) { R2 = new float[nf]; twoRCosTheta = new float[nf]; yt_1 = new float[nf]; yt_2 = new float[nf]; c_i = new float[nf]; clearHistory(); } /** Compute the filter coefficients used for real-time rendering from the modal model parameters. */ public void computeFilter() { computeResonCoeff(); for(int i=0;i<contactVector.size();i++ ){ contactVector.elementAt(i).computeLocation(); } } /** Compute the reson coefficients from the modal model parameters. Cache values for location computation. */ public void computeResonCoeff() { for(int i=0;i<modalModel.nf;i++) { float tmp_r = (float)(Math.exp(-modalModel.dscale*modalModel.d[i]/srate)); R2[i] = tmp_r*tmp_r; twoRCosTheta[i] = (float)(2*Math.cos(2*Math.PI*modalModel.fscale*modalModel.f[i]/srate)*tmp_r); c_i[i] = (float)(Math.sin(2*Math.PI*modalModel.fscale*modalModel.f[i]/srate)*tmp_r); } } /** Set state to non-vibrating. */ public void clearHistory() { for(int i=0;i<modalModel.nf;i++) { yt_1[i] = yt_2[i] = 0; } } /** Compute the next buffer and store in member float[] buf. */ protected synchronized void computeBuffer() { try { computeModalFilterBank(this.buf); } catch(BufferNotAvailableException e) { System.out.println(e); } } /** Apply external force[] and compute response through bank of modal filters. @param output provided output buffer. */ private void computeModalFilterBank(float[] output) throws BufferNotAvailableException { int bufsz = getBufferSize(); for(int k=0;k<bufsz;k++) { output[k] = 0; } int nf = modalModel.nfUsed; int ncontacts = contactVector.size(); for(int i=0;i<nf;i++) { float tmp_twoRCosTheta = twoRCosTheta[i]; float tmp_R2 = R2[i]; float tmp_yt_1 = yt_1[i]; float tmp_yt_2 = yt_2[i]; for(int ic=0;ic<ncontacts;ic++) { // move array access out of inner loop contactAmpTemp[ic] = contactVector.elementAt(ic).ampR[i]; } for(int k=0;k<bufsz;k++) { float ynew = tmp_twoRCosTheta * tmp_yt_1 - tmp_R2 * tmp_yt_2; // Just this loop (empty) brings 668 (one contact) down to 544 for(int ic=0;ic<ncontacts;ic++) { // stuff in this loop slows things a factor of 3, to 200, with everything // Just this line by itself gives 266 Contact contact = contactVector.elementAt(ic); // Just this stuff gives 210 if(contact.isOn) { // BUG: This assumes scrBuffers has Sources in same order as Contacts. Probably OK but check! ynew += contactAmpTemp[ic] * srcBuffers[ic][k]; } // try just array mult and comment out the above gives 219 => arrays are very very bad! //ynew += contactAmpTemp[ic] * contactAmpTemp[ic]; //ynew += foo[ic] * foo[ic]; // no, this makes no difference with above line! } tmp_yt_2 = tmp_yt_1; tmp_yt_1 = ynew; output[k] += ynew; } yt_1[i] = tmp_yt_1; yt_2[i] = tmp_yt_2; } } }