/*
* -------------------------------------------------------------------------
* $Id: Harmonic.java,v 1.4 2006/01/31 20:02:09 estewart Exp $
* -------------------------------------------------------------------------
* Copyright (c) 1999 Visual Numerics Inc. All Rights Reserved.
*
* This software is confidential information which is proprietary to
* and a trade secret of Visual Numerics, Inc. Use, duplication or
* disclosure is subject to the terms of an appropriate license
* agreement.
*
* VISUAL NUMERICS MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE
* SUITABILITY OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING
* BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. VISUAL
* NUMERICS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
* AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR
* ITS DERIVATIVES.
*--------------------------------------------------------------------------
*/
package com.imsl.demo.Harmonic;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.text.DecimalFormat;
import com.imsl.math.FFT;
import com.imsl.chart.*;
import com.imsl.demo.gallery.Describe;
/**
* Reads from a set of .wav files, plots the waveform, computes
* and plots an FFT on request.
*
* @author R.B.E.Taylor
* @created October 23, 2002
*/
public class Harmonic extends JFrameChart
implements ActionListener, ChangeListener, MouseListener, MouseMotionListener {
private Chart chart;
private AxisXY axis;
private Data data;
private JSlider slider;
private JTextField displayField;
private ButtonGroup selectCelloString;
private boolean zoom, iszoom;
private double[] from, to, current;
private double[] t, y, t_sub, y_sub;
private String plotColor, plotLabel;
public Harmonic(boolean exitOnClose) {
setTitle("Harmonic Analysis");
if (!exitOnClose) {
// remove the WindowListener, installed by JFrameChart, that
// exits the application when the window is closed.
Object l[] = getListeners(java.awt.event.WindowListener.class);
for (int k = 0; k < l.length; k++) {
removeWindowListener((java.awt.event.WindowListener)l[k]);
}
}
// Set default values.
zoom = true;
getWavData("Astring.wav");
plotColor = "orange";
plotLabel = "A String";
DecimalFormat tFormat = new DecimalFormat("0.000");
DecimalFormat yFormat = new DecimalFormat("###");
chart = getChart();
axis = new AxisXY(chart);
axis.getAxisX().getAxisLabel().setTextFormat(tFormat);
axis.getAxisX().getAxisLabel().setTextAngle(90);
axis.getAxisX().getAxisTitle().setTitle("Time, sec");
axis.getAxisY().getAxisLabel().setTextFormat(yFormat);
axis.getAxisY().getAxisTitle().setTitle("Relative Amplitude");
from = new double[2];
to = new double[2];
current = new double[2];
from[0] = 0.0; from[1] = 128.0;
to[0] = 0.8; to[1] = -128;
// Add MouseListener and MouseMotionListener to chart panel.
chart.getLegend().setPaint(true);
chart.getLegend().setViewport(0.65, 0.85, 0.05, 0.15);
getPanel().addMouseListener(this);
getPanel().addMouseMotionListener(this);
Container cp = getContentPane();
// Create components in control panel.
ButtonGroup selectCelloString = new ButtonGroup();
JRadioButton Astringbutton = new JRadioButton("A String", true);
Astringbutton.addActionListener(this);
JRadioButton Dstringbutton = new JRadioButton("D String", false);
Dstringbutton.addActionListener(this);
JRadioButton Gstringbutton = new JRadioButton("G String", false);
Gstringbutton.addActionListener(this);
JRadioButton Cstringbutton = new JRadioButton("C String", false);
Cstringbutton.addActionListener(this);
selectCelloString.add(Astringbutton);
selectCelloString.add(Dstringbutton);
selectCelloString.add(Gstringbutton);
selectCelloString.add(Cstringbutton);
JPanel stringPanel = new JPanel();
stringPanel.setLayout(new GridLayout(2,2));
stringPanel.add(Astringbutton);
stringPanel.add(Dstringbutton);
stringPanel.add(Gstringbutton);
stringPanel.add(Cstringbutton);
JButton draw = new JButton("Show Full Sample");
draw.addActionListener(this);
JButton reset = new JButton("Reset View");
reset.addActionListener(this);
JButton transform = new JButton("Calculate FFT");
transform.addActionListener(this);
JPanel buttonPanel = new JPanel();
buttonPanel.add(stringPanel);
buttonPanel.add(transform);
buttonPanel.add(draw);
buttonPanel.add(reset);
// Create the display panel
JPanel displayPanel = new JPanel();
displayField = new JTextField("Current position: t = ?? Y = ??", 53);
displayField.setBorder(null);
displayField.setEditable(false);
displayPanel.add(displayField);
cp.add(buttonPanel, BorderLayout.NORTH);
cp.add(displayPanel, BorderLayout.SOUTH);
drawGraph();
Describe des = new Describe(this, "/com/imsl/demo/Harmonic/Harmonic.html");
des.show();
Dimension ds = des.getSize();
Dimension ss = getToolkit().getScreenSize();
int h = Math.min(ss.width/2, ss.height-ds.height-32);
int w = (int)(h/0.8);
setSize(w, h);
setLocation(ss.width-ds.width, ds.height);
setResizable(false);
}
// Get the description line.
private String getDescription() {
DecimalFormat nf = new DecimalFormat("0.000");
StringBuffer sb = new StringBuffer();
if (iszoom) {
sb.append("Zooming In from: ");
sb.append("t = " + nf.format(from[0]) + ", Y = " + nf.format(from[1]));
sb.append(" To: ");
sb.append("t = " + nf.format(to[0]) + ", Y = " + nf.format(to[1]));
}
else {
sb.append("Cursor At: t = " + nf.format(current[0]) + ", Y = " + nf.format(current[1]));
}
return sb.toString();
}
// Get data from .wav files:
private void getWavData(String wavefilename) {
int numWavBytes;
byte[] byteStream;
float bytesPerSec;
try {
AccessWaveFile fileobject = new AccessWaveFile(wavefilename);
numWavBytes = fileobject.getAvailableBytes();
// workaround compression of jar file issue by hardcoding values.
switch (wavefilename.charAt(0)) {
case 'A': numWavBytes = 86272; break;
case 'D': numWavBytes = 75904; break;
case 'G': numWavBytes = 81664; break;
case 'C': numWavBytes = 92672; break;
default: numWavBytes = fileobject.getAvailableBytes(); break;
}
byteStream = fileobject.getByteStream(numWavBytes);
bytesPerSec = fileobject.getBytesPerSec();
} catch (Exception e) {
JOptionPane.showMessageDialog(this, e.getMessage(), "Exception", JOptionPane.ERROR_MESSAGE);
e.printStackTrace();
System.exit(1);
return;
}
// Convert data and construct t-values:
t = new double[numWavBytes];
y = new double[numWavBytes];
for (int i = 0; i < y.length; i++) {
y[i] = (double) byteStream[i];
t[i] = (double) i/bytesPerSec; //time in seconds
}
}
// Draw the graph.
private void drawGraph() {
// Subset it before plotting:
if (zoom) {
int i = 0;
double min = Math.min(from[0], to[0]);
while (t[i] < min) i++;
int i_init = i;
double max = Math.max(from[0], to[0]);
while (t[i] <= max) {
i++;
if (i == t.length-1) break;
}
int i_final = i;
t_sub = new double[i_final - i_init + 1];
y_sub = new double[i_final - i_init + 1];
for (i = 0; i <= i_final - i_init; i++) {
t_sub[i] = t[i + i_init];
y_sub[i] = y[i + i_init];
}
} else {
t_sub = new double[t.length];
y_sub = new double[t.length];
for (int i = 0; i < y_sub.length; i++) {
t_sub[i] = t[i];
y_sub[i] = y[i];
}
}
// If in zooming mode, then zoom the graph. Otherwise, use default ranges.
if (zoom) {
axis.getAxisX().setAutoscaleInput(AxisXY.AUTOSCALE_OFF);
axis.getAxisY().setAutoscaleInput(AxisXY.AUTOSCALE_OFF);
axis.getAxisX().setWindow(Math.min(from[0], to[0]), Math.max(from[0], to[0]));
axis.getAxisY().setWindow(-128.0, 128.0);
} else {
axis.getAxisX().setAutoscaleInput(AxisXY.AUTOSCALE_OFF);
axis.getAxisX().setWindow(0.0, t[t.length-1]);
axis.getAxisY().setAutoscaleInput(AxisXY.AUTOSCALE_OFF);
axis.getAxisY().setWindow(-128.0, 128.0);
}
data = new Data(axis, t_sub, y_sub);
data.setTitle(plotLabel);
data.setDataType(Data.DATA_TYPE_LINE);
data.setLineColor(plotColor);
}
private void plotNewFFT() {
// Calculate the FFT :
double[] t_lim = new double[2];
t_lim[0] = t_sub[0];
t_lim[1] = t_sub[t_sub.length - 1];
double[] a;
FFT transform = new FFT(y_sub.length);
a = transform.forward(y_sub);
// Calculate the power spectrum, as per documentation,
// including a factor normalising to the length of the input:
double[] power = new double[y_sub.length/2]; //should take care of n odd/even
double[] f = new double[power.length];
f[0] = (double) 0.0;
power[0] = Math.abs(a[0])/y_sub.length;
for (int i = 1; i < power.length; i++) {
f[i] = i/(t_lim[1] - t_lim[0]);
power[i] = Math.sqrt(a[2*i-1]*a[2*i-1] + a[2*i]*a[2*i])/y_sub.length;
}
PlotFFT pf = new PlotFFT(this);
pf.draw(f, power, t_lim, plotLabel, plotColor);
pf.show();
}
// Implement ActionListener
public void actionPerformed(ActionEvent e) {
data.remove(); // clear current trace
if (e.getActionCommand().equals("Show Full Sample")) {
zoom = false;
} else if (e.getActionCommand().equals("Reset View")) {
zoom = true;
from[0] = 0.0; from[1] = 128.0;
to[0] = 0.8; to[1] = -128;
} else if (e.getActionCommand().equals("A String")) {
plotColor = "orange";
plotLabel = "A String";
getWavData("Astring.wav");
} else if (e.getActionCommand().equals("C String")) {
plotColor = "red";
plotLabel = "C String";
getWavData("Cstring.wav");
} else if (e.getActionCommand().equals("D String")) {
plotColor = "magenta";
plotLabel = "D String";
getWavData("Dstring.wav");
} else if (e.getActionCommand().equals("G String")) {
plotColor = "blue";
plotLabel = "G String";
getWavData("Gstring.wav");
} else if (e.getActionCommand().equals("Calculate FFT")) {
plotNewFFT();
}
drawGraph();
repaint();
}
// Implement ChangeListener
public void stateChanged(ChangeEvent e) {
}
// Implement MouseListener
public void mouseClicked(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
// Get the starting position for zooming.
axis.mapDeviceToUser(e.getX(), e.getY(), from);
axis.mapDeviceToUser(e.getX(), e.getY(), to);
zoom = true;
iszoom = true;
}
public void mouseReleased(MouseEvent e) {
int x = e.getX();
int y = e.getY();
// The selection must be from 2 different points.
int[] point = new int[2];
axis.mapUserToDevice(from[0], from[1], point);
if (point[0] == x) return;
if (point[1] == y) return;
// Get the ending position for zooming.
axis.mapDeviceToUser(x, y, to);
iszoom = false;
data.remove();
drawGraph();
repaint();
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
// Implement MouseMotionListener
public void mouseDragged(MouseEvent e) {
// Draw the selection square and update display information.
paint(e, true);
displayField.setText(getDescription());
}
public void mouseMoved(MouseEvent e) {
// Update display information.
axis.mapDeviceToUser(e.getX(), e.getY(), current);
displayField.setText(getDescription());
}
// Draw the selection in different color.
protected void paint(MouseEvent mouseEvent, boolean erase) {
Graphics2D g = (Graphics2D) Harmonic.this.getPanel().getGraphics();
if (erase) paint(g);
axis.mapDeviceToUser(mouseEvent.getX(), mouseEvent.getY(), to);
paint(g);
g.dispose();
}
protected void paint(Graphics2D g) {
Color colorFill = new Color(255,0,255,128);
Color colorOutline = new Color(0,255,0);
int[] pointA = new int[2];
int[] pointB = new int[2];
axis.mapUserToDevice(from[0], from[1], pointA);
axis.mapUserToDevice(to[0], to[1], pointB);
Shape shape = new Rectangle(Math.min(pointA[0], pointB[0]),
Math.min(pointA[1], pointB[1]), Math.abs(pointA[0]-pointB[0]),
Math.abs(pointA[1]-pointB[1]));
g.setXORMode(colorOutline);
g.draw(shape);
g.setXORMode(colorFill);
g.fill(shape);
}
public static void main(String args[]) {
boolean exitOnClose = true;
if (args.length > 0 && args[0].equals("-noexit")) exitOnClose = false;
new Harmonic(exitOnClose).show();
}
}