/******************************************************************************
 *                                                                            *
 * Copyright (c) 1999-2004 Wimba S.A., All Rights Reserved.                   *
 *                                                                            *
 * COPYRIGHT:                                                                 *
 *      This software is the property of Wimba S.A.                           *
 *      This software is redistributed under the Xiph.org variant of          *
 *      the BSD license.                                                      *
 *      Redistribution and use in source and binary forms, with or without    *
 *      modification, are permitted provided that the following conditions    *
 *      are met:                                                              *
 *      - Redistributions of source code must retain the above copyright      *
 *      notice, this list of conditions and the following disclaimer.         *
 *      - Redistributions in binary form must reproduce the above copyright   *
 *      notice, this list of conditions and the following disclaimer in the   *
 *      documentation and/or other materials provided with the distribution.  *
 *      - Neither the name of Wimba, the Xiph.org Foundation nor the names of *
 *      its contributors may be used to endorse or promote products derived   *
 *      from this software without specific prior written permission.         *
 *                                                                            *
 * WARRANTIES:                                                                *
 *      This software is made available by the authors in the hope            *
 *      that it will be useful, but without any warranty.                     *
 *      Wimba S.A. is not liable for any consequence related to the           *
 *      use of the provided software.                                         *
 *                                                                            *
 * Class: Recorder.java                                                       *
 *                                                                            *
 * Author: Marc GIMPEL                                                        *
 *                                                                            *
 * Date: Jun 1, 2004                                                          *
 *                                                                            *
 ******************************************************************************/

/* $Id: Recorder.java,v 1.2 2004/10/21 16:21:58 mgimpel Exp $ */

package org.xiph.speex.player;

import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.TargetDataLine;
import javax.swing.JButton;
import javax.swing.JFrame;

import org.xiph.speex.spi.SpeexEncoding;

/**
 * JavaSound Recorder.
 *
 * @author Marc Gimpel, Wimba S.A. (mgimpel@horizonwimba.com)
 * @version $Revision: 1.2 $
 */
public class Recorder
  extends Player
{
  /** Revision Number */
  public static final String REVISION = "$Revision: 1.2 $";

  /** Audio sampled at 8 kHz (telephone quality). */
  public static final String SAMPLERATE_8KHZ  =  "8000 Hz";
  /** Audio sampled at 11 kHz. */
  public static final String SAMPLERATE_11KHZ = "11025 Hz";
  /** Audio sampled at 16 kHz (wideband). */
  public static final String SAMPLERATE_16KHZ = "16000 Hz";
  /** Audio sampled at 22 kHz (FM radio quality). */
  public static final String SAMPLERATE_22KHZ = "22050 Hz";
  /** Audio sampled at 32 kHz (ultra-wideband). */
  public static final String SAMPLERATE_32KHZ = "32000 Hz";
  /** Audio sampled at 44 kHz (CD quality). */
  public static final String SAMPLERATE_44KHZ = "44100 Hz";
  /** Mono Audio (1 channel). */
  public static final String CHANNELS_MONO    = "mono";
  /** Stereo Audio (2 channels). */
  public static final String CHANNELS_STEREO  = "stereo";

  // Possible States for the Finite State Machine 
  /** Finite State Machine State: Recording */
  protected static final int STATE_RECORDING  = 6;
  /** Finite State Machine State: Recording Paused */
  protected static final int STATE_REC_PAUSED = 7;

  /** Record Button */
  protected JButton recordButton;

  protected Capture capture;
  
  protected byte[] audio;

  //--------------------------------------------------------------------------
  // Initialization code
  //--------------------------------------------------------------------------

  /**
   * Command Line entrance.
   * @param args
   */
  public static void main(final String[] args)
  {
    String filename = null;
    if (args.length > 0) {
      filename = args[0];
    }
    final Recorder recorder = new Recorder(filename);
    recorder.init();
    JFrame frame = new JFrame("Recorder");
    frame.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {System.exit(0);}
      public void windowDeiconified(WindowEvent e) { recorder.start(); }
      public void windowIconified(WindowEvent e) { recorder.stop(); }
    });
    frame.getContentPane().add("Center", recorder);
    frame.pack();
    frame.setVisible(true);
  }

  /**
   * Build a Recorder.
   * @param file
   */
  public Recorder(final String file)
  {
    super(file);
  }

  /**
   * Initialize the Player Component.
   */
  public void init()
  {
    super.init();
    capture = new Capture(); 
  }

  /**
   * Returns an InputSteam containing the Audio to playback.
   * @return an InputSteam containing the Audio to playback.
   * @throws IOException
   */
  protected InputStream getAudioStream()
    throws IOException
  {
    if (audio == null) {
      return new BufferedInputStream(audioFile.openStream());
    }
    else {
      return new ByteArrayInputStream(audio);
    }
  }

  //--------------------------------------------------------------------------
  // Running code
  //--------------------------------------------------------------------------

  /**
   * Capture thread
   */
  protected class Capture
    implements Runnable
  {
    protected ByteArrayOutputStream out;
    protected AudioInputStream audioInputStream;
    protected AudioFormat audioFormat;
    protected DataLine.Info info;
    protected AudioFileFormat.Type targetType;
    protected TargetDataLine line;
    protected byte[] buffer;
    
    protected Thread thread;

    /**
     * Start the playback thread which fills the JavaSound playback buffer.
     */
    protected void start() {
      thread = new Thread(this);
      thread.setName("Capture");
      thread.start();
    }

    /**
     * Stop the playback thread and destroy all resources.
     */
    protected void stop() {
      // Stop the thread
      thread = null;
      // Close the line
      if (line != null) {
        line.stop();
        line.close();
        line = null;
      }
      // stop and close the output stream
      try {
        out.flush();
        out.close();
      } catch (IOException ex) {
        ex.printStackTrace();
      }
      // load bytes into the audio input stream for playback
      audio = out.toByteArray();
System.out.println("size="+audio.length);
    }

    /**
     * Setup the JavaSound System to play the Audio.
     */
    protected void setupSound()
    {
      // define the required attributes for our line, 
      // and make sure a compatible line is supported.
      audioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
                                    44100.0F, 16, 1, 2, 44100.0F, false);
      /*
      Now, we are trying to get a TargetDataLine. The TargetDataLine is used
      later to read audio data from it.
      If requesting the line was successful, we are opening it (important!).
      */
      info = new DataLine.Info(TargetDataLine.class, audioFormat);
      try {
        line = (TargetDataLine) AudioSystem.getLine(info);
        line.open(audioFormat);
      }
      catch (LineUnavailableException e) {
        e.printStackTrace();
      }
      catch (SecurityException e) {
        // An applet requires the following permissions to record audio:
        // permission javax.sound.sampled.AudioPermission : record
        e.printStackTrace();
      }
      audioInputStream = new AudioInputStream(line);
      AudioFormat targetFormat = new AudioFormat(SpeexEncoding.SPEEX_Q6,
                                                 audioFormat.getSampleRate(),
                                                 -1, // sample size in bits
                                                 audioFormat.getChannels(),
                                                 -1, // frame size
                                                 -1, // frame rate
                                                 false); // little endian
      audioInputStream = AudioSystem.getAudioInputStream(targetFormat, audioInputStream);
      audioFormat = audioInputStream.getFormat();
      out = new ByteArrayOutputStream();
      // setup copying buffer
      buffer = new byte[128000];
    }
    
    /**
     * The code that runs in the thread and recovers the JavaSound capture
     * buffer.
     * Implemented from Runnable interface.
     */
    public void run()
    {
      int read = 0;
      while (thread != null && state == STATE_RECORDING && read != -1) {
        try {
          read = audioInputStream.read(buffer, 0, buffer.length);
        }
        catch (IOException e) {
          e.printStackTrace();
        }
        if (read > 0) {
          out.write(buffer, 0, read);
        }
      }
    }
  } // End class Capture

  //--------------------------------------------------------------------------
  // Action processing code
  //--------------------------------------------------------------------------

  /**
   * Process Actions when button are pressed.
   * Implemented from ActionListener interface.
   */
  public void actionPerformed(final ActionEvent e) {
    if (e.getSource() == timer) {
      progressBar.setValue(getProgress());
    }
    else {
      if ("Play".equals(e.getActionCommand())) {
        playIt();
      }
      else if ("Record".equals(e.getActionCommand())) {
        recordIt();
      }
      else if ("Pause".equals(e.getActionCommand())) {
        if (state == STATE_PAUSED) {
          playIt();
        }
        else if (state == STATE_REC_PAUSED) {
          recordIt();
        }
        else {
          pauseIt();
        }
      }
      else if ("Stop".equals(e.getActionCommand())) {
        stopIt();
      }
      else {
      }
    }
  }
  
  /**
   *
   */
  public synchronized void stopIt()
  {
    recordButton.setEnabled(false);
    playButton.setEnabled(false);
    pauseButton.setEnabled(false);
    stopButton.setEnabled(false);
    oldstate = state;
    state = STATE_STOPPED;
    if (oldstate == STATE_PLAYING || oldstate == STATE_PAUSED) {
      playback.stop();
    }
    else if (oldstate == STATE_RECORDING || oldstate == STATE_REC_PAUSED) {
      capture.stop();
    }
    timer.stop();
    progressBar.setValue(0);
    recordButton.setEnabled(true);
    playButton.setEnabled(true);
    pauseButton.setEnabled(false);
    stopButton.setEnabled(false);
  }

  /**
   *
   */
  public synchronized void playIt()
  {
    recordButton.setEnabled(false);
    playButton.setEnabled(false);
    pauseButton.setEnabled(false);
    stopButton.setEnabled(false);
    oldstate = state;
    state = STATE_PLAYING;
    if (oldstate == STATE_STOPPED) {
      playback.setupSound();
    }
    if (playback.thread == null || !playback.thread.isAlive()) {
      playback.start();
    }
    playback.line.start();
    timer.start();
    recordButton.setEnabled(false);
    playButton.setEnabled(false);
    pauseButton.setEnabled(true);
    stopButton.setEnabled(true);
  }

  /**
   *
   */
  public synchronized void pauseIt()
  {
    recordButton.setEnabled(false);
    playButton.setEnabled(false);
    pauseButton.setEnabled(false);
    stopButton.setEnabled(false);
    oldstate = state;
    if (oldstate == STATE_PLAYING) {
      state = STATE_PAUSED;
      playback.line.stop();
      recordButton.setEnabled(false);
      playButton.setEnabled(true);
    }
    else if (oldstate == STATE_RECORDING) {
      state = STATE_REC_PAUSED;
      capture.line.stop();
      recordButton.setEnabled(true);
      playButton.setEnabled(false);
    }
    timer.stop();
    pauseButton.setEnabled(true);
    stopButton.setEnabled(true);
  }

  /**
   *
   */
  public synchronized void recordIt()
  {
    recordButton.setEnabled(false);
    playButton.setEnabled(false);
    pauseButton.setEnabled(false);
    stopButton.setEnabled(false);
    oldstate = state;
    state = STATE_RECORDING;
    if (oldstate == STATE_STOPPED) {
      capture.setupSound();
    }
    if (capture.thread == null || !capture.thread.isAlive()) {
      capture.start();
    }
    capture.line.start();
    timer.start();
    recordButton.setEnabled(false);
    playButton.setEnabled(false);
    pauseButton.setEnabled(true);
    stopButton.setEnabled(true);
  }

  /**
   * Return the progress of the playback.
   * @return the progress of the playback.
   */
  protected int getProgress()
  {
    audioLength = 500000;
    if (state == STATE_PLAYING || state == STATE_PAUSED) {
      return super.getProgress();
    }
    else if (state == STATE_RECORDING || state == STATE_REC_PAUSED) {
      return capture.line.getFramePosition() * 1000 / audioLength;
    }
    else {
      return 0;
    }
  }
  //--------------------------------------------------------------------------
  // GUI code
  //--------------------------------------------------------------------------
  
  /**
   * Create the ButtonPanel for the recorder.
   * The recorder button panel that should look something like this:
   * <pre> 
   * +--------------------------------+
   * | +------+ +----+ +-----+ +----+ |
   * | |record| |play| |pause| |stop| |  Button Panel
   * | +------+ +----+ +-----+ +----+ |
   * +--------------------------------+
   * </pre>
   */
  protected void createButtonPanel()
  {
    recordButton = buildButton("Record", "Record",
                               "/images/player_record.gif",
                               "/images/player_record2.gif",
                               "/images/player_record3.gif", this);
    playerButtonPane.add(recordButton);
    super.createButtonPanel();
  }
}
