import java.awt.*;
import java.awt.event.*;
import java.applet.Applet; 
import java.awt.image.*; 
import javax.sound.sampled.*;
import java.io.*;


/**
 * Class SpectrumAnalyzer - displays microphone signal
 * created with BlueJ
 * 
 * @author (Martin Lieberherr) 
 * @version (July 30th, 2012)
 */
public class SpectrumAnalyzer extends Applet implements ActionListener, KeyListener, MouseListener
 // the applet is wrapped within a main-method, see below, to convert it into an application
{  
    // instance variables and attributes  (all private)
    int nx0 = 10;       // left coordinate of displays in window frame in pixel
    int nx = 1000;       // number of pixels in x-direction (number of set points) in displays
    int ny0Mic = 10;      // top coordinate of microphone display on window frame
    int nyMic = 50;     // number of pixels in y-directon for display of microphone signal
    int ny0Freq = ny0Mic+nyMic+10; // top coordinate of amplitude-display in window frame
    int nyFreq = 300;   // number of pixels in y-direction for display of frequency-spectrum
                                // for applet window see main  (nx, nyMic+nyFreq+..=600)
    int iCursor = -1;        // cursor-position in array amplitude[i] or logAmpl[i]
  
    // specifications for AudioFormat, try other values it it does not work
    float sampleRate = 44100.0F; //8000,11025,16000,22050,44100  (Hertz)
    int sampleSizeInBits = 16; //8,16
    int channels = 1; //1,2
    boolean signed = true; //true,false
    boolean bigEndian = false; //true,false
    
    AudioFormat audioFormat;
    TargetDataLine targetDataLine;
    
    // size of buffers to read from targetDataLine (soundcard-output)
    int maxIndex = Math.round(sampleRate*10);  // max. Index for predefined arrays 
    int doubleBufferSize = Math.round(2*4410);  // size of the doubleBuffer (number of sampled data points)  (default)
    byte tempBuffer[] = new byte[2*maxIndex];  // arbitrary-size temporary holding buffer for reading of TargetDataLine (two consecutive bytes = 1 sample)
    double doubleBuffer[] = new double[maxIndex];              // same content as tempBuffer but stored as double (1 double = 1 sample)
    
    // defaults for calculation of acustical spectrum
    double f1 = 0; // lower end of frequency band (default)
    double f2 = 4000; // higher end of frequency band  (default)
    long  nf = 1000; // number of calculated frequency-amplitudes between f1 and f2 (default)
    boolean frozen = false;  // display frozen for examination (default)
    double amplitude[] = new double[maxIndex];
    double logAmpl[] = new double[maxIndex];
    
    
    // attributes
    
    Label titleLabel;           // holds a title and the author's name
    Label f1Label;              // text "f1  (Hz) = "
    TextField f1Field;          // display / input f1
    Label nfLabel;              // text "nf = "
    TextField nfField;          // display / input nf
    Label f2Label;              // text "f2  (Hz) = "
    TextField f2Field;          // display / input f2 
    Label statusLine;           // display messages / status
    Button freezeButton;        // used to freeze the display
    Button storeButton;         // used to store the frequency-amplitudes
    boolean storeFlag = false;  // true if the spectrum shall be stored as text-file
    Button infoButton;         // show information
    Label zeroLabel;            // for checkbox to set the mean microphone signal to zero
    Checkbox zeroBox;
    Label filterLabel;          // for radiobuttons for choice of filter-type
    CheckboxGroup filterGroup;
    Checkbox rectBox;
    Checkbox hannBox;
    //     Checkbox hammingBox;
    //     Checkbox gaussBox;
    Label ampScalingLabel;          // radiobuttons for selection of scaling
    CheckboxGroup ampScalingGroup;
    Checkbox linBox;
    Checkbox logBox;
    Label peakFreqLabel;             // display frequency of maximum
    double peakFreq;   
    Label cursorLabel;              // display coordinates of cursor in spectrum
    Label samplingRateLabel;        // to display the sampling rate
    Label sampleSizeLabel;          // to display the word "sample size"
    TextField sampleSizeField;      // for displaying / inputting the sample size (in points)
    Label sampleTimeLabel;          // duration to take one sample
    Label freqResLabel;         // display of frequency resolution
    double freqRes;             // frequency resolution in Hertz
    
    // buffered image for display of microphone signal
    BufferedImage  BuffImage1 = new BufferedImage(nx, nyMic, BufferedImage.TYPE_INT_RGB);
    Graphics g1 = BuffImage1.createGraphics(); 
    
    // buffered image for display of frequency-spectrum
    BufferedImage  BuffImage2 = new BufferedImage(nx, nyFreq, BufferedImage.TYPE_INT_RGB);
    Graphics g2 = BuffImage2.createGraphics();
    
    // buffered image for cursor (vertical line)
    BufferedImage  BuffImage3 = new BufferedImage(1, nyFreq, BufferedImage.TYPE_INT_RGB);
    Graphics g3 = BuffImage3.createGraphics();
    
    // *************************************************************************************************************************************
    
    // frame to display the applet within (applets can't listen to the microphone) 
     /**
     * Makes a Frame and displays the applet SpectrumAnalyzer() within
     * (applets can't listen to the microphone)
     */
    public static void main(String[] args) {
    SpectrumAnalyzer myApplet = new SpectrumAnalyzer(); // define applet of interest
    // security forbids the applet to listen to  the microphone! 
    Frame myFrame = new Frame("Spectrum Analyzer  -- by Martin Lieberherr"); // create frame with title
    // Call applet's init method (since Java App does not
    // call it as a browser automatically does)
    myApplet.init();    
    // add applet to the frame
    myFrame.add(myApplet, BorderLayout.CENTER);
    myFrame.setSize(1020,600); // for SpectrumAnalyzer-Applet
    // myFrame.pack(); // set window to appropriate size (for its elements)
    myFrame.setVisible(true); // usual step to make frame visible
    myFrame.addWindowListener(new WindowAdapter()
        {
            public void windowClosing(WindowEvent we)
            {
                System.exit(0);  // pressing the close-button exits the program
            }
        });
    myApplet.start();
  } // end main
    
    
    

 
     /**
     * Called by the browser or applet viewer to inform this Applet that it
     * has been loaded into the system. It is always called before the first 
     * time that the start method is called.
     */
    public void init()
    {   
        setLayout(null);  // for "hard" layout in pixels
        
        int yText = nyMic+nyFreq+30;  // position of next element on window
        int xText = nx0;  // position of next element on window
        
        // titleLabel = new Label("        red (top): signal from internal microphone (scaled),    blue: acustical spectrum (scaled) between frequencies f1 and f2");
        // titleLabel.setBounds(xText, yText, nx, 20);// setBounds(x_left, y_top, width, height) for display
        // add(titleLabel);  //added to ContentPane below graphical displays 
        // yText = yText+30;
        
        f1Label = new Label("f1 (Hz):");
        f1Label.setBounds(xText, yText, 60, 20);  // setBounds(x_left, y_top, width, height) for display
        add(f1Label);  //added to ContentPane below graphical displays
        f1Field = new TextField(""+f1);    
        f1Field.setBounds(xText+60, yText, 100, 20);
        add(f1Field);
        f1Field.addActionListener(this); // ActionListener is called if "enter" is pressed in TextField
        
        xText = xText+400;
        nfLabel = new Label("nf = ");
        nfLabel.setBounds(xText, yText, 40, 20);
        add(nfLabel);
        xText = xText+40;
        nfField = new TextField(""+nf);
        nfField.setBounds(xText, yText, 60, 20);
        add(nfField);
        nfField.addActionListener(this);
        
        xText = nx-200;
        f2Label = new Label("f2 (Hz):"); 
        f2Label.setBounds(xText, yText, 60, 20);
        add(f2Label);
        f2Field = new TextField(""+f2); 
        f2Field.setBounds(xText+60, yText, 100, 20);
        add(f2Field);
        f2Field.addActionListener(this);
        
        yText = yText + 30;
        xText = nx0;
        zeroBox = new Checkbox("zero", true);
        zeroBox.setBounds(xText, yText, 70, 20);
        add(zeroBox);
        
        xText = xText+80;
        filterLabel = new Label("     filter:");
        filterLabel.setBounds(xText, yText, 60, 20);
        add(filterLabel);
        
        filterGroup = new CheckboxGroup();
        xText = xText+70;
        rectBox = new Checkbox("rect", filterGroup, true);
        rectBox.setBounds(xText, yText, 50,20);
        add(rectBox);
        xText = xText+60;
        hannBox = new Checkbox("Hann", filterGroup, false);
        hannBox.setBounds(xText, yText, 60,20);
        add(hannBox);
        //         xText = xText+70;
        //         hammingBox = new Checkbox("Hamming", filterGroup, false);
        //         hammingBox.setBounds(xText, yText, 90,20);
        //         add(hammingBox);
        //         xText = xText+100;
        //         gaussBox = new Checkbox("gaussian", filterGroup, false);
        //         gaussBox.setBounds(xText, yText, 80,20);
        //         add(gaussBox);
        
        xText = xText+100;
        ampScalingLabel = new Label("   amplitude:");
        ampScalingLabel.setBounds(xText, yText, 90, 20);
        add(ampScalingLabel);
        xText = xText+100;
        ampScalingGroup = new CheckboxGroup();
        linBox = new Checkbox("lin", ampScalingGroup, true);
        linBox.setBounds(xText, yText, 50, 20);
        add(linBox);
        xText = xText+60;
        logBox = new Checkbox("log", ampScalingGroup, false);
        logBox.setBounds(xText, yText, 50, 20);
        add(logBox);
        
        yText = yText + 30;
        xText = nx0;
        peakFreq = 0.0; 
        peakFreqLabel = new Label("peak-frequency: " + peakFreq + " Hz");
        peakFreqLabel.setBounds(xText, yText, 200, 20);
        add(peakFreqLabel);
        
        xText = xText+210;
        cursorLabel = new Label("cursor: out of spectrum");
        cursorLabel.setBounds(xText, yText, 500, 20);
        add(cursorLabel);
        
        yText = yText + 30;
        xText = nx0;
        samplingRateLabel= new Label("sampling rate: " + sampleRate + " Hz");
        samplingRateLabel.setBounds(xText, yText, 200, 20);
        add(samplingRateLabel);
        xText = xText + 210;
        sampleSizeLabel = new Label("sample size: ");
        sampleSizeLabel.setBounds(xText, yText, 90, 20);
        add(sampleSizeLabel);
        xText = xText + 90;
        sampleSizeField = new TextField(""+ doubleBufferSize);
        sampleSizeField.setBounds(xText, yText, 80, 20);
        add(sampleSizeField);
        sampleSizeField.addActionListener(this);
        xText = xText + 80;
        sampleTimeLabel = new Label(" points (" + doubleBufferSize/sampleRate + " s)");
        sampleTimeLabel.setBounds(xText, yText, 150, 20);
        add(sampleTimeLabel);
        
        xText = xText + 170;
        freqRes = Math.round((double) sampleRate/doubleBufferSize*1000.0)/1000.0;
        freqResLabel = new Label("frequency-resolution: " + freqRes + " Hz");
        freqResLabel.setBounds(xText, yText,200,20);
        add(freqResLabel);
        
        yText = yText+30;
        xText = nx0;
        freezeButton = new Button("freeze");
        freezeButton.setBounds(xText, yText, 70, 40);
        add(freezeButton);
        freezeButton.addActionListener(this);
        
        xText = xText+80;
        storeButton = new Button("");    
        storeButton.setBounds(xText, yText, 70, 40);
        add(storeButton);
        storeButton.addActionListener(this);
        
        xText = xText+80;
        infoButton = new Button("info");
        infoButton.setBounds(xText, yText, 70, 40);
        add(infoButton);
        infoButton.addActionListener(this);
       
        xText = xText+80;
        statusLine = new Label("Status: Capturing sound");
        statusLine.setBounds(xText, yText+10, 500, 20);
        add(statusLine);
        
        addKeyListener(this);
        addMouseListener(this);
  
        // initialize buffered image
        g1.setColor(Color.red);
        g1.fillRect(0,0,nx,nyMic);
        g1.setColor(Color.green);
        g1.drawString("Microphone,  initialializing", 20, 20);
        
        g2.setColor(Color.blue);
        g2.fillRect(0,0,nx,nyFreq);
        g2.setColor(Color.yellow);
        g2.drawString("Spectrum, initializing", 20, 20);
        
        g3.setColor(Color.green);
        g3.fillRect(0,0,1,nyFreq);
        
       // captureAudio(); is called in the start()-method
 
    }  // end init ()
 
    
   
    public void start(){
      captureAudio();
    } // end start()

    
    /** update()-method for applet
     *  (overridden to avoid flicker)
     *  @param g  the Grapics object for this applet
     */
    public void update( Graphics g )  {
        // display buffered image with mic-signal, spectrum and cursor
        g.drawImage(BuffImage1, nx0, ny0Mic, null);
        g.drawImage(BuffImage2, nx0, ny0Freq, null);
        if ((iCursor >= 0) && (iCursor < nf)) {
            g.drawImage(BuffImage3, (int) (nx0+iCursor*nx/nf), ny0Freq, null);
        }
    } // end update()
    
    /**
     * Paint method for applet.
     * 
     * @param  g   the Graphics object for this applet
     */
    public void paint(Graphics g)
    {
        update( g );
    } // end paint()

    
     /**
     * ActionListener Interface method.
     * Called when action events occur with registered components that
     * can fire action events.
     * @param  ae   the ActionEvent object created by the event
     */
    public void actionPerformed(ActionEvent evt)
    {
        double temp; 
        
        // if a value for f1 is entered
        if (evt.getSource() == f1Field) {
            try { 
                f1 = Double.parseDouble(f1Field.getText()); 
                f1Field.setText(""+f1);
                statusLine.setText("Status: f1 changed to "+f1+" Hz");
                }
            catch (NumberFormatException e) {
                statusLine.setText("Status: f1 had incorrect number format");
                f1Field.setText(""+f1); // System.exit(0);
                 }
            repaint();
            }
        
       // if a value for nf is entered
        if (evt.getSource() == nfField) {
            try { 
                nf = (long) Double.parseDouble(nfField.getText()); 
                if (nf>maxIndex) {nf = maxIndex;}
                if (nf<2) {nf = 2;}
                nfField.setText(""+nf);
                statusLine.setText("Status: nf changed to "+nf);
                }
            catch (NumberFormatException e) {
                statusLine.setText("Status: nf had incorrect number format");
                nfField.setText(""+nf); // System.exit(0);
                 }
            repaint();
            }
        
        // if a value for f2 is entered
        if (evt.getSource() == f2Field) {
            try { 
                f2 = Double.parseDouble(f2Field.getText()); 
                f2Field.setText(""+f2);
                statusLine.setText("Status: f2 changed to "+f2+" Hz");
                }
            catch (NumberFormatException e) {
                statusLine.setText("Status: f2 has incorrect number format");
                f2Field.setText(""+f2); //System.exit(0);
                 }
            repaint();
            }    
            
        // if a value for doubleBufferSize (sample size, points in sample) is entered
        if (evt.getSource() == sampleSizeField) {
            if (frozen) {
                sampleSizeField.setText(""+doubleBufferSize);
                statusLine.setText("Status: sample size constant while frozen");
            }
            else {
                try { 
                    doubleBufferSize = (int) Double.parseDouble(sampleSizeField.getText()); 
                    if (doubleBufferSize>maxIndex) {doubleBufferSize = maxIndex;}
                    if (doubleBufferSize<2) {doubleBufferSize = 2;}
                    sampleSizeField.setText(""+doubleBufferSize);
                    sampleTimeLabel.setText(" points (" + doubleBufferSize/sampleRate + " s)");
                    freqRes = Math.round((double) sampleRate/doubleBufferSize*1000.0)/1000.0;
                    freqResLabel.setText("frequency-resolution: " + freqRes + " Hz");
                    statusLine.setText("Status: sample size changed to "+doubleBufferSize);
                }
                catch (NumberFormatException e) {
                    statusLine.setText("Status: sample size had incorrect number format");
                    sampleSizeField.setText(""+doubleBufferSize); // System.exit(0);
                 }
            }
            repaint();
            }    
            
        // if the freeze-button is pressed,  stops data taking or let it run again
        if (evt.getSource() == freezeButton) {
             try { 
                if (frozen) {
                    frozen = false;
                    freezeButton.setLabel("freeze");
                    statusLine.setText("Status: capturing sound");
                    storeButton.setLabel("");
                }
                else {
                    frozen = true;
                    freezeButton.setLabel(" run ");
                    statusLine.setText("Status: displaying captured data-set");
                    storeButton.setLabel("store");
                }
                f1Field.setText(""+f1);
                nfField.setText(""+nf);
                f2Field.setText(""+f2);
                repaint();
                }
            catch (NumberFormatException e) {
                System.exit(0);
                 }
            }
        
        // if the storeButton is pressed   
        if (evt.getSource() == storeButton) {
            if (frozen) { 
            
            try {
                storeFlag = true;
                }
             catch (Exception e) {
                statusLine.setText("Status: ERROR");
                System.exit(0);
                }
            }
            }  
            
        // if the infoButton is pressed   
        if (evt.getSource() == infoButton) {
            
            try {
                showInfo();
                repaint();
                }
             catch (Exception e) {
                 statusLine.setText("Status: ERROR");
                System.exit(0);
                }
            }      
    } // end actionPerformed()
 
    
    private void showInfo()
    {
        String newline = System.getProperty("line.separator");
        Frame infoFrame = new Frame("informations");
        infoFrame.addWindowListener(new WindowAdapter()
        {
            public void windowClosing(WindowEvent we)
            {
                we.getWindow().dispose();  // pressing the close-button closes the window
            }
        });
        infoFrame.setVisible(true);
        TextArea textArea = new TextArea(60, 100);
        textArea.setEditable(false);

        textArea.append("SpectrumAnalyzer"+newline);
        textArea.append(" "+newline);
        textArea.append("Takes the signal from the microphone and calculates its frequency content with a Fourier-transform."+newline);
        textArea.append(" "+newline);
        textArea.append("top window: microphone-signal (soundcard) multiplied by filter-function (scaled to fit in window)"+newline);
        textArea.append("below: calculated amplitude versus frequency (discrete Fourier transform; scaled to fit in window)"+newline);
        textArea.append(" "+newline);
        textArea.append("f1: lower end of frequency-band (inclusive)"+newline);
        textArea.append("f2: upper end of frequency-band (exclusive)"+newline);
        textArea.append("nf: number of calculated amplitudes in frequency-band"+newline);
        textArea.append("    (type <enter> or <return> after entering a new number!)"+newline);
        textArea.append(" "+newline);
        textArea.append("zero-checkbox: arithmetic mean of sampled values is set to zero"+newline);
        textArea.append(" "+newline);
        textArea.append("filter-buttons:"+newline);
        textArea.append("       rectangular: filter-function = 1 (no filter)"+newline);
        textArea.append("       von Hann: filter function = 0.5 - 0.5*cos(...), suppresses side lobes"+newline);
        textArea.append(" "+newline);
        textArea.append("amplitude-buttons:"+newline);
        textArea.append("       lin: amplitude-spectrum, linear scaling, maximum = 100 percent, minimum = 0"+newline);
        textArea.append("       log: power-spectrum, logarithmic scaling, maximum = 0.0 decibel, minimum = -100 dB"+newline);
        textArea.append(" "+newline);
        textArea.append("peak-frequency: where the maximum in the spectrum is"+newline);
        textArea.append(" "+newline);
        textArea.append("cursor appears after a mouse-click into spectrum-display"+newline);
        textArea.append("       arrow left-key shifts cursor to the left, arrow right-key to the right"+newline);
        textArea.append("       arrow up-key shifts cursor to the maximum, arrow down-key out of display"+newline);
        textArea.append(" "+newline);
        textArea.append("sampling rate: collected points per second in the sample"+newline);
        textArea.append(" "+newline);
        textArea.append("sample size: number of points collected per sample"+newline);
        textArea.append("           (temporal size:   sample size / sampling rate)"+newline);
        textArea.append(" "+newline);
        textArea.append("frequency-resolution: 1/temporal size of sample"+newline);
        textArea.append(" "+newline);
        textArea.append("freeze-button: stops taking new data from microphone"+newline);
        textArea.append(" "+newline);
        textArea.append("store-button: saves frequencies and (linear) amplitudes as Freq-Ampl-XXXXX.txt"+newline);
        textArea.append("   (rename the file, it is in the same directory as the application)"+newline);
        textArea.append(" "+newline);
        textArea.append("status-line: displays messages to the user"+newline);
        textArea.append(" "+newline);
        textArea.append(" "+newline);
        textArea.append("known problems:"+newline);
        textArea.append("nonsense-values are not prohibited, i.e. f2 < f1 or f2 > sample rate/2,"+newline);
        textArea.append("     only values that lead to a crash are excluded. "+newline);
        textArea.append("cursor reacts to arrow left/right key only after an appropriate mouse-click."+newline);
        //textArea.append("known problems:"+newline);
        //textArea.append("known problems:"+newline);
        textArea.append(" "+newline);        
        textArea.append("copyright: Martin Lieberherr, July 2012"+newline);
        textArea.append("SpectrumAnalyzer may be used free of charge for educational and other non-commercial purposes."+newline);
        textArea.append("Use at your own risk, any liability is excluded."+newline);
        textArea.append(" "+newline);
        
        infoFrame.add(textArea, BorderLayout.CENTER);
        infoFrame.pack();
    } // end showInfo()
    
    
    
    /**
     * Returns information about this applet. 
     * An applet should override this method to return a String containing 
     * information about the author, version, and copyright of the Applet.
     *
     * @return a String representation of information about this Applet
     */
    public String getAppletInfo()
    {
        // provide information about the applet
        return "Title:SpectrumAnalyzer   \nAuthor: Martin Lieberherr  \nDetects microphone signal and displays its Fourier spectrum. \nJuly 30th, 2012 ";
    }
    
    
    public void mouseEntered( MouseEvent e ) {
      // called when the pointer enters the applet's rectangular area
   }
   
   public void mouseExited( MouseEvent e ) {
      // called when the pointer leaves the applet's rectangular area
   }
   
   public void mouseClicked( MouseEvent e ) {
      // called after a press and release of a mouse button
      // with no motion in between
      // (If the user presses, drags, and then releases, there will be
      // no click event generated.)
   }
   
   public void mousePressed( MouseEvent e ) {  // called after a mouse button is pressed down
      int mouseX = e.getX(); // mouse-coordinates in pixels
      int mouseY = e.getY(); 
     
      if ((mouseY >= ny0Freq) && (mouseY < ny0Freq+nyFreq) && (mouseX >= nx0) && (mouseX < nx0+nx))  {
          iCursor = (int) Math.round( (mouseX-nx0)*nf/nx);
          
        }
        
   }
   public void mouseReleased( MouseEvent e ) {  
       // called after a button is released
    }
       
    public void keyTyped(KeyEvent evt) {
          // The user has typed a character
          // empty method required by KeyListener
          
      // char ch = evt.getKeyChar();  // The character typed.

      //if (ch == 'B' || ch == 'b') {
      //   squareColor = Color.blue;
      //   repaint();}
      
   }  // end keyTyped()
   
    
   public void keyPressed(KeyEvent evt) { 
          // Called when the user has pressed a key, which can be
          // a special key such as an arrow key.  
          
      int key = evt.getKeyCode();  // keyboard code for the key that was pressed
      
      if (key == 37) {  // (arrow left)
         iCursor = iCursor - 1;
        }
      if (key == 39) { // (arrow right)
            iCursor = iCursor+1; 
        }
      if (key == 40) { // (arrow down)
            iCursor = -1; // out of drawing-window
        }
      if (key == 38) { // (arrow up)
            // iCursor = -1; // out of drawing-window
            iCursor = (int) Math.round((peakFreq-f1)/(f2-f1)*nf); // to the maximum
        }
      repaint();
   }  // end keyPressed()


   public void keyReleased(KeyEvent evt) { 
      // empty method, required by the KeyListener Interface
   }
    
   
   
   // This method sets up AudioFormat and TargetDataLine to capture sounds from the microphone (sound card)
   // It starts the captureThread.
  private void captureAudio(){
    try{
      //Get everything set up for capture
      audioFormat = new AudioFormat(sampleRate,sampleSizeInBits,channels,signed,bigEndian);
      DataLine.Info dataLineInfo =new DataLine.Info(TargetDataLine.class,audioFormat);
      targetDataLine = (TargetDataLine) AudioSystem.getLine(dataLineInfo);
      targetDataLine.open(audioFormat);
      targetDataLine.start();
      // start Thread
      Thread captureThread = new Thread(new CaptureThread());
      captureThread.start();
    } catch (Exception e) {
      System.out.println(e);
      System.exit(0);
    }//end catch
  }//end captureAudio method
  
  
 // saves spectral data in a text-file
public  void SaveAsTextFile()
{
  String newline = System.getProperty("line.separator"); 
  try {
    BufferedWriter out = new BufferedWriter(new FileWriter("Freq-Ampl-"+System.currentTimeMillis()+".txt"));
    out.write("frequency (Hz) " + "\t" +"amplitude"+newline);  // "\t" is a tabulator
    for (int i=0; i<nf; i++) {
        out.write("" + ((double) i*(f2-f1)/nf+f1) + "\t" + amplitude[i] +newline);
    }
    out.close();
  } 
  catch (IOException e) {
      System.out.println(e);
      System.exit(0);
  }
}
  
  
  //Inner class to capture data from microphone resp. sound card
class CaptureThread extends Thread{
  int cnt;
  double omega, dt;
  double ySin, yCos, cosi, sinu, cosDt, sinDt;
  double temp1, temp2;
  double micMean, micMax, micMin;
  double ampMax, ampMin, ampScale;
  
  public void run(){

    try{
        
      while ( true ) {
        //Read data from the internal buffer of the data line while frozen = false.
        if ( !frozen ) {
            cnt = targetDataLine.read(tempBuffer,0,doubleBufferSize*2);  // read out soundcard 
            // int cnt = targetDataLine.read(tempBuffer,0,tempBuffer.length);
        }
        
        for (int i=0; i<(doubleBufferSize-1); i++) {
            // convert 16 bit signed byte little endian into double normalized between -32768...+32767
            doubleBuffer[i] = (double) (  (tempBuffer[i+i + 0] & 0xFF) | (tempBuffer[i+i + 1] << 8) );
        } 
        // test-signal
        // for (int i=0; i<(doubleBufferSize-1); i++) {
            //   doubleBuffer[i] = (double) 1.0*Math.sin(2*Math.PI*1000.0*i/sampleRate) + 0.5*Math.sin(2*Math.PI*1100.0*i/sampleRate) + 3;
        // }
        micMean = 0.0;
        if (zeroBox.getState()) {
            for (int i=0; i<(doubleBufferSize-1); i++) {
                micMean = micMean+doubleBuffer[i];
            }
            micMean = micMean/doubleBufferSize;
            for (int i=0; i<(doubleBufferSize-1); i++) {
                doubleBuffer[i] =doubleBuffer[i] - micMean;
            }
        }
          
        if (hannBox.getState()) {
            for (int i=0; i<(doubleBufferSize-1); i++) {
                // apply a Hann filter 
                doubleBuffer[i] = doubleBuffer[i]*(0.5-0.5*Math.cos(Math.PI*2*i/doubleBufferSize));
            }
        }
        //         if (hammingBox.getState()) {
                //             for (int i=0; i<(doubleBufferSize-1); i++) {
                //                 // apply a Hamming filter 
                //                 doubleBuffer[i] = doubleBuffer[i]*(0.54-0.46*Math.cos(Math.PI*2*i/doubleBufferSize));
                //             }
                //         }
                //         
        //         if (gaussBox.getState()) {
                //             for (int i=0; i<(doubleBufferSize-1); i++) {
                //                 // apply a gaussian filter 
                //                 temp1 = (2.0*i/doubleBufferSize - 1)/0.49;
                //                 doubleBuffer[i] = doubleBuffer[i]*Math.exp(-0.5*temp1*temp1);
                //             }
                //         }
        micMax = 1.0;
        micMin = -1.0;
        for (int i=0; i<(doubleBufferSize-1); i++) {
            if (doubleBuffer[i] > micMax ) {micMax = doubleBuffer[i];}  // store maximum elongation
            if (doubleBuffer[i] < micMin ) {micMin = doubleBuffer[i];}  // store minimum elongation
        }
        // draw microphone signal
        g1.setColor(Color.white);
        g1.fillRect(0,0,nx,nyMic);
        g1.setColor(Color.red);
        temp1 = (double) nx/doubleBufferSize;
        temp2 = (double) nyMic/(micMax-micMin);
        for (int i=0; i<(doubleBufferSize-1); i++) {
            g1.drawLine( (int) (temp1*i), (int) (nyMic-temp2*(doubleBuffer[i]-micMin)),(int) (temp1*(i+1)), (int) (nyMic-temp2*(doubleBuffer[i+1]-micMin)));
        }
        
        // calculate discrete Fourier transform (DFT, not an FFT !) 
        ampMax = 1e-60;
        ampMin = 0.1;
        dt = 1 / ((double) sampleRate);
        for (int i=0; i<nf; i++) {
            omega = 2.0*Math.PI*( (double) (f1+(f2-f1)*i/nf) );
            cosi = 1; // Math.cos(omega*t) for t = 0;
            sinu = 0; //Math.sin(omega*t) for t = 0;
            cosDt = Math.cos(omega*dt);
            sinDt = Math.sin(omega*dt);
            ySin = 0; 
            yCos = 0; 
            for (int j = 0; j<doubleBufferSize; j++ ) {
                ySin = ySin + doubleBuffer[j]*sinu;
                yCos = yCos + doubleBuffer[j]*cosi;
                temp1 = sinu*cosDt + cosi*sinDt;
                cosi = cosi*cosDt - sinu*sinDt;
                sinu = temp1;
            }
            amplitude[i] = Math.sqrt(yCos*yCos+ySin*ySin); // amplitude-spectrum
            // amplitude[i] = yCos*yCos+ySin*ySin; // power-spectrum
            if (amplitude[i] > ampMax) { ampMax = amplitude[i]; peakFreq = ( (double) i)/nf*(f2-f1)+f1; }
            if (amplitude[i] < ampMin) { ampMin = amplitude[i]; }
        }
        // scale amplitude to max = 1
        for (int i=0; i<nf; i++) {
            amplitude[i] = amplitude[i]/ampMax;
        }     
        ampMin = ampMin/ampMax;
        if (storeFlag) {
            SaveAsTextFile();
            storeFlag = false;
        }
        // display peak-frequency
        peakFreq = Math.round(peakFreq*1000.0)/1000.0;
        peakFreqLabel.setText("peak-frequency: " + peakFreq + " Hz");
        // plot spectrum,  linear or logarithmic 
        g2.setColor(Color.white);
        g2.fillRect(0,0,nx,nyFreq);
        g2.setColor(Color.blue);
        if (linBox.getState()) {  //  for linear display of amplitudes
            ampScale = (double) (nyFreq-2);
            for (int i=0; i<nf-1; i++) {
                g2.drawLine( (int) (i*nx/nf), (int) (nyFreq-1-ampScale*amplitude[i]), (int) ((i+1)*nx/nf), (int) (nyFreq-1-ampScale*amplitude[i+1]) );
            }
        }
        if (logBox.getState()) { // for logarithmic display of amplitudes
            ampScale = 20.0/Math.log(10);  // for power-spectrum from amplitudes
            ampMin = -10;
            if (zeroBox.getState()) {logAmpl[0] = -100;}
            else {logAmpl[0] = ampScale*Math.log(amplitude[0]); if (logAmpl[0]<ampMin) {ampMin=logAmpl[0];}}
            for (int i=1; i<nf; i++) {
                if (amplitude[i] > 0) {logAmpl[i] = ampScale*Math.log(amplitude[i]);}
                else {logAmpl[i] = -100;}
                if (logAmpl[i] < ampMin) {ampMin = logAmpl[i];}
            }
            ampScale = (nyFreq-2)/ampMin;
            for (int i=0; i<nf-1; i++) {
                g2.drawLine( (int) (i*nx/nf), (int) (ampScale*logAmpl[i]), (int) ((i+1)*nx/nf), (int) (ampScale*logAmpl[i+1]) );
            }
        }
        // display cursor-coordinates (freqency, amplitude log or lin)
        if ((iCursor >= 0) && (iCursor < nf) ) {
            temp1 = (double) iCursor*(f2-f1)/nf+f1;
            temp1 = (double) Math.round(temp1*100.0)/100.0;
            if (linBox.getState()) { 
              temp2 = amplitude[iCursor];
              temp2 = (double) Math.round(temp2*1e5)/1e3;
              cursorLabel.setText("cursor: " + temp1 + " Hz,  " + temp2 + " %");
            }
            else {
              temp2 = logAmpl[iCursor];
              temp2 = (double) Math.round(temp2*1e5)/1e5;
              cursorLabel.setText("cursor: " + temp1 + " Hz,  " + temp2 + " dB");
            }
        }
        else {
            cursorLabel.setText("cursor: out of spectrum, click / press arrows");
        }
        repaint();
        sleep(20);
      }//end while
    }catch (Exception e) {
      System.out.println(e);
      System.exit(0);
    }//end catch
  }//end run
}//end inner class CaptureThread

}
