/* Filter Applet * * By James Stewart * Dept of Computer Science * University of Toronto * jstewart@cs.toronto.edu * * Feel free to copy and modify this applet, but please give credit * to the original author where appropriate. * * Draws a discrete signal, a filter, and applies the filter * to the signal. * * General parameters: * * signal list of data points, assumed to be evenly spaced samples * of the signal * * filter list of filter points, assumed to be evenly spaced * * For examples of using this applet, see the *.html file in the * directory above this one. */ import java.util.*; import java.awt.*; import java.applet.Applet; /** An (x,y) point */ class xy_pair { int x, y; } /** A Signal */ class signal { float data[]; // the data points int size; // the number of data points boolean present[]; // whether each data point is present float max_data, min_data; // max and min data values int upper, lower; // upper and lower integers bounding y range int zero_index; // index of x=0 in the array of data // (this WILL BE in the range 0 .. size-1) int orig_x, orig_y; // (x,y) location of origin in window // Things having to do with appearance: final int UNIT_SPACING = 15; // # pixels per unit final int POINT_SIZE = 6; // diameter of a data point final int TAIL_SIZE = 10; // number of pixels on axis beyond extreme data point final int TICK_HALFWIDTH = 2; // number of visible pixels on each side of a tick mark final Color AxisColor = Color.darkGray; final Color LineColor = Color.black; final Color PointColor = Color.green; /** * Constructor just allocates the arrays */ signal() { size = 0; } void reset( int n ) { data = new float[n]; present = new boolean[n]; size = n; min_data = max_data = upper = lower = zero_index = 0; } void copy_signal( signal P ) { size = P.size; max_data = P.max_data; min_data = P.min_data; upper = P.upper; lower = P.lower; zero_index = P.zero_index; orig_x = P.orig_x; orig_y = P.orig_y; for (int i=0; i max_data) { max_data = item; if (Math.ceil( max_data ) > upper) upper = (int) Math.ceil( max_data ); } if (item < min_data) { min_data = item; if (Math.floor( min_data ) < lower) lower = (int) Math.floor( min_data ); } data[position] = item; present[position] = true; } /** * Convolve two signals and store the result here. */ void convolve( signal P, signal f ) { reset( P.size ); // Construct this signal to have same size as P zero_index = P.zero_index; // and same zero_index // Convolve for (int i=0; i < P.size; i++) convolve_one( P, f, i ); } /** * Compute and store (P*f)[i]. */ void convolve_one( signal P, signal f, int i ) { float sum, p_data; // Compute the new point sum = 0; for (int j=0; j= 0 && index < P.size) p_data = P.data[index]; else p_data = 0; sum += p_data * f.data[j]; } // Store the new point add_data( sum, i ); } /** * Parse a string containing the points of a signal. Return a * signal with these points. Upon a parsing error, return the * old_signal. * * If the string contains a number between square brackets * (e.g. [4.5]) then that number is positions at x=0. */ void parse_signal_string( String string ) { StringTokenizer t = new StringTokenizer( string ); int n = t.countTokens(); // Set the size of this signal to be the number of tokens reset(n); // Parse each number in string for (int i=0; i P.size) { f_position = 0; Q.clear_data(); } repaint(); return true; } /** * Positions all three signals. We must first convolve P and f to * get Q, determine the size and position of Q, then erase Q so that * the user can later fill it in. */ void position_all_signals() { xy_pair pos = new xy_pair(); Dimension size, max_size; // Position P, f, and Q pos.x = f.zero_index * f.UNIT_SPACING; // shift P right to leave f space on left size = P.position( pos ); // position P pos.x = f_position * f.UNIT_SPACING; // shift f according to convolution position pos.y += size.height + VERTICAL_SPACING; // shift down size = f.position( pos ); // position f pos.x = f.zero_index * f.UNIT_SPACING; // Q lines up below P pos.y += size.height + VERTICAL_SPACING; size = Q.position( pos ); // position Q // Resize the panel size.width += f.size * f.UNIT_SPACING; if (size.width < size().width) size.width = size().width; size.height += pos.y; resize( size ); } } /** The filter applet */ public class filter_applet extends java.applet.Applet { signal P = new signal(); // original signal signal Q = new signal(); // filtered signal signal f = new signal(); // filter String filter_string; // the filter as a string String signal_string; // the signal as a string TextField filter_field; // displays filter as a string TextField signal_field; // displays signal as a string boolean auto_convolve = true; filterCanvas filter_canvas = new filterCanvas( P, f, Q, auto_convolve ); // Things having to do with appearance: Font font = new Font( "TimesRoman", Font.PLAIN, 14 ); FontMetrics fm = getFontMetrics( font ); final int DEFAULT_FILTER_SIZE = 30; // width of filter_field in chars final int DEFAULT_SIGNAL_SIZE = 30; // width of signal_field in chars /** * Init the applet. */ public void init() { GridBagLayout gbLayout = new GridBagLayout(); GridBagConstraints gbConstraints = new GridBagConstraints(); Panel tp1 = new Panel(); Panel tp2 = new Panel(); Panel tp3 = new Panel(); Panel tp4 = new Panel(); CheckboxGroup cbg = new CheckboxGroup(); Checkbox cb_auto, cb_manual; // Get the applet parameters parse_parameters(); // Determine Q = P * f Q.convolve( P, f ); if (!auto_convolve) Q.clear_data(); // Lay out the graphs filter_canvas.position_all_signals(); // Set up UI components signal_field = new TextField( DEFAULT_SIGNAL_SIZE ); if (signal_string != null) signal_field.setText( signal_string ); filter_field = new TextField( DEFAULT_FILTER_SIZE ); if (filter_string != null) filter_field.setText( filter_string ); // In the text panel, put signal above filter tp1.add( new Label( "Signal", Label.RIGHT ) ); tp1.add( signal_field ); tp2.add( new Label( "Filter ", Label.RIGHT ) ); tp2.add( filter_field ); tp3.add( new Label( "Convolution:", Label.CENTER ) ); cb_auto = new Checkbox( " Automatic", cbg, auto_convolve ); tp3.add( cb_auto ); cb_manual = new Checkbox( " Manual", cbg, ! auto_convolve ); tp3.add( cb_manual ); tp4.add( new Label( " " ) ); // tp3.add( new Button( "Iterate Filter" ) ); // text_panel.setLayout( new GridLayout(2,1,0,5) ); setLayout( gbLayout ); //gbConstraints.fill = GridBagConstraints.BOTH; //gbConstraints.weighty = 1; //gbConstraints.weightx = 1; // gbConstraints.ipady = -10; gbConstraints.gridheight = 1; gbConstraints.gridwidth = 1; gbConstraints.anchor = GridBagConstraints.WEST; gbConstraints.gridx = 1; gbConstraints.gridy = 1; gbLayout.setConstraints( tp1, gbConstraints ); add( tp1 ); gbConstraints.anchor = GridBagConstraints.EAST; gbConstraints.gridx = 2; gbConstraints.gridy = 1; gbLayout.setConstraints( tp2, gbConstraints ); add( tp2 ); gbConstraints.anchor = GridBagConstraints.CENTER; gbConstraints.gridx = 1; gbConstraints.gridy = 2; gbConstraints.gridwidth = 2; gbLayout.setConstraints( tp3, gbConstraints ); add( tp3 ); gbConstraints.gridx = 1; gbConstraints.gridy = 3; gbLayout.setConstraints( tp4, gbConstraints ); add( tp4 ); gbConstraints.gridx = 1; gbConstraints.gridy = 4; gbConstraints.gridheight = 1; gbConstraints.gridwidth = 2; gbLayout.setConstraints( filter_canvas, gbConstraints ); add( filter_canvas ); // gbConstraints.anchor = GridBagConstraints.NORTHEAST; // gbConstraints.gridheight = GridBagConstraints.RELATIVE; } /** * Upon starting, start any threads that were previously stopped */ public void start() { /* if (thread != null && thread.isAlive()) thread.resume(); */ } /** * Upon stopping, stop all active threads */ public void stop() { /* if (thread != null && thread.isAlive()) thread.suspend(); */ } /** * Upon a mouseDown, colour the two nodes involved, but * don't rotate. */ public boolean mouseDown( Event evt, int x, int y ) { return false; } /** * Upon a mouseUp, rotate the two nodes. This involves spawning * a thread that will slowly move the nodes. */ public boolean mouseUp( Event evt, int x, int y ) { return false; } /** * Handle an action from one of the UI components. */ public boolean action( Event evt, Object arg ) { // Handle the event if (evt.target instanceof TextField) { filter_string = filter_field.getText(); f.parse_signal_string( filter_string ); signal_string = signal_field.getText(); P.parse_signal_string( signal_string ); Q.convolve( P, f ); if (!auto_convolve) Q.clear_data(); filter_canvas.position_all_signals(); filter_canvas.f_position = 0; filter_canvas.repaint(); } else if (evt.target instanceof Checkbox) { if (auto_convolve) Q.clear_data(); // go to manual else Q.convolve( P, f ); // go to automatic filter_canvas.f_position = 0; auto_convolve = ! auto_convolve; filter_canvas.auto_convolve = ! filter_canvas.auto_convolve; filter_canvas.repaint(); } else if (evt.target instanceof Button) { P.copy_signal( Q ); Q.convolve( P, f ); if (!auto_convolve) Q.clear_data(); filter_canvas.position_all_signals(); filter_canvas.f_position = 0; filter_canvas.repaint(); } else System.out.println( "ERROR in filter applet: Unrecognized UI event" ); return true; } /** * Parse the parameters supplied with the applet. These are listed at the * top of this file. */ void parse_parameters() { // Read signal signal_string = getParameter( "signal" ); if (signal_string != null) P.parse_signal_string( signal_string ); // Read filter filter_string = getParameter( "filter" ); if (filter_string != null) f.parse_signal_string( filter_string ); } }