import java.awt.*;
import java.applet.*;
import sun.audio.*;
import graph.*;

// @(#)fourier.java	1.15 96/07/15

/*
 * Copyright (c) 1996 Manfred Thole
 *      thole@nst.ing.tu-bs.de
 *
 * Modified by Tom Huber, huber@gac.edu
 * Revision Date: 20-Sep-96
 *    This requires the graph2d package from Leigh Brookshaw
 *       http://www.sci.usq.edu.au/staff/leighb/graph
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 */

/**
    Fourier synthesis.
    @author Manfred Thole, thole@nst.ing.tu-bs.de
    @version 96/07/15
    @
    @ Modified by Tom Huber, huber@gac.edu
    @ version 20-Sep-96
    @ To Compile, this requires the graph2d package from Leigh Brookshaw
    @    http://www.sci.usq.edu.au/staff/leighb/graph
    @
  */

public class fourier extends Applet {
  // Max. |ak|,|bk|
  final static int MAX_AB = 50;
  // "k_max"
  final static int MAX_ANZ = 14;
  // a_k = a[k]/INT_MULT
  final static double INT_MULT = 50.0;
  // time base
  final static double TIM_MULT = 40.0/Math.PI;
  // Audio??
  final static boolean AUDIO = true;
  // Maximum Number of Waves
  final static int MAX_WAVES = 3;

  private double acoeff [][] = new double[MAX_WAVES][MAX_ANZ];
  private double bcoeff [][] = new double[MAX_WAVES][MAX_ANZ];

  private Button la_a [] = new Button[MAX_ANZ];
  private Button la_b [] = new Button[MAX_ANZ];

  private TextField ta_a [] = new TextField[MAX_ANZ];
  private TextField ta_b [] = new TextField[MAX_ANZ];

  private TextField CosExpr = new TextField();
  private TextField SinExpr = new TextField();

  private Scrollbar s_a [] = new Scrollbar[MAX_ANZ];
  private Scrollbar s_b [] = new Scrollbar[MAX_ANZ];

  Panel panel1, panel2 ;    // Sub-containers for all this stuff.

  GridBagLayout gridbag = new GridBagLayout();

  private CheckboxGroup WaveNumber;
  private Checkbox Wave1;
  private Checkbox Wave2;
  private Checkbox Wave3;

  private CheckboxGroup AudioGroup;
  private Checkbox AudioOn;
  private Checkbox AudioOff;


  private Image bg;
  private Graphics bgg;

  private String sin_name;
  private String cos_name;

  private byte sound[] = new byte[40];
  private java.io.InputStream soundStream;
  private boolean soundOn;

  private int iy[] = new int[81];

  private int Wnum=0;   // Wave Number

  int height,width;


  public void init() {
    width = size().width;
    height = size().height;
    setBackground(Color.lightGray);
    resize(width,height);

    sin_name = "Sin: ";
    cos_name = "Cos: ";

    panel1 = new Panel();
    panel1.setLayout(gridbag);

    CosExpr = new TextField(13);
    SinExpr = new TextField(13);

    CosExpr.setForeground(Color.blue);
    CosExpr.setBackground(Color.white);
    SinExpr.setForeground(Color.blue);
    SinExpr.setBackground(Color.white);

    constrain(panel1, new Label(cos_name), 0, 0, 1, 1);
    constrain(panel1, new Label(sin_name), 3, 0, 1, 1);
    constrain(panel1, CosExpr, 1,0,2,1);
    constrain(panel1, SinExpr, 4,0,2,1);

    for (int i=0; i < MAX_ANZ; i++)
      {
      if (i<10)
  	   la_a[i] = new Button("a" + i + ":  ");
      else
  	   la_a[i] = new Button("a" + i + ":");
      ta_a[i] = new TextField(10);
      ta_a[i].setText(""+acoeff[Wnum][i]);
      ta_a[i].setForeground(Color.blue);
      ta_a[i].setBackground(Color.white);
      ta_a[i].setEditable(true);
	s_a[i] = new Scrollbar(Scrollbar.HORIZONTAL,(int)(acoeff[Wnum][i]*INT_MULT),1,-MAX_AB,MAX_AB);
      s_a[i].setBackground(Color.white);
      constrain(panel1, la_a[i], 0, i+1, 1, 1);
      constrain(panel1, ta_a[i], 1, i+1, 1, 1);
      constrain(panel1, s_a[i],  2, i+1, 1, 1, GridBagConstraints.HORIZONTAL,
          GridBagConstraints.NORTHWEST, 1.0, 0.0, 0, 0, 0, 10, 40, 0);
	if ( i == 0 )
	  {
	    constrain(panel1, new Label(""), 3, 1, 1, 1);
	  }
	else
	  {
	    if (i<10)
	       la_b[i] = new Button("b" + i + ":  ");
          else
	       la_b[i] = new Button("b" + i + ":");
          ta_b[i] = new TextField(10);
          ta_b[i].setText(""+bcoeff[Wnum][i]);
          ta_b[i].setForeground(Color.blue);
          ta_b[i].setBackground(Color.white);
          ta_b[i].setEditable(true);
	    s_b[i] = new Scrollbar(Scrollbar.HORIZONTAL,(int)(bcoeff[Wnum][i]*INT_MULT),1,-MAX_AB,MAX_AB);
          s_b[i].setBackground(Color.white);
          constrain(panel1, la_b[i], 3, i+1, 1, 1);
          constrain(panel1, ta_b[i], 4, i+1, 1, 1);
          constrain(panel1, s_b[i],  5, i+1, 1, 1, GridBagConstraints.HORIZONTAL,
          GridBagConstraints.NORTHWEST, 1.0, 0.0, 0, 0, 0, 10, 40, 0);
//          constrain(panel1, s_b[i],  5, i+1, 1, 1,0,5,0,5);
	  }
      }
    WaveNumber = new CheckboxGroup();
    add(Wave1 = new Checkbox("Wave1",WaveNumber,true));
    add(Wave2 = new Checkbox("Wave2",WaveNumber,false));
    add(Wave3 = new Checkbox("Wave3",WaveNumber,false));

    AudioGroup = new CheckboxGroup();
    add(AudioOn = new Checkbox("Audio On",AudioGroup,true));
    add(AudioOff = new Checkbox("Audio Off",AudioGroup,false));

    panel2 = new Panel();
    panel2.setLayout(gridbag);
    //
    // Create a Panel to contain all the components along the
    // left hand side of the window.  Use a GridBagLayout for it.
    constrain(panel2,Wave1,0,0,1,1);
    constrain(panel2,Wave2,0,1,1,1);
    constrain(panel2,Wave3,0,2,1,1);
    constrain(panel2, new Label(""), 0, 3, 1, 1);
    constrain(panel2,AudioOn,0,4,1,1);
    constrain(panel2,AudioOff,0,5,1,1);
//    constrain(panel2, new Label("  Sample:"), 0, 6, 1, 1);
//    constrain(panel2, new Label("  (-1^(x-1))/x"), 0, 7, 1, 1);

    // Finally, use a GridBagLayout to arrange the panels themselves
    this.setLayout(gridbag);
    // And add the panels to the toplevel window
    constrain(this, panel1, 0, 0, 1, 1, GridBagConstraints.NONE,
        GridBagConstraints.NORTHWEST, 0.0, 0.0, 205, 10, 0, 0);
    constrain(this, panel2, 0, 0, 1, 1, GridBagConstraints.NONE,
        GridBagConstraints.NORTHWEST, 0.0, 0.0, 10, 380, 0, 0);

//    bg = this.createImage(320,200);
    bg = this.createImage(width,height);
    bgg = bg.getGraphics();
    bgg.setColor(Color.lightGray);
    bgg.fillRect(0, 0, width, height);
    bgg.setColor(Color.black);
    bgg.fillRect(0, 0, 320, 200);
    bgg.setColor( new Color(0, 75, 0) );
    for (int i = 0; i < 320; i+=20)
         bgg.drawLine(i, 0, i, 200);
    for (int i = 100; i < 200; i+=30)
   	   bgg.drawLine(0, i, 320, i);
    for (int i = 100; i > 0; i-=30)
   	   bgg.drawLine(0, i, 320, i);
    bgg.setColor(Color.green);
    bgg.drawLine(0, 100, 320, 100);
    bgg.drawLine(160, 0, 160, 200);

    CalculatePlot();
    if ( AUDIO )  {
      soundStream = new ContinuousAudioDataStream(new AudioData(sound));
      soundOn = true;
    }
  }


  private final void CalculatePlot() {
    if ( AUDIO && soundOn )
      AudioPlayer.player.stop(soundStream);

    // One period, TIM_MULT dependent! This needs clean up...
    for (int i = 0; i < 80; i++)
      {
	double y = 0;
	y += acoeff[Wnum][0]/2.0;
	for (int j = 1; j < MAX_ANZ; j++)
	  {
	    y += acoeff[Wnum][j]*Math.cos(j*i/TIM_MULT);
	    y += bcoeff[Wnum][j]*Math.sin(j*i/TIM_MULT);
	  }
	iy[i] = (int) (100-y*30);
	if ( AUDIO && ( i % 2 != 0 ) )
	  sound[i/2] = int2ulaw((int)(y*2000));
      } // for (i...
      iy[80] = iy[0];

      if ( AUDIO && soundOn )
         AudioPlayer.player.start(soundStream);
  }

  public boolean handleEvent(Event e) {
//    this.showStatus("ID: "+e.id+"   Event: " + e.target);

    switch (e.id) { // See pg 114 of Java in a Nutshell for other event types

    case Event.ACTION_EVENT:

      if (e.target instanceof TextField) {
        for (int i=0; i < MAX_ANZ; i++)  {
  	    if ( e.target.equals(ta_a[i]) )  {
            ParseFunction function = new ParseFunction( ((TextField)e.target).getText() );

            if(!function.parse()) {
               this.showStatus("Failed to parse function!");
               System.out.println("Failed to parse function!");
               return true;
            }
            try {
               double value = function.getResult();
  	         acoeff[Wnum][i] = value;
               acoeff[Wnum][i] = Math.min(acoeff[Wnum][i],1.0);
               acoeff[Wnum][i] = Math.max(acoeff[Wnum][i],-1.0);
            } catch(Exception err) {
               this.showStatus("Failed to parse function!");
               System.out.println("Failed to parse function!");
               acoeff[Wnum][i] = 0.0;
            }
              CosExpr.setText("");
              SinExpr.setText("");
  	        ta_a[i].setText(""+acoeff[Wnum][i]);
	        s_a[i].setValue( (int)(acoeff[Wnum][i]*INT_MULT) );
	        break;
	    }  // if ( e.target.equals(ta_a[i]) )

  	    else if ( e.target.equals(ta_b[i]) )  {
            ParseFunction function = new ParseFunction( ((TextField)e.target).getText() );

            if(!function.parse()) {
               this.showStatus("Failed to parse function!");
               System.out.println("Failed to parse function!");
               return true;
            }
            try {
               double value = function.getResult();
  	         bcoeff[Wnum][i] = value;
               bcoeff[Wnum][i] = Math.min(bcoeff[Wnum][i],1.0);
               bcoeff[Wnum][i] = Math.max(bcoeff[Wnum][i],-1.0);
            } catch(Exception err) {
               this.showStatus("Failed to parse function!");
               System.out.println("Failed to parse function!");
               bcoeff[Wnum][i] = 0.0;
            }
              CosExpr.setText("");
              SinExpr.setText("");
  	        ta_b[i].setText(""+bcoeff[Wnum][i]);
	        s_b[i].setValue( (int)(bcoeff[Wnum][i]*INT_MULT) );
	        break;
	    }  // else if ( e.target.equals(ta_b[i]) )

        } // for (int i=0; i < MAX_ANZ; i++)

        if (e.target.equals(CosExpr)) {
            ParseFunction function = new ParseFunction( ((TextField)e.target).getText() );

            if(!function.parse()) {
               this.showStatus("Failed to parse function!");
               System.out.println("Failed to parse function!");
               return true;
            }
            for (int i=0; i < MAX_ANZ; i++) {
              try {
               double X = (double)(i);
               double value = function.getResult(X);
  	         acoeff[Wnum][i] = value;
               acoeff[Wnum][i] = Math.min(acoeff[Wnum][i],1.0);
               acoeff[Wnum][i] = Math.max(acoeff[Wnum][i],-1.0);
              } catch(Exception err) {
               this.showStatus("Failed to parse function!");
               System.out.println("Failed to parse function!");
               acoeff[Wnum][i] = 0.0;
              }
  	        ta_a[i].setText(""+acoeff[Wnum][i]);
	        s_a[i].setValue( (int)(acoeff[Wnum][i]*INT_MULT) );
            }
        }

        else if (e.target.equals(SinExpr)) {
            ParseFunction function = new ParseFunction( ((TextField)e.target).getText() );

            if(!function.parse()) {
               this.showStatus("Failed to parse function!");
               System.out.println("Failed to parse function!");
               return true;
            }
            for (int i=1; i < MAX_ANZ; i++) {
              try {
               double X = (double)(i);
               double value = function.getResult(X);
  	         bcoeff[Wnum][i] = value;
               bcoeff[Wnum][i] = Math.min(bcoeff[Wnum][i],1.0);
               bcoeff[Wnum][i] = Math.max(bcoeff[Wnum][i],-1.0);
              } catch(Exception err) {
               this.showStatus("Failed to parse function!");
               System.out.println("Failed to parse function!");
               bcoeff[Wnum][i] = 0.0;
              }
  	        ta_b[i].setText(""+bcoeff[Wnum][i]);
	        s_b[i].setValue( (int)(bcoeff[Wnum][i]*INT_MULT) );
            }
        }
      repaint();
      return true;
      } //  if (e.target instanceof TextField)

      if (e.target instanceof Button) {
       for (int i=0; i < MAX_ANZ; i++)  {
  	   if ( e.target.equals(la_a[i]) )  {
//             this.showStatus("Zeroing "+e.target);
  	       acoeff[Wnum][i] = 0.0;
             CosExpr.setText("");
             SinExpr.setText("");
	       ta_a[i].setText(""+acoeff[Wnum][i]);
	       s_a[i].setValue( (int)(acoeff[Wnum][i]*INT_MULT) );
             repaint();
             return true;
	   }  // if ( e.target.equals(ta_a[i]) )
  	   else if ( e.target.equals(la_b[i]) )  {
           if (i > 0) {
//             this.showStatus("Zeroing "+e.target);
  	       bcoeff[Wnum][i] = 0.0;
             CosExpr.setText("");
             SinExpr.setText("");
	       ta_b[i].setText(""+bcoeff[Wnum][i]);
	       s_b[i].setValue( (int)(bcoeff[Wnum][i]*INT_MULT) );
             repaint();
             return true;
           } // if (i > 0)
	   }  // else if ( e.target.equals(ta_b[i]) )
        } // for (int i=0; i < MAX_ANZ; i++)
      } // if (e.target instanceof Button)


      else if (e.target.equals(Wave1)) {
         Wnum = 0;
         CosExpr.setText("");
         SinExpr.setText("");
         this.showStatus("Using Wave Number 1");
         for (int i=0; i < MAX_ANZ; i++)  {
  	      ta_a[i].setText(""+acoeff[Wnum][i]);
	      s_a[i].setValue( (int)(acoeff[Wnum][i]*INT_MULT) );
            if (i > 0) {
     	         ta_b[i].setText(""+bcoeff[Wnum][i]);
	         s_b[i].setValue( (int)(bcoeff[Wnum][i]*INT_MULT) );
            }
         }
         repaint();
         return true;

      }
      else if (e.target.equals(Wave2)) {
         Wnum = 1;
         CosExpr.setText("");
         SinExpr.setText("");
         this.showStatus("Using Wave Number 2");
         for (int i=0; i < MAX_ANZ; i++)  {
  	      ta_a[i].setText(""+acoeff[Wnum][i]);
	      s_a[i].setValue( (int)(acoeff[Wnum][i]*INT_MULT) );
            if (i > 0) {
     	         ta_b[i].setText(""+bcoeff[Wnum][i]);
	         s_b[i].setValue( (int)(bcoeff[Wnum][i]*INT_MULT) );
            }
         }
         repaint();
         return true;

      }
      else if (e.target.equals(Wave3)) {
         Wnum = 2;
         CosExpr.setText("");
         SinExpr.setText("");
         this.showStatus("Using Wave Number 3");
         for (int i=0; i < MAX_ANZ; i++)  {
  	      ta_a[i].setText(""+acoeff[Wnum][i]);
	      s_a[i].setValue( (int)(acoeff[Wnum][i]*INT_MULT) );
            if (i > 0) {
     	         ta_b[i].setText(""+bcoeff[Wnum][i]);
	         s_b[i].setValue( (int)(bcoeff[Wnum][i]*INT_MULT) );
            }
         }
         repaint();
         return true;

      }
      else if (e.target.equals(AudioOff)) {
         if ( soundOn ) {
   	      AudioPlayer.player.stop(soundStream);
	      showStatus("Audio OFF");
            soundOn = false;
         }
         return true;
      }

      else if (e.target.equals(AudioOn)) {
         if ( !soundOn ) {
  	      AudioPlayer.player.start(soundStream);
	      showStatus("Audio ON");
            soundOn = true;
         }
         return true;

      }

    case Event.SCROLL_ABSOLUTE:
    case Event.SCROLL_LINE_UP:
    case Event.SCROLL_PAGE_UP:
    case Event.SCROLL_LINE_DOWN:
    case Event.SCROLL_PAGE_DOWN:

      for (int i=0; i < MAX_ANZ; i++) {
   	   if ( e.target.equals(s_a[i]) )
	   {
           CosExpr.setText("");
           SinExpr.setText("");
           int value = ((Scrollbar)e.target).getValue();
	     acoeff[Wnum][i] = (double)(value) / INT_MULT;
	     ta_a[i].setText(""+acoeff[Wnum][i]);
	     break;
	   }
	   else if ( ( i != 0 ) &&  ( e.target.equals(s_b[i]) ) )
	   {
           CosExpr.setText("");
           SinExpr.setText("");
           int value = ((Scrollbar)e.target).getValue();
	     bcoeff[Wnum][i] = (double)(value) / INT_MULT;
	     ta_b[i].setText(""+bcoeff[Wnum][i]);
	     break;
	   }
	} // for (int i=0...
      repaint();
      return true;
    case Event.WINDOW_DESTROY:
      destroy();
      return true;


    default:
       return super.handleEvent(e);

    } // switch ...

  }

  public void paint(Graphics g) {
    CalculatePlot();
    g.drawImage(bg, 0, 0, null);
    g.clipRect(0, 0, 320, 200);  // Keeps drawing out of area

    g.setColor(Color.red);
    for (int i = 0; i < 320; i+=80) {
      for (int j = 0; j < 80; j++) {
	      g.drawLine(i+j, iy[j], i+j+1, iy[j+1]);
      }
    }


  }

  public void stop() {
     if ( AUDIO && soundOn )
	AudioPlayer.player.stop(soundStream);
  }

  public void start() {
     if ( AUDIO && soundOn )
	AudioPlayer.player.start(soundStream);
  }

  public void destroy() {
    if ( AUDIO )
      AudioPlayer.player.stop(soundStream);
    for (int i=0; i < MAX_ANZ; i++)
      {
      for (int j=0; j<MAX_WAVES; j++) {
	   acoeff[j][i] = 0.0;
	   bcoeff[j][i] = 0.0;
      }
	la_a[i] = null;
	la_b[i] = null;
	s_a[i] = null;
	s_b[i] = null;
      iy = null;
      }
      Wnum = 0;
      removeAll();
  }

  public static byte int2ulaw(int ch) {
    int mask;

    if (ch < 0) {
      ch = -ch;
      mask = 0x7f;
    }
    else {
      mask = 0xff;
    }
    //
    if (ch < 32) {
      ch = 0xF0 | 15 - (ch/2);
    }
    else
      if (ch < 96) {
	ch = 0xE0 | 15 - (ch-32)/4;
      }
      else
	if (ch < 224) {
	  ch = 0xD0 | 15 - (ch-96)/8;
	}
	else
	  if (ch < 480) {
	    ch = 0xC0 | 15 - (ch-224)/16;
	  }
	  else
	    if (ch < 992 ) {
	      ch = 0xB0 | 15 - (ch-480)/32;
	    }
	    else
	      if (ch < 2016) {
		ch = 0xA0 | 15 - (ch-992)/64;
	      }
	      else
		if (ch < 4064) {
		  ch = 0x90 | 15 - (ch-2016)/128;
		}
		else
		  if (ch < 8160) {
		    ch = 0x80 | 15 - (ch-4064)/256;
		  }
		  else {
		    ch = 0x80;
		  }
    return (byte)(mask & ch);
  }


  public String getAppletInfo()
    {
      return "fourier.class 96/07/15, Copyright (C) 1996 Manfred Thole\n" +
	     "This program is free software; you can redistribute it and/or modify it\n" +
             "under the terms of the GNU General Public License as published by the\n" +
             "Free Software Foundation; either version 2 of the License, or (at your\n" +
             "option) any later version.\n" +
             "\n" +
             "This program is distributed in the hope that it will be useful, but\n" +
             "WITHOUT ANY WARRANTY; without even the implied warranty of\n" +
             "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n" +
             "General Public License for more details.\n" +
             "\n" +
             "You should have received a copy of the GNU General Public License along\n" +
             "with this program; if not, write to the Free Software Foundation, Inc.,\n" +
             "675 Mass Ave, Cambridge, MA 02139, USA.\n";
    }

  public String[][] getParameterInfo() {
    String[][] info = { { "sin_name", "String", "Sine: / Sine: / ..." },
			{ "cos_name", "String", "Cosine: / Cosine: / ..." }
		      };
    return info;
  }

   public void constrain(Container container, Component component,
                  int grid_x, int grid_y, int grid_width, int grid_height,
                  int fill, int anchor, double weight_x, double weight_y,
                  int top, int left, int bottom, int right, int ipad_x,
                  int ipad_y)
    {
        GridBagConstraints c = new GridBagConstraints();
        c.gridx = grid_x; c.gridy = grid_y;
        c.gridwidth = grid_width; c.gridheight = grid_height;
        c.fill = fill; c.anchor = anchor;
        c.ipadx = ipad_x; c.ipady = ipad_y;
        c.weightx = weight_x; c.weighty = weight_y;
        if (top+bottom+left+right > 0)
            c.insets = new Insets(top, left, bottom, right);

        ((GridBagLayout)container.getLayout()).setConstraints(component, c);
        container.add(component);
    }

    public void constrain(Container container, Component component,
                  int grid_x, int grid_y, int grid_width, int grid_height,
                  int fill, int anchor, double weight_x, double weight_y,
                  int top, int left, int bottom, int right) {
       constrain(container, component, grid_x, grid_y, grid_width,
                  grid_height, fill, anchor, weight_x, weight_y,
                  top, left, bottom, right, 0, 0);
    }

    public void constrain(Container container, Component component,
                  int grid_x, int grid_y, int grid_width, int grid_height) {
        constrain(container, component, grid_x, grid_y,
              grid_width, grid_height, GridBagConstraints.NONE,
              GridBagConstraints.NORTHWEST, 0.0, 0.0, 0, 0, 0, 0, 0, 0);
    }

    public void constrain(Container container, Component component,
                  int grid_x, int grid_y, int grid_width, int grid_height,
                  int top, int left, int bottom, int right) {
        constrain(container, component, grid_x, grid_y,
              grid_width, grid_height, GridBagConstraints.NONE,
              GridBagConstraints.NORTHWEST,
              0.0, 0.0, top, left, bottom, right, 0, 0);
    }


}

