// BarWaves.java (C) 2001 by Paul Falstad, www.falstad.com import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.DataLine; import javax.sound.sampled.SourceDataLine; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.util.Random; import java.util.Vector; class BarWavesCanvas extends Canvas { BarWavesFrame pg; BarWavesCanvas(BarWavesFrame p) { pg = p; } public Dimension getPreferredSize() { return new Dimension(300, 400); } public void update(Graphics g) { pg.updateBarWaves(g); } public void paint(Graphics g) { pg.updateBarWaves(g); } }; class BarWavesLayout implements LayoutManager { public BarWavesLayout() { } public void addLayoutComponent(String name, Component c) { } public void removeLayoutComponent(Component c) { } public Dimension preferredLayoutSize(Container target) { return new Dimension(500, 500); } public Dimension minimumLayoutSize(Container target) { return new Dimension(100, 100); } public void layoutContainer(Container target) { int barwidth = 0; int i; for (i = 1; i < target.getComponentCount(); i++) { Component m = target.getComponent(i); if (m.isVisible()) { Dimension d = m.getPreferredSize(); if (d.width > barwidth) { barwidth = d.width; } } } Insets insets = target.insets(); int targetw = target.size().width - insets.left - insets.right; int cw = targetw - barwidth; int targeth = target.size().height - (insets.top + insets.bottom); target.getComponent(0).move(insets.left, insets.top); target.getComponent(0).resize(cw, targeth); cw += insets.left; int h = insets.top; for (i = 1; i < target.getComponentCount(); i++) { Component m = target.getComponent(i); if (m.isVisible()) { Dimension d = m.getPreferredSize(); if (m instanceof Scrollbar) { d.width = barwidth; } if (m instanceof Label) { h += d.height / 5; d.width = barwidth; } m.move(cw, h); m.resize(d.width, d.height); h += d.height; } } } }; class BarWavesFrame extends Frame implements ComponentListener, ActionListener, AdjustmentListener, MouseMotionListener, MouseListener, ItemListener { Thread engine = null; Dimension winSize; Image dbimage; Random random; int maxTerms = 50; int modeCount; int maxMaxTerms = 90; int sampleCount; double modeTable[][]; double modeNorms[]; public static final double epsilon = .0000001; public static final double epsilon2 = .003; public String getAppletInfo() { return "BarWaves by Paul Falstad"; } Button sineButton; Button blankButton; Checkbox stoppedCheck; Checkbox soundCheck; Choice modeChooser; Choice setupChooser; Vector setupList; Setup setup; Choice displayChooser; Scrollbar dampingBar; Scrollbar speedBar; Scrollbar loadBar; Scrollbar baseFreqBar; Scrollbar stiffnessBar; double magcoef[]; double dampcoef[]; double phasecoef[]; double phasecoefcos[]; double phasecoefadj[]; double omega[]; static final double pi = 3.14159265358979323846; double step; double func[]; double funci[]; int thickness[]; int xpoints[], ypoints[]; int selectedCoef; int magnitudesY; static final int SEL_NONE = 0; static final int SEL_FUNC = 1; static final int SEL_MAG = 2; static final int MODE_SHAPE = 0; static final int MODE_FORCE = 1; static final int MODE_THICKNESS = 2; static final int DISP_PHASE = 0; static final int DISP_PHASECOS = 1; static final int DISP_MODES = 2; static final int BOUND_HINGED = 0; static final int BOUND_FREE = 1; static final int BOUND_CLAMPED = 2; int selection; int dragX, dragY; boolean dragging; boolean java2present; double t; int pause; Color gray1 = new Color(76, 76, 76); Color gray2 = new Color(127, 127, 127); int getrand(int x) { int q = random.nextInt(); if (q < 0) { q = -q; } return q % x; } BarWavesCanvas cv; BarWavesFrame() { super("Bar Waves Applet"); } public void init() { java2present = true; if (System.getProperty("java.version").indexOf("1.1") == 0) { java2present = false; } setupList = new Vector(); Setup s = new FreeBarSetup(); while (s != null) { setupList.addElement(s); s = s.createNext(); } selectedCoef = -1; setLayout(new BarWavesLayout()); cv = new BarWavesCanvas(this); cv.addComponentListener(this); cv.addMouseMotionListener(this); cv.addMouseListener(this); add(cv); setupChooser = new Choice(); int i; for (i = 0; i != setupList.size(); i++) { setupChooser.add("Setup: " + ((Setup) setupList.elementAt(i)).getName()); } setup = (Setup) setupList.elementAt(0); setupChooser.addItemListener(this); add(setupChooser); add(sineButton = new Button("Fundamental")); sineButton.addActionListener(this); add(blankButton = new Button("Clear")); blankButton.addActionListener(this); stoppedCheck = new Checkbox("Stopped"); stoppedCheck.addItemListener(this); add(stoppedCheck); soundCheck = new Checkbox("Sound", false); soundCheck.addItemListener(this); add(soundCheck); modeChooser = new Choice(); modeChooser.add("Mouse = Shape bar"); modeChooser.add("Mouse = Apply static force"); modeChooser.addItemListener(this); add(modeChooser); displayChooser = new Choice(); displayChooser.add("Display Phases"); displayChooser.add("Display Phase Cosines"); displayChooser.add("Display Modes"); displayChooser.addItemListener(this); add(displayChooser); add(new Label("Simulation Speed", Label.CENTER)); add(speedBar = new Scrollbar(Scrollbar.HORIZONTAL, 166, 1, 24, 300)); speedBar.addAdjustmentListener(this); add(new Label("Damping", Label.CENTER)); add(dampingBar = new Scrollbar(Scrollbar.HORIZONTAL, 10, 1, 0, 400)); dampingBar.addAdjustmentListener(this); add(new Label("Resolution", Label.CENTER)); add(loadBar = new Scrollbar(Scrollbar.HORIZONTAL, maxTerms, 1, 40, maxMaxTerms )); loadBar.addAdjustmentListener(this); setLoadCount(); add(new Label("Base Frequency", Label.CENTER)); add(baseFreqBar = new Scrollbar(Scrollbar.HORIZONTAL, 84, 12, 30, 168 )); baseFreqBar.addAdjustmentListener(this); baseFreqBar.disable(); add(new Label("String Stiffness", Label.CENTER)); add(stiffnessBar = new Scrollbar(Scrollbar.HORIZONTAL, 10, 1, 0, 100)); stiffnessBar.addAdjustmentListener(this); stiffnessBar.disable(); try { String param = null; //applet.getParameter("PAUSE"); if (param != null) { pause = Integer.parseInt(param); } } catch (Exception e) { } magcoef = new double[maxMaxTerms]; phasecoef = new double[maxMaxTerms]; phasecoefcos = new double[maxMaxTerms]; phasecoefadj = new double[maxMaxTerms]; func = new double[maxMaxTerms + 1]; funci = new double[maxMaxTerms + 1]; xpoints = new int[4]; ypoints = new int[4]; random = new Random(); setDamping(); reinit(); cv.setBackground(Color.black); cv.setForeground(Color.lightGray); resize(500, 500); handleResize(); show(); } void reinit() { doFundamental(); } void handleResize() { Dimension d = winSize = cv.getSize(); if (winSize.width == 0) { return; } dbimage = createImage(d.width, d.height); } void doFundamental() { doBlank(); magcoef[0] = 1; if (soundCheck.getState()) { doPlay(); } } void doBlank() { int x; for (x = 0; x <= sampleCount; x++) { func[x] = 0; } transform(true); } void transform(boolean novel) { int x, y; t = 0; for (y = 0; y != modeCount; y++) { double a = 0; double b = 0; for (x = 1; x != sampleCount; x++) { a += modeTable[x][y] * func[x]; b -= modeTable[x][y] * funci[x]; } a /= modeNorms[y]; b /= omega[y] * modeNorms[y]; if (a < epsilon && a > -epsilon) { a = 0; } if (b < epsilon && b > -epsilon) { b = 0; } if (novel) { b = 0; } double r = java.lang.Math.sqrt(a * a + b * b); magcoef[y] = r; double ph2 = java.lang.Math.atan2(b, a); phasecoefadj[y] = ph2; phasecoef[y] = ph2; } } int getPanelHeight() { return winSize.height / 3; } void centerString(Graphics g, String s, int y) { FontMetrics fm = g.getFontMetrics(); g.drawString(s, (winSize.width - fm.stringWidth(s)) / 2, y); } public void paint(Graphics g) { cv.repaint(); } public void updateBarWaves(Graphics realg) { if (!java2present) { centerString(realg, "Need java2 for this applet.", 100); return; } if (winSize == null || winSize.width == 0) { return; } Graphics g = dbimage.getGraphics(); boolean allQuiet = true; if (!stoppedCheck.getState()) { int val = speedBar.getValue() - 100; //if (val > 40) //val += getrand(10); double tadd = java.lang.Math.exp(val / 20.) * (.1 / 50); // add random crap into the time to avoid aliasing tadd *= 1 + getrand(300) * (.00191171); t += tadd; } g.setColor(cv.getBackground()); g.fillRect(0, 0, winSize.width, winSize.height); g.setColor(cv.getForeground()); int i; int panelHeight = getPanelHeight(); int midy = panelHeight / 2; int halfPanel = panelHeight / 2; double ymult = .75 * halfPanel; for (i = -1; i <= 1; i++) { g.setColor((i == 0) ? gray2 : gray1); g.drawLine(0, midy + (i * (int) ymult), winSize.width, midy + (i * (int) ymult) ); } g.setColor(gray2); g.drawLine(winSize.width / 2, midy - (int) ymult, winSize.width / 2, midy + (int) ymult ); int sampStart = (setup.leftBoundary() == BOUND_FREE) ? 1 : 0; int sampEnd = sampleCount - ((setup.rightBoundary() == BOUND_FREE) ? 1 : 0); if (dragging && selection == SEL_FUNC) { g.setColor(Color.cyan); allQuiet = true; for (i = sampStart; i <= sampEnd; i++) { int x = winSize.width * i / sampleCount; int y = midy - (int) (ymult * func[i]); drawBarPiece(g, x, y, i, sampStart); } } if (!stoppedCheck.getState() && !dragging) { for (i = 0; i != modeCount; i++) { magcoef[i] *= dampcoef[i]; } } double magcoefdisp[] = magcoef; double phasecoefdisp[] = phasecoef; double phasecoefcosdisp[] = phasecoefcos; if (!(dragging && selection == SEL_FUNC)) { g.setColor(Color.white); int j; for (j = 0; j != modeCount; j++) { if (magcoef[j] < epsilon && magcoef[j] > -epsilon) { magcoef[j] = phasecoef[j] = phasecoefadj[j] = 0; continue; } allQuiet = false; phasecoef[j] = (omega[j] * t + phasecoefadj[j]) % (2 * pi); if (phasecoef[j] > pi) { phasecoef[j] -= 2 * pi; } else if (phasecoef[j] < -pi) { phasecoef[j] += 2 * pi; } phasecoefcos[j] = java.lang.Math.cos(phasecoef[j]); } for (i = sampStart; i <= sampEnd; i++) { int x = winSize.width * i / sampleCount; double dy = 0; for (j = 0; j != modeCount; j++) { dy += magcoefdisp[j] * modeTable[i][j] * phasecoefcosdisp[j]; } func[i] = dy; int y = midy - (int) (ymult * dy); drawBarPiece(g, x, y, i, sampStart); } if (setup.getThickness() == 0) { if (setup.leftBoundary() == BOUND_FREE) { drawPin(g, 1, midy, ymult); } if (setup.rightBoundary() == BOUND_FREE) { drawPin(g, sampleCount - 1, midy, ymult); } } } if (selectedCoef != -1 && !dragging && ( magcoefdisp[selectedCoef] > .04 || magcoefdisp[selectedCoef] < -.04 )) { g.setColor(Color.yellow); ymult *= magcoefdisp[selectedCoef]; for (i = sampStart; i <= sampEnd; i++) { int x = winSize.width * i / sampleCount; double dy = modeTable[i][selectedCoef] * phasecoefcosdisp[selectedCoef]; int y = midy - (int) (ymult * dy); drawBarPiece(g, x, y, i, sampStart); } } if (selectedCoef != -1) { int f = getFreq(selectedCoef); g.setColor(Color.yellow); centerString(g, f + " Hz", panelHeight); } else if (soundCheck.getState()) { int f = getFreq(0); g.setColor(Color.white); centerString(g, "Fundamental = " + f + " Hz", panelHeight); } int termWidth = getTermWidth(); ymult = .6 * halfPanel; g.setColor(Color.white); if (displayChooser.getSelectedIndex() == DISP_PHASE || displayChooser.getSelectedIndex() == DISP_PHASECOS) { magnitudesY = panelHeight; } else { magnitudesY = panelHeight * 2; } midy = magnitudesY + (panelHeight / 2) + (int) ymult / 2; g.setColor(gray2); g.drawLine(0, midy, winSize.width, midy); g.setColor(gray1); g.drawLine(0, midy - (int) ymult, winSize.width, midy - (int) ymult); g.drawLine(0, midy + (int) ymult, winSize.width, midy + (int) ymult); g.drawLine(0, midy - (int) ymult / 4, winSize.width, midy - (int) ymult / 4); g.drawLine(0, midy + (int) ymult / 4, winSize.width, midy + (int) ymult / 4); int dotSize = termWidth - 3; if (dotSize < 3) { dotSize = 3; } for (i = 0; i != modeCount; i++) { int t = termWidth * i + termWidth / 2; int y = midy - (int) (logcoef(magcoefdisp[i]) * ymult); g.setColor(i == selectedCoef ? Color.yellow : Color.white); g.drawLine(t, midy, t, y); g.fillOval(t - dotSize / 2, y - dotSize / 2, dotSize, dotSize); } if (displayChooser.getSelectedIndex() == DISP_PHASE || displayChooser.getSelectedIndex() == DISP_PHASECOS) { g.setColor(Color.white); boolean cosines = displayChooser.getSelectedIndex() == DISP_PHASECOS; ymult = .75 * halfPanel; midy = ((panelHeight * 5) / 2); for (i = -2; i <= 2; i++) { if (cosines && (i == 1 || i == -1)) { continue; } g.setColor((i == 0) ? gray2 : gray1); g.drawLine(0, midy + (i * (int) ymult) / 2, winSize.width, midy + (i * (int) ymult) / 2 ); } if (!cosines) { ymult /= pi; } for (i = 0; i != modeCount; i++) { int t = termWidth * i + termWidth / 2; double ph = (cosines) ? phasecoefcosdisp[i] : phasecoefdisp[i]; if (magcoef[i] > -epsilon2 / 4 && magcoefdisp[i] < epsilon2 / 4) { ph = 0; } int y = midy - (int) (ph * ymult); g.setColor(i == selectedCoef ? Color.yellow : Color.white); g.drawLine(t, midy, t, y); g.fillOval(t - dotSize / 2, y - dotSize / 2, dotSize, dotSize); } } else if (displayChooser.getSelectedIndex() == DISP_MODES) { int sqw = (winSize.width - 25) / 3; int sqh = (int) (sqw / pi); int topY = panelHeight; int leftX = 0; int ox, oy = -1; for (i = 0; i != modeCount; i++) { if (!( magcoefdisp[i] > .06 || magcoefdisp[i] < -.06 )) { continue; } g.setColor(gray2); int centerX = leftX + sqw / 2; int centerY = topY + sqh / 2; g.drawLine(leftX, centerY, leftX + sqw, centerY); g.drawLine(centerX, topY, centerX, topY + sqh); g.setColor(i == selectedCoef ? Color.yellow : Color.white); g.drawRect(leftX, topY, sqw, sqh); ox = -1; ymult = sqh * .5 * magcoefdisp[i]; int j; for (j = sampStart; j <= sampEnd; j++) { int x = leftX + sqw * j / sampleCount; double dy = modeTable[j][i] * phasecoefcosdisp[i]; int y = centerY - (int) (ymult * dy); if (ox != -1) { g.drawLine(ox, oy, x, y); } ox = x; oy = y; } leftX += sqw + 10; if (leftX + sqw > winSize.width) { leftX = 0; topY += sqh + 10; if (topY + sqh > panelHeight * 2) { break; } } } } realg.drawImage(dbimage, 0, 0, this); if (!stoppedCheck.getState() && !allQuiet) { cv.repaint(pause); } } void drawPin(Graphics g, int pos, int midy, double ymult) { int x = winSize.width * pos / sampleCount; g.setColor(gray2); g.drawLine(x, (int) (midy - ymult), x, (int) (midy + ymult) ); g.setColor(Color.white); g.fillOval(x - 2, midy - (int) (func[pos] * ymult) - 2, 5, 5); } int getTermWidth() { int termWidth = winSize.width / modeCount; int maxTermWidth = winSize.width / 30; if (termWidth > maxTermWidth) { termWidth = maxTermWidth; } termWidth &= ~1; return termWidth; } void getVelocities() { int k, j; for (j = 0; j != sampleCount; j++) { double dy = 0; for (k = 0; k != modeCount; k++) { dy += magcoef[k] * modeTable[j][k] * java.lang.Math.sin(phasecoef[k]) * omega[k]; } funci[j] = -dy; } } void drawBarPiece(Graphics g, int x, int y, int i, int sampStart) { int thick = setup.getThickness(); xpoints[0] = xpoints[3]; ypoints[0] = ypoints[3]; xpoints[1] = xpoints[2]; ypoints[1] = ypoints[2]; xpoints[2] = x; ypoints[2] = y - thick; xpoints[3] = x; ypoints[3] = y + thick; if (i != sampStart) { if (thick == 0) { g.drawLine(xpoints[0], ypoints[0], xpoints[2], ypoints[2]); } else { g.fillPolygon(xpoints, ypoints, 4); } } } void edit(MouseEvent e) { if (selection == SEL_NONE) { return; } int x = e.getX(); int y = e.getY(); switch (selection) { case SEL_MAG: editMag(x, y); break; case SEL_FUNC: editFunc(x, y); break; } } void editMag(int x, int y) { if (selectedCoef == -1) { return; } int panelHeight = getPanelHeight(); double ymult = .6 * panelHeight / 2; double midy = magnitudesY + (panelHeight / 2) + (int) ymult / 2; double coef = -(y - midy) / ymult; coef = unlogcoef(coef); if (coef < -1) { coef = -1; } if (coef > 1) { coef = 1; } if (magcoef[selectedCoef] == coef) { return; } magcoef[selectedCoef] = coef; cv.repaint(pause); } void editFunc(int x, int y) { if (modeChooser.getSelectedIndex() == MODE_FORCE) { editFuncForce(x, y); return; } if (dragX == x) { editFuncPoint(x, y); dragY = y; } else { // need to draw a line from old x,y to new x,y and // call editFuncPoint for each point on that line. yuck. int x1 = (x < dragX) ? x : dragX; int y1 = (x < dragX) ? y : dragY; int x2 = (x > dragX) ? x : dragX; int y2 = (x > dragX) ? y : dragY; dragX = x; dragY = y; for (x = x1; x <= x2; x++) { y = y1 + (y2 - y1) * (x - x1) / (x2 - x1); editFuncPoint(x, y); } } } double logep2 = 0; double logcoef(double x) { if (x >= .25 || x <= -.25) { return x; } x *= 4; double ep2 = epsilon2; int sign = (x < 0) ? -1 : 1; x *= sign; if (x < ep2) { return 0; } if (logep2 == 0) { logep2 = -java.lang.Math.log(2 * ep2); } return .25 * sign * (java.lang.Math.log(x + ep2) + logep2) / logep2; } double unlogcoef(double x) { if (x >= .25 || x <= -.25) { return x; } double ep2 = epsilon2; int sign = (x < 0) ? -1 : 1; return .25 * sign * (java.lang.Math.exp(4 * x * sign * logep2 - logep2) - ep2); } void editFuncPoint(int x, int y) { int panelHeight = getPanelHeight(); int midy = panelHeight / 2; int halfPanel = panelHeight / 2; int periodWidth = winSize.width; double ymult = .75 * halfPanel; int lox = x * sampleCount / periodWidth; int hix = ((x + 1) * sampleCount - 1) / periodWidth; double val = (midy - y) / ymult; if (val > 1) { val = 1; } if (val < -1) { val = -1; } if (lox < 1) { lox = 1; } if (hix >= sampleCount) { hix = sampleCount - 1; } for (; lox <= hix; lox++) { if (modeChooser.getSelectedIndex() == MODE_THICKNESS) { thickness[lox] = (midy < y) ? (y - midy) * 2 : (midy - y) * 2; if (thickness[lox] == 0) { thickness[lox] = 1; } } else { func[lox] = val; funci[lox] = 0; } } func[sampleCount] = func[0]; cv.repaint(pause); if (soundCheck.getState() == false) { transform(false); } } void editFuncForce(int x, int y) { int panelHeight = getPanelHeight(); int midy = panelHeight / 2; int halfPanel = panelHeight / 2; int periodWidth = winSize.width; double ymult = .75 * halfPanel; int ax = x * sampleCount / periodWidth; double val = (midy - y) / ymult; if (val > 1) { val = 1; } if (val < -1) { val = -1; } if (ax < 1 || ax >= sampleCount) { return; } // solve equation A x = b, using diagonalized A, // A = M Lambda M^-1. x = M Lambda^-1 M^-1 b. b is // all zeros except at ax where it is 1. double q[] = new double[modeCount]; int i, j; // multiply b by Lambda^-1 M^-1. M^-1 has eigenvectors as rows // (with the norm of each eigenvector divided out). Lambda^-1 // is a diagonal matrix with 1/lambdas as elements (lambda=omega^2) for (i = 0; i != modeCount; i++) { q[i] = modeTable[ax][i] / (omega[i] * omega[i] * modeNorms[i]); } // multiply q by M to get result. M has eigenvectors as columns. for (i = 0; i != sampleCount; i++) { double dy = 0; for (j = 0; j != modeCount; j++) { dy += q[j] * modeTable[i][j]; } func[i] = dy; } // ok now we just scale the whole thing so we get the answer // we want at ax. double mult = val / func[ax]; for (i = 0; i <= sampleCount; i++) { func[i] *= mult; funci[i] = 0; } cv.repaint(pause); if (soundCheck.getState() == false) { transform(true); } } public void componentHidden(ComponentEvent e) { } public void componentMoved(ComponentEvent e) { } public void componentShown(ComponentEvent e) { cv.repaint(pause); } public void componentResized(ComponentEvent e) { handleResize(); cv.repaint(pause); } public void actionPerformed(ActionEvent e) { if (e.getSource() == sineButton) { doFundamental(); cv.repaint(); } if (e.getSource() == blankButton) { doBlank(); cv.repaint(); } } public void adjustmentValueChanged(AdjustmentEvent e) { System.out.print(((Scrollbar) e.getSource()).getValue() + "\n"); if (e.getSource() == dampingBar || e.getSource() == speedBar) { setDamping(); } if (e.getSource() == loadBar) { setLoadCount(); } if (e.getSource() == stiffnessBar) { genModes(); } cv.repaint(pause); } public boolean handleEvent(Event ev) { if (ev.id == Event.WINDOW_DESTROY) { //if (applet == null) dispose(); //else applet.destroyFrame(); return true; } return super.handleEvent(ev); } void setLoadCount() { setup = (Setup) setupList.elementAt(setupChooser.getSelectedIndex()); sampleCount = maxTerms = loadBar.getValue(); step = pi / sampleCount; int x, y; thickness = new int[sampleCount + 1]; int i; for (i = 0; i <= sampleCount; i++) { thickness[i] = 5; } genModes(); setDamping(); } void setDamping() { int i; dampcoef = new double[modeCount]; double tadd = java.lang.Math.exp((speedBar.getValue() - 100) / 20.) * (.1 / 50); for (i = 0; i != modeCount; i++) { double damper = java.lang.Math.exp(dampingBar.getValue() / 40 - 3) * 30; if (dampingBar.getValue() <= 2) { damper = 0; } double damp2 = omega[i] * java.lang.Math.sqrt( java.lang.Math.sqrt(1 + damper * damper / (omega[i] * omega[i])) - 1); dampcoef[i] = java.lang.Math.exp(-damp2 * tadd * .004); } } public void mouseDragged(MouseEvent e) { dragging = true; edit(e); } public void mouseMoved(MouseEvent e) { if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) { return; } int x = e.getX(); int y = e.getY(); dragX = x; dragY = y; int panelHeight = getPanelHeight(); int oldCoef = selectedCoef; selectedCoef = -1; selection = 0; if (y < panelHeight) { selection = SEL_FUNC; } if (y >= magnitudesY && y < magnitudesY + panelHeight) { int termWidth = getTermWidth(); selectedCoef = x / termWidth; if (selectedCoef >= modeCount) { selectedCoef = -1; } if (selectedCoef != -1) { selection = SEL_MAG; } } if (selectedCoef != oldCoef) { cv.repaint(pause); } } public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2 && selectedCoef != -1) { int i; for (i = 0; i != modeCount; i++) { if (selectedCoef != i) { magcoef[i] = 0; } } magcoef[selectedCoef] = 1; cv.repaint(pause); } } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { if (!dragging && selectedCoef != -1) { selectedCoef = -1; cv.repaint(pause); } } public void mousePressed(MouseEvent e) { if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0) { return; } if (selection == SEL_FUNC) { getVelocities(); } dragging = true; edit(e); } public void mouseReleased(MouseEvent e) { if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0) { return; } if (dragging && selection == SEL_FUNC) { if (modeChooser.getSelectedIndex() == MODE_THICKNESS) { genModes(); } else { transform(false); if (soundCheck.getState()) { doPlay(); } } } if (dragging && selection == SEL_MAG && soundCheck.getState()) { doPlay(); } dragging = false; cv.repaint(pause); } public void itemStateChanged(ItemEvent e) { if (e.getItemSelectable() == stoppedCheck) { cv.repaint(pause); return; } if (e.getItemSelectable() == soundCheck) { if (soundCheck.getState()) { speedBar.setValue(250); dampingBar.setValue(170); baseFreqBar.enable(); setDamping(); doPlay(); } else { baseFreqBar.disable(); } } if (e.getItemSelectable() == displayChooser) { cv.repaint(pause); } if (e.getItemSelectable() == setupChooser) { setLoadCount(); if (setup instanceof StiffStringSetup) { stiffnessBar.enable(); } else { stiffnessBar.disable(); } } } void dodiff(double matrix[][], int r, int i, int n, double mult) { if (i < 1 && setup.leftBoundary() == BOUND_HINGED) { return; } if (i > sampleCount - 1 && setup.rightBoundary() == BOUND_HINGED) { return; } if (n == 2 && !(setup instanceof StringSetup)) { if (i <= 1 && setup.leftBoundary() == BOUND_FREE) { return; } if (i >= sampleCount - 1 && setup.rightBoundary() == BOUND_FREE) { return; } } if (n > 0) { dodiff(matrix, r, i - 1, n - 2, -mult); dodiff(matrix, r, i + 1, n - 2, -mult); dodiff(matrix, r, i, n - 2, mult * 2); return; } if (i >= 1 && i <= sampleCount - 1) { matrix[r][i] += mult; } } void genModes() { int n = sampleCount - 1; double matrix[][] = new double[n + 1][n + 1]; double d[] = new double[n + 1]; double e[] = new double[n + 1]; int i, j; for (i = 1; i <= n; i++) { setup.doMatrixStep(matrix, i, n); } if (setup instanceof StringSetup) { if (setup.leftBoundary() == BOUND_FREE) { matrix[1][1]--; } if (setup.rightBoundary() == BOUND_FREE) { matrix[n][n]--; } } for (j = 1; j <= n; j++) { for (i = 1; i <= n; i++) { System.out.print(matrix[i][j] + " "); } System.out.print("\n"); } tred2(matrix, n, d, e); tqli(d, e, n, matrix); modeCount = sampleCount - 1; omega = new double[modeCount]; // now get the eigenvalues and sort them int omegamap[] = new int[sampleCount]; for (i = j = 0; i != n; i++) { if (d[i + 1] < 1e-8) { modeCount--; continue; } omega[j] = java.lang.Math.sqrt(d[i + 1]); omegamap[j] = i; j++; } int si, sj; // sort the omegas for (si = 1; si < modeCount; si++) { double v = omega[si]; int vm = omegamap[si]; sj = si; while (omega[sj - 1] > v) { omega[sj] = omega[sj - 1]; omegamap[sj] = omegamap[sj - 1]; sj--; if (sj <= 0) { break; } } omega[sj] = v; omegamap[sj] = vm; } modeTable = new double[sampleCount + 1][modeCount]; modeNorms = new double[modeCount]; for (i = 0; i != modeCount; i++) { int om = omegamap[i] + 1; double maxf = 0; for (j = 0; j != sampleCount; j++) { modeTable[j][i] = matrix[j][om]; if (modeTable[j][i] > maxf) { maxf = modeTable[j][i]; } if (-modeTable[j][i] > maxf) { maxf = -modeTable[j][i]; } } modeNorms[i] = 1 / (maxf * maxf); for (j = 0; j != sampleCount; j++) { modeTable[j][i] /= maxf; } } double mult = 1 / omega[0]; for (i = 0; i != modeCount; i++) { omega[i] *= mult; } } void tred2(double a[][], int n, double d[], double e[]) { int l, k, j, i; double scale, hh, h, g, f; // this loop gets faster as i decreases for (i = n; i >= 2; i--) { l = i - 1; h = scale = 0.0; if (l > 1) { for (k = 1; k <= l; k++) { scale += java.lang.Math.abs(a[i][k]); } if (scale == 0.0) { e[i] = a[i][l]; } else { for (k = 1; k <= l; k++) { a[i][k] /= scale; h += a[i][k] * a[i][k]; } f = a[i][l]; g = (f >= 0.0 ? -java.lang.Math.sqrt(h) : java.lang.Math.sqrt(h)); e[i] = scale * g; h -= f * g; a[i][l] = f - g; f = 0.0; for (j = 1; j <= l; j++) { a[j][i] = a[i][j] / h; g = 0.0; for (k = 1; k <= j; k++) { g += a[j][k] * a[i][k]; } for (k = j + 1; k <= l; k++) { g += a[k][j] * a[i][k]; } e[j] = g / h; f += e[j] * a[i][j]; } hh = f / (h + h); for (j = 1; j <= l; j++) { f = a[i][j]; e[j] = g = e[j] - hh * f; for (k = 1; k <= j; k++) { a[j][k] -= (f * e[k] + g * a[i][k]); } } } } else { e[i] = a[i][l]; } d[i] = h; } d[1] = 0.0; e[1] = 0.0; /* Contents of this loop can be omitted if eigenvectors not wanted except for statement d[i]=a[i][i]; */ // speed decreases as i increases for (i = 1; i <= n; i++) { l = i - 1; if (d[i] != 0) { for (j = 1; j <= l; j++) { g = 0.0; for (k = 1; k <= l; k++) { g += a[i][k] * a[k][j]; } for (k = 1; k <= l; k++) { a[k][j] -= g * a[k][i]; } } } d[i] = a[i][i]; a[i][i] = 1.0; for (j = 1; j <= l; j++) { a[j][i] = a[i][j] = 0.0; } } } // this is from Numerical Recipes in C. It finds the eigenvalues // and eigenvectors of an nxn tridiagonal symmetric matrix specified // by d[] and e[]. void tqli(double d[], double e[], int n, double z[][]) { int m, l, iter, i, k; double s, r, p, g, f, dd, c, b; for (i = 2; i <= n; i++) { e[i - 1] = e[i]; } e[n] = 0.0; // faster as l increases for (l = 1; l <= n; l++) { iter = 0; do { for (m = l; m <= n - 1; m++) { dd = java.lang.Math.abs(d[m]) + java.lang.Math.abs(d[m + 1]); if ((double) (java.lang.Math.abs(e[m]) + dd) == dd) { break; } } if (m != l) { if (iter++ == 30) { System.out.print("Too many iterations in tqli\n"); } g = (d[l + 1] - d[l]) / (2.0 * e[l]); r = pythag(g, 1.0); g = d[m] - d[l] + e[l] / (g + SIGN(r, g)); s = c = 1.0; p = 0.0; for (i = m - 1; i >= l; i--) { f = s * e[i]; b = c * e[i]; e[i + 1] = (r = pythag(f, g)); if (r == 0.0) { d[i + 1] -= p; e[m] = 0.0; break; } s = f / r; c = g / r; g = d[i + 1] - p; r = (d[i] - g) * s + 2.0 * c * b; d[i + 1] = g + (p = s * r); g = c * r - b; for (k = 1; k <= n; k++) { f = z[k][i + 1]; z[k][i + 1] = s * z[k][i] + c * f; z[k][i] = c * z[k][i] - s * f; } } if (r == 0.0 && i >= l) { continue; } d[l] -= p; e[l] = g; e[m] = 0.0; } } while (m != l); } } double SIGN(double a, double b) { return b >= 0 ? java.lang.Math.abs(a) : -java.lang.Math.abs(a); } double SQR(double a) { return a * a; } double pythag(double a, double b) { double absa, absb; absa = java.lang.Math.abs(a); absb = java.lang.Math.abs(b); if (absa > absb) { return absa * java.lang.Math.sqrt(1.0 + SQR(absb / absa)); } else { return (absb == 0.0 ? 0.0 : absb * java.lang.Math.sqrt(1.0 + SQR(absa / absb))); } } abstract class Setup { abstract String getName(); abstract Setup createNext(); abstract int leftBoundary(); abstract int rightBoundary(); int getThickness() { return 3; } void doMatrixStep(double matrix[][], int i, int n) { dodiff(matrix, i, i, 4, 1); } } ; class FreeBarSetup extends Setup { String getName() { return "bar, free"; } Setup createNext() { return new HingedBarSetup(); } int leftBoundary() { return BOUND_FREE; } int rightBoundary() { return BOUND_FREE; } } ; class HingedBarSetup extends Setup { String getName() { return "bar, hinged"; } Setup createNext() { return new ClampedBarSetup(); } int leftBoundary() { return BOUND_HINGED; } int rightBoundary() { return BOUND_HINGED; } } ; class ClampedBarSetup extends Setup { String getName() { return "bar, clamped"; } Setup createNext() { return new ClampedFreeBarSetup(); } int leftBoundary() { return BOUND_CLAMPED; } int rightBoundary() { return BOUND_CLAMPED; } } ; class ClampedFreeBarSetup extends Setup { String getName() { return "bar, clamped/free"; } Setup createNext() { return new HingedClampedBarSetup(); } int leftBoundary() { return BOUND_CLAMPED; } int rightBoundary() { return BOUND_FREE; } } ; class HingedClampedBarSetup extends Setup { String getName() { return "bar, hinged/clamped"; } Setup createNext() { return new StringSetup(); } int leftBoundary() { return BOUND_HINGED; } int rightBoundary() { return BOUND_CLAMPED; } } ; class StringSetup extends Setup { String getName() { return "string, pinned"; } void doMatrixStep(double matrix[][], int i, int n) { dodiff(matrix, i, i, 2, 1); } Setup createNext() { return new String1FreeSetup(); } int leftBoundary() { return BOUND_HINGED; } int rightBoundary() { return BOUND_HINGED; } int getThickness() { return 0; } } ; class String1FreeSetup extends StringSetup { String getName() { return "string, pinned/free"; } Setup createNext() { return new String2FreeSetup(); } int rightBoundary() { return BOUND_FREE; } } ; class String2FreeSetup extends String1FreeSetup { String getName() { return "string, free/free"; } Setup createNext() { return new StiffStringSetup(); } int leftBoundary() { return BOUND_FREE; } } ; class StiffStringSetup extends StringSetup { String getName() { return "stiff string, pinned"; } void doMatrixStep(double matrix[][], int i, int n) { dodiff(matrix, i, i, 2, 1); double stiff = stiffnessBar.getValue() * .1; dodiff(matrix, i, i, 4, stiff); } Setup createNext() { return new StiffStringClampedSetup(); } } ; class StiffStringClampedSetup extends StiffStringSetup { String getName() { return "stiff string, clamped"; } Setup createNext() { return null; } int leftBoundary() { return BOUND_CLAMPED; } int rightBoundary() { return BOUND_CLAMPED; } } ; double sndmin, sndmax; int getFreq(int n) { double stepsize = java.lang.Math.log(2) / 12; double freq = java.lang.Math.exp(baseFreqBar.getValue() * stepsize); return (int) (freq * omega[n]); } void doPlay() { final int rate = 22000; final int sampcount = rate; byte b[] = new byte[sampcount]; double stepsize = java.lang.Math.log(2) / 12; double freq = java.lang.Math.exp(baseFreqBar.getValue() * stepsize); double n = 2 * pi * freq / rate; n /= omega[0]; // filter out frequencies above Nyquist freq double maxomega = pi / n; int m = modeCount; while (m > 0 && omega[m - 1] > maxomega) { m--; } if (m == 0) { return; } // filter out frequencies less than 20 Hz (we do that so that // they do not throw off the bounds checking of the waveform) int m0 = 0; // freq = rate*omega*n/(2*pi) double minomega = 20 * 2 * pi / (rate * n); while (m0 < m && omega[m0] < minomega) { m0++; } if (m0 == m) { return; } boolean failed; int i; int sampWindow = rate / 40; int offset = 0; double lastscale = 1000; double mag[] = new double[modeCount]; for (i = 0; i != modeCount; i++) { mag[i] = magcoef[i]; } do { failed = false; double mn = (-sndmin > sndmax) ? -sndmin : sndmax; if (mn < .02) { mn = .02; } double scale = 126 / mn; if (scale > lastscale) { scale = lastscale; } sndmin = sndmax = 0; for (i = 0; i != sampWindow; i++) { double dy = 0; int j; int ii = i + offset; for (j = m0; j != m; j++) { dy += mag[j] * java.lang.Math.sin(ii * n * omega[j]) * scale; } if (dy < sndmin) { sndmin = dy; } if (dy > sndmax) { sndmax = dy; } b[ii] = (byte) dy; if (dy < -127 || dy > 127) { failed = true; } } sndmin /= scale; sndmax /= scale; if (failed) { continue; } offset += sampWindow; for (i = 0; i != modeCount; i++) { mag[i] *= dampcoef[i]; } if (offset >= sampcount) { break; } } while (true); AudioFormat format = new AudioFormat(rate, 8, 1, true, true); DataLine.Info info = new DataLine.Info( SourceDataLine.class, format ); SourceDataLine line = null; try { line = (SourceDataLine) AudioSystem.getLine(info); line.open(format, sampcount); } catch (Exception e) { e.printStackTrace(); } line.start(); line.write(b, 0, sampcount); cv.repaint(); } };