/* $Id$
 *
 * Copyright (c) 1999 Xenonsoft Limited. All Rights Reserved.
 *
 * This software is protected by copyright. You are hereby notified from
 * now by reading this message. This software is also the confidential 
 * and proprietary information of Xenonsoft Limited.  ("Confidential
 * Information"). 
 *
 * This software is distributed under the Xenonsoft Public end user 
 * License ("XPeL"), where the machine-readable source code is provided 
 * under the "Open Source" model.
 * For more information, please read the file "LICENSE-XPL.txt" 
 */

// GuiTestSound.java

//
// Description:
//	Swing GUI Test Harness for Sound Engine
//	  
// Author:
//	Peter Pilgrim
//	Thu Jun 17 20:47:13 BST 1999
//	  
// RCS HEADER 
// 
// $Author$
// $Date$
// $Source$
// $Revision$    $State$    $Locker$
//
// ================================================================================
// History
//	  $Log$
// 
// 
//

package xenon.soundkit.gui;

import java.io.FileWriter;
import java.io.PrintWriter;
import java.io.File;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.util.Hashtable;
import java.util.Enumeration;
import java.awt.*;				// for Frame, Dialog, Component
import java.awt.event.*;			// for ActionEvent, ActionListener
import java.awt.datatransfer.*;			// for Clipboard, StringSelection
import javax.swing.*;				// for JButton, JPanel et al
import javax.swing.event.*;			// for ChangeEvent
import javax.swing.border.*;			// for TitledBorder


import xenon.soundkit.*;			// for SoundEngine, PPMCAudioPlayer

public class GuiTestSound extends JPanel 
implements ActionListener, ClipboardOwner
{
    public final static WaveTuple[]  waveTupleList = {
	new WaveTuple( "Sine Wave", 
		       "StaticWave.SINE_WAVE",
		       StaticWave.SINE_WAVE ),
	new WaveTuple( "Square Wave", 
		       "StaticWave.SQUARE_WAVE",
		       StaticWave.SQUARE_WAVE ),
	new WaveTuple( "Triangle Wave", 
		       "StaticWave.TRIANGLE_WAVE",
		       StaticWave.TRIANGLE_WAVE ),
	new WaveTuple( "Ramp Forward Wave", 
		       "StaticWave.RAMP_FORWARD_WAVE",
		       StaticWave.RAMP_FORWARD_WAVE ),
	new WaveTuple( "Ramp Backward Wave", 
		       "StaticWave.RAMP_BACKWARD_WAVE",
		       StaticWave.RAMP_BACKWARD_WAVE ),
	new WaveTuple( "Periodic White Noise Wave", 
		       "StaticWave.PERIODIC_WHITE_NOISE_WAVE",
		       StaticWave.PERIODIC_WHITE_NOISE_WAVE ),
	new WaveTuple( "White Noise Wave", 
		       "new WhiteNoiseWave()",
		       new WhiteNoiseWave() ),
    };
    
    protected PPMCAudioPlayer	audioPlayer;

    protected JButton		bt_play;
    protected JButton		bt_make;
    protected JButton		bt_quit;
    protected JButton		bt_copy;
    protected JButton		bt_save;
    
    protected SliderPane	pitchPane1;
    protected SliderPane	pitchPane2;
    protected SliderPane	pitchPane3;

    protected SliderPane	attackPane;
    protected SliderPane	decayPane;
    protected SliderPane	sustainPane;
    protected SliderPane	releasePane;

    protected SimpleSliderPane	timePane;
    protected SimpleSliderPane	volumePane;
    protected SimpleSliderPane	freqPane;
    protected SimpleSliderPane	durationPane;
    
    protected JCheckBox		cb_hold_pitch_mod;
    protected JCheckBox		cb_hold_amp_mod;
    protected JCheckBox		cb_noise_gate_flag;
    protected JCheckBox		cb_noise_mix_gate_flag;
    protected JCheckBox		cb_fm_delta_random_flag;
    protected JComboBox		combo_wave;
    
    protected WholeNumberField	tf_pitch_step_unit;
    protected WholeNumberField	tf_amplitude_step_unit;

    protected File		saveFile=new File("sndout.java");
    protected JTextArea		ta_output;
    protected Envelope		envelope=null;
    protected SoundEngine	sound=null;
    
    protected Clipboard		clipboard;
    protected Hashtable		waveMap;
    
    public GuiTestSound( PPMCAudioPlayer audioPlayer )
    {
	this.audioPlayer = audioPlayer;

	this.clipboard = getToolkit().getSystemClipboard();
	
	// Build the wave form table map
	waveMap = new Hashtable();
	for (int k=0; k<waveTupleList.length; ++k )
	    waveMap.put( waveTupleList[k].name, waveTupleList[k] );
	
	Font buttonFont = new Font( "Dialog", Font.BOLD, 10 );
	
	setLayout( new VerticalLayout( VerticalLayout.TOP ));

	// ======================================================================
	// Envelope Controls
	// ======================================================================
	JPanel gridPanel = new JPanel();
	gridPanel.setLayout( new GridLayout( 4, 2 ));

	pitchPane1 = new SliderPane( 
	    "Pitch1", 
	    Envelope.PITCH_MIN_DELTA, 0,   Envelope.PITCH_MAX_DELTA,
	    Envelope.PITCH_MIN_STEP,  128, Envelope.PITCH_MAX_STEP );
	gridPanel.add( pitchPane1 );

	attackPane = new SliderPane( 
	    "Attack", 
	    Envelope.ATTACK_MIN_DELTA, 128, Envelope.ATTACK_MAX_DELTA,
	    Envelope.ATTACK_MIN_STEP,  1,   Envelope.ATTACK_MAX_STEP );
	gridPanel.add( attackPane );
	
	pitchPane2 = new SliderPane( 
	    "Pitch2", 
	    Envelope.PITCH_MIN_DELTA, 0,   Envelope.PITCH_MAX_DELTA,
	    Envelope.PITCH_MIN_STEP,  128, Envelope.PITCH_MAX_STEP );
	gridPanel.add( pitchPane2 );

	decayPane = new SliderPane( 
	    "Decay", 
	    Envelope.DECAY_MIN_DELTA, 0,   Envelope.DECAY_MAX_DELTA,
	    Envelope.DECAY_MIN_STEP,  128, Envelope.DECAY_MAX_STEP );
	gridPanel.add( decayPane );
	
	pitchPane3 = new SliderPane( 
	    "Pitch3",
	    Envelope.PITCH_MIN_DELTA, 0,   Envelope.PITCH_MAX_DELTA,
	    Envelope.PITCH_MIN_STEP,  128, Envelope.PITCH_MAX_STEP );
	gridPanel.add( pitchPane3 );

	sustainPane = new SliderPane( 
	    "Sustain",
	    Envelope.SUSTAIN_MIN_DELTA, 0,   Envelope.SUSTAIN_MAX_DELTA,
	    Envelope.SUSTAIN_MIN_STEP,  128, Envelope.SUSTAIN_MAX_STEP );
	gridPanel.add( sustainPane );

	JPanel buttonPanel = new JPanel();
	buttonPanel.setLayout( new FlowLayout( FlowLayout.LEFT ));

	bt_play = new JButton( "Play" );
	bt_play.setFont( buttonFont );
	bt_play.setToolTipText( "Play synthesised sound   " );
	buttonPanel.add( bt_play );
	bt_make = new JButton( "Make" );
	bt_make.setFont( buttonFont );
	bt_make.setToolTipText( "Create synthesised sound   " );
	buttonPanel.add( bt_make );
	bt_quit = new JButton( "Quit" );
	bt_quit.setFont( buttonFont );
	bt_quit.setToolTipText( "Quit test harness application    " );
	buttonPanel.add( bt_quit );
	gridPanel.add( buttonPanel );
	
	releasePane = new SliderPane( 
	    "Release", 
	    Envelope.RELEASE_MIN_DELTA, -1,   Envelope.RELEASE_MAX_DELTA,
	    Envelope.RELEASE_MIN_STEP,  128,  Envelope.RELEASE_MAX_STEP );
	gridPanel.add( releasePane );
	
	add( gridPanel );

	add( new JSeparator() );
	
	// ======================================================================
	// Sound Engine Controls
	// ======================================================================
	gridPanel = new JPanel();
	gridPanel.setLayout( new GridLayout( 2, 2 ));

	timePane = new SimpleSliderPane( "Sample Time Duration", 1, 5, 20 );
	gridPanel.add( timePane );

	volumePane = new SimpleSliderPane( 
	    "Volume", 
	    SoundEngine.MIN_VOLUME, 127, SoundEngine.MAX_VOLUME);
	gridPanel.add( volumePane );

	// Nyquist frequency = Sample Rate / 2 (see below)
	freqPane = new SimpleSliderPane( 
	    "Frequency", 
	    SoundEngine.MIN_FREQUENCY, 127, 4000 );
	gridPanel.add( freqPane );

	durationPane = new SimpleSliderPane( "Duration", 0, 80, 640 );
	gridPanel.add( durationPane );
	add( gridPanel );
	add( new JSeparator() );

	// ======================================================================
	// Envelope Step Unit Controls
	// ======================================================================

	// Pitch step unit text field
	Box horzBox = Box.createHorizontalBox();
	
	buttonPanel = new JPanel();
	buttonPanel.setLayout( new BorderLayout(1,1));
	TitledBorder titledBorder1 = new TitledBorder( "PSU");
	titledBorder1.setTitleFont( new Font( "SanSerif", Font.PLAIN, 10 ));
	tf_pitch_step_unit = new WholeNumberField( 1, 1, 256, 5 );
	tf_pitch_step_unit.setFont( buttonFont );
	tf_pitch_step_unit.setToolTipText( "Pitch Step Unit   " );
	buttonPanel.add( tf_pitch_step_unit, BorderLayout.CENTER );
	buttonPanel.setBorder( titledBorder1 );
	horzBox.add( buttonPanel );
	
	// Amplitude step unit text field
	buttonPanel = new JPanel();
	buttonPanel.setLayout( new BorderLayout(1,1));
	TitledBorder titledBorder2 = new TitledBorder( "ASU");
	titledBorder2.setTitleFont( new Font( "SanSerif", Font.PLAIN, 10 ));
	tf_amplitude_step_unit = new WholeNumberField( 1, 1, 256, 5 );
	tf_amplitude_step_unit.setFont( buttonFont );
	tf_amplitude_step_unit.setToolTipText( "Amplitude Step Unit   " );
	buttonPanel.add( tf_amplitude_step_unit,BorderLayout.CENTER );
	buttonPanel.setBorder( titledBorder2 );
	horzBox.add( buttonPanel );
	horzBox.add( Box.createHorizontalStrut(10) );

	bt_copy = new JButton( "Copy Text" );
	bt_copy.setFont( buttonFont );
	bt_copy.setToolTipText( "Copy text output to System Clipboard   " );
	horzBox.add( bt_copy );
	horzBox.add( Box.createHorizontalStrut(5) );

	bt_save = new JButton( "Save Text" );
	bt_save.setFont( buttonFont );
	bt_save.setToolTipText( "Save text output to a file  " );
	horzBox.add( bt_save );
	horzBox.add( Box.createHorizontalStrut(15) );

	combo_wave = new JComboBox();
	for (int k=0; k<waveTupleList.length; ++k )
	    combo_wave.addItem( waveTupleList[k] );
	combo_wave.setRenderer( new WaveformListCellRenderer() );
	combo_wave.setSelectedIndex(0);
	horzBox.add( combo_wave );
	horzBox.add( Box.createHorizontalStrut(15) );
	horzBox.add( Box.createHorizontalGlue() );

	add( horzBox );
	add( new JSeparator() );

	// ======================================================================
	// Sound Engine Flag Controls
	// ======================================================================

	// Check Button Panel
	Font checkFont = new Font( "Dialog", Font.PLAIN, 8 );
	JPanel checkPanel = new JPanel();
	checkPanel.setLayout( new FlowLayout( FlowLayout.LEFT ));

	cb_hold_pitch_mod = new JCheckBox( "HOLD PITCH MOD" );
	cb_hold_pitch_mod.setFont( checkFont );
	cb_hold_pitch_mod.setToolTipText( "Hold pitch (frequency) modulation  ");
	cb_hold_pitch_mod.setSelected(true);
	checkPanel.add( cb_hold_pitch_mod );

	cb_hold_amp_mod = new JCheckBox( "HOLD AMP MOD" );
	cb_hold_amp_mod.setFont( checkFont );
	cb_hold_amp_mod.setToolTipText( 
	    "Hold amplitude modulation after sustain envelope  ");
	checkPanel.add( cb_hold_amp_mod );

	cb_noise_gate_flag = new JCheckBox( "NOISE GATE FLAG" );
	cb_noise_gate_flag.setFont( checkFont );
	cb_noise_gate_flag.setToolTipText( "Add arbitary noise gate digital filter  ");
	checkPanel.add( cb_noise_gate_flag );

	cb_noise_mix_gate_flag = new JCheckBox( "NOISE MIX GATE FLAG" );
	cb_noise_mix_gate_flag.setFont( checkFont );
	cb_noise_mix_gate_flag.setToolTipText( 
	    "Add arbitary white noise but mix it  ");
	checkPanel.add( cb_noise_mix_gate_flag );

	cb_fm_delta_random_flag	= new JCheckBox( "FM DELTA RANDOM FLAG" );
	cb_fm_delta_random_flag.setFont( checkFont );
	cb_fm_delta_random_flag.setToolTipText( 
	    "Add random delta pitch modulation  ");
	checkPanel.add( cb_fm_delta_random_flag );

	// cb_ = new JCheckBox( "" );
	// cb_.setFont( checkFont );
	// cb_.setToolTipText( "  ");
	// checkPanel.add( cb_ );

	
	add( checkPanel );
	add( new JSeparator() );
	
	// ======================================================================
	// Text Area Output
	// ======================================================================
	ta_output = new JTextArea();
	ta_output.setFont( new Font( "Monospace", Font.PLAIN, 10 ));

	JScrollPane textScroller = new JScrollPane( ta_output ) {
	    public Dimension getPreferredSize() {
		Dimension size=new Dimension( super.getPreferredSize() );
		if ( size.height < 128 ) 
		size.height = 128;
		return size;
	    }
	};
	add( textScroller );
	
	bt_make.addActionListener( this );
	bt_play.addActionListener( this );
	bt_quit.addActionListener( this );
	bt_copy.addActionListener( new CopyListener() );
	bt_save.addActionListener( new SaveListener() );
	// cb_hold_pitch_mod.addActionListener( this );
    }

    /** Create the sound wave clip from current data on the GUI */
    public void generateSoundWaveClip() 
    {
	int index = combo_wave.getSelectedIndex();
	if (index < 0 ) return;
	
	WaveTuple waveTuple = waveTupleList[index];
	
	int psu = tf_pitch_step_unit.getValue();
	int asu = tf_amplitude_step_unit.getValue();
	
	int pd1 = pitchPane1.getValue1();
	int ps1 = pitchPane1.getValue2();
	int pd2 = pitchPane2.getValue1();
	int ps2 = pitchPane2.getValue2();
	int pd3 = pitchPane3.getValue1();
	int ps3 = pitchPane3.getValue2();
	
	int ad = attackPane.getValue1();
	int as = attackPane.getValue2();
	int dd = decayPane.getValue1();
	int ds = decayPane.getValue2();
	int sd = sustainPane.getValue1();
	int ss = sustainPane.getValue2();
	int rd = releasePane.getValue1();
	int rs = releasePane.getValue2();

	double timeLength = (double)timePane.getValue();
	int volume   = volumePane.getValue();
	int freq     = freqPane.getValue();
	int duration = durationPane.getValue();
	    
	// Program the sound engine flags and text output simultaneously
	StringBuffer flagBuf= new StringBuffer();
	int flags=0x0;
	flagBuf.setLength(0);
	if ( cb_hold_pitch_mod.isSelected() ) {
	    flagBuf.append( "| SoundEngine.HOLD_PITCH_MODULATION " );
	    flags |= SoundEngine.HOLD_PITCH_MODULATION;
	}
	if ( cb_hold_amp_mod.isSelected() ) {
	    flagBuf.append( "| SoundEngine.HOLD_AMPLITUDE_MODULATION " );
	    flags |= SoundEngine.HOLD_AMPLITUDE_MODULATION;
	}
	if ( cb_noise_gate_flag.isSelected() ) {
	    flagBuf.append( "| SoundEngine.NOISE_GATE_FLAG " );
	    flags |= SoundEngine.NOISE_GATE_FLAG;
	}
	if ( cb_noise_mix_gate_flag.isSelected() ) {
	    flagBuf.append( "| SoundEngine.NOISE_MIX_GATE_FLAG " );
	    flags |= SoundEngine.NOISE_MIX_GATE_FLAG;
	}
	if ( cb_fm_delta_random_flag.isSelected() ) {
	    flagBuf.append( "| SoundEngine.FM_DELTA_RANDOM_FLAG " );
	    flags |= SoundEngine.FM_DELTA_RANDOM_FLAG;
	}
	    
	// Create envelope and sound
	envelope = new Envelope( 
	    psu,  pd1, ps1, pd2, ps2, pd3, ps3,     
	    asu,  ad, as, dd, ds, sd, ss, rd, rs );
	    
	sound = new SoundEngine(
	    8000, timeLength, volume, freq, duration,
	    waveTuple.getWave(), envelope );

	// Write sound creation as a Java language in the text area
	String waveInitial = waveTuple.getJavaInitializationString();

	StringBuffer buf = new StringBuffer();
	buf.setLength(0);
	buf.append("// Generating sound ...\n" );
	buf.append(
	    "Envelope envelope = new Envelope(\n"+
	    "\t"+psu+",  "+pd1+","+ps1+", "+pd2+","+ps2+", "+pd3+","+ps3+",\n"+
	    "\t"+asu+","+ad+","+as+", "+dd+","+ds+", "+
	    sd+","+ss+", "+rd+","+rs+" );\n" );
	buf.append(
	    "SoundEngine sound = new SoundEngine(\n"+
	    "\t8000, "+timeLength+", "+volume+", "+freq+", "+
	    duration+",\n"+"\t"+ waveInitial+", envelope );\n" );
	if ( flags != 0x0 ) 
	    buf.append("sound.setFlags( 0x0 "+flagBuf+ ");\n" );
	buf.append( "sound.makeWave();" );
	ta_output.setText( buf.toString() );

	sound.setFlags( 0x0 | flags );
	sound.makeWave();
    }

    public void actionPerformed( ActionEvent evt ) 
    {
	Object src = evt.getSource();
	if ( src == bt_make ) {
	    
	    audioPlayer.stop( 0 );
	    bt_make.setEnabled(false);
	    bt_play.setEnabled(false);
	    try {
		generateSoundWaveClip();
	    }
	    catch (Throwable t) {
		System.err.println("*Exception* : An internal error occurred"+
				   "please report the following stacktrace:");
		System.err.println( t.getMessage() );
		System.err.println( "----------------------------------------");
		t.printStackTrace();
	    }
	    finally {
		bt_make.setEnabled(true);
		bt_play.setEnabled(true);
	    }
	}
	else if ( src == bt_play && sound != null ) {
	    audioPlayer.play( 0, sound.getSoundData() );
	}
	else if ( src == bt_quit ) {
	    System.out.println( "Goodbye!");
	    System.exit(0);
	}
    }
    
    public static void main ( String [] args )
    {
	PPMCAudioPlayer pp = PPMCAudioPlayer.getInstance();
	
	JFrame frame = new JFrame( "Gui Test Application BBC Sound Emulator+" );
	frame.getContentPane().setLayout( new BorderLayout( 2, 2 ));

	GuiTestSound guiPane = new GuiTestSound( pp );
	frame.getContentPane().add( guiPane, BorderLayout.CENTER );
	
	frame.pack();
	// frame.setDefaultCloseOperation( frame.EXIT_ON_CLOSE );
	frame.setLocation( 50, 50 );
	frame.setVisible(true);

	frame.addWindowListener( new WindowClosureListener() );
    }


    public void lostOwnership( Clipboard clip, Transferable transferable )
    {
	System.out.println("Lost ownership of clipboard");
    }
    
    // ======================================================================
    //			I N N E R    C L A S S E S
    // ======================================================================
    
    protected class SaveListener implements ActionListener {
	public void actionPerformed( ActionEvent evt )
	{
	    JFileChooser chooser = new JFileChooser();
	    chooser.setDialogTitle( "Choose Save/Append File For Output Text" );
	    chooser.setFileSelectionMode( JFileChooser.FILES_ONLY );
	    chooser.setFileHidingEnabled( false );

	    String parentDir = saveFile.getParent();
	    if (parentDir==null)
		parentDir = System.getProperty("user.dir");
	    File currDir = new File( parentDir);
	    chooser.setCurrentDirectory( currDir );
	    chooser.setSelectedFile( saveFile );

	    int option = chooser.showSaveDialog( GuiTestSound.this );
	    if ( option == JFileChooser.APPROVE_OPTION &&
		 chooser.getSelectedFile() != null ) {
		
		// Append the text area output to file
		saveFile = chooser.getSelectedFile();
		FileWriter  fwriter = null;
		PrintWriter pwriter = null;
		try {
		    fwriter = new FileWriter( saveFile.getPath(), true );
		    pwriter = new PrintWriter( fwriter );
		    pwriter.println( ta_output.getText() );
		    pwriter.println( "// ==========\n");
		}
		catch (IOException ioe) {
		    System.err.println( ioe.getMessage() );
		}
		finally {
		    if (fwriter != null)
			try { fwriter.close(); } catch (IOException e) { }
		    pwriter.close();
		}
	    }
	}
    }
    

    protected class CopyListener implements ActionListener {
	public void actionPerformed( ActionEvent evt )
	{
	    StringSelection contents = 
		new StringSelection( ta_output.getText() );
	    // Place the transferable onto the clipboard
	    clipboard.setContents( contents, GuiTestSound.this );
	}
    }
    

    public static class WindowClosureListener extends WindowAdapter
    {
	public void windowClosing( WindowEvent e )
	{
	    System.exit(0);
	}
    }
}
