
/*------------------------------------------------------------------------*
 * Originally implemented by Scott Flinn, in association with the
 * Imager Graphics Laboratory at the University of British Columbia
 * (scottflinn@alumni.uwaterloo.ca).
 *
 * This source code may be freely distributed and modified for any purpose
 * as long as these introductory comments are not removed.  Please be
 * aware that this represents the author's initial experiments with the
 * Java platform and should not necessarily be considered good examples
 * of Java programming.
 *------------------------------------------------------------------------*/

/*------------------------------------------------------------------------*
 *  The TT class implements the Tritone Paradox demonstration.  It is
 *    meant to be run in a separate window using AppletStarter.
 *------------------------------------------------------------------------*/

import  java.applet.*;
import  java.awt.*;
import  java.net.URL;

/*------------------------------  Class TT  ------------------------------*/

public class TT extends WindowApplet
{
  private TTPanel      canvas;
  private TTControls   controls;
  private Button       dismiss;

  public  AudioClip[]  TTone110 = null;        // 110 Hz base tone pair
  public  AudioClip[]  TTone160 = null;        // 160 Hz base tone pair

    /*-----------------------  constructor  ------------------------*/

    TT()
    {
      GridBagLayout       gridbag;
      GridBagConstraints  c;

        // Create the layout manager and a constraint object
        gridbag = new GridBagLayout();
        c = new GridBagConstraints();
        setLayout( gridbag );

        // Create the canvas panel and put it on top
        canvas = new TTPanel( this );
        c.gridwidth = GridBagConstraints.REMAINDER;
        c.fill = GridBagConstraints.BOTH;
        c.weightx = 1.0;
        c.weighty = 1.0;
        gridbag.setConstraints( canvas, c );
        add( canvas );

        // Create the control panel and put it second from the bottom
        controls = new TTControls( canvas );
        c.fill = GridBagConstraints.HORIZONTAL;
        c.weighty = 0.0;
        gridbag.setConstraints( controls, c );
        add( controls );

        // Create the dismiss button and put it at the bottom
        dismiss = new Button( "Dismiss" );
        gridbag.setConstraints( dismiss, c );
        add( dismiss );
        validate();
    }

    /*---------------------------  init  ---------------------------*/

    public void init( AppletStarter applet )
    {
      URL       urlBase;
      String    toneName, loading = "Loading audio clip ";
      Graphics  g;
      int       i, n;

        // record id of AppletStarter instance that launched us
        this.applet = applet;

        // load audio clips
        urlBase = applet.getCodeBase();
        n = 1;  // number of clips loaded in total

        TTone110 = new AudioClip[2];
        applet.setLabel( loading + "1 of 4 ..." );
        TTone110[0] = applet.getAudioClip( urlBase, "tt/a110.au" );
        applet.setLabel( loading + "2 of 4 ..." );
        TTone110[1] = applet.getAudioClip( urlBase, "tt/b110.au" );

        TTone160 = new AudioClip[2];
        applet.setLabel( loading + "3 of 4 ..." );
        TTone160[0] = applet.getAudioClip( urlBase, "tt/a160.au" );
        applet.setLabel( loading + "4 of 4 ..." );
        TTone160[1] = applet.getAudioClip( urlBase, "tt/b160.au" );

        // let AppletStarter know that we're ready to go
        applet.appletLoaded();
    }

    /*--------------------------  action  --------------------------*/

    public boolean action( Event event, Object obj )
    {
        if ( event.target == dismiss )
        {
            canvas.stopAnimation();
            controls.enableStart();
            hide();
            return true;
        }
        return false;
    }
}

/*---------------------------  Class TTPanel  ----------------------------*/

class TTPanel extends Panel implements Runnable
{
  // Public constants
  public final int  minPeriod =  150;     // Milliseconds
  public final int  maxPeriod = 1500;     // Milliseconds

  // Public parameters
  public int      period   = 960;         // Milliseconds
  public boolean  goingUp  = true;

  // Audio tone set from applet
  AudioClip[]     TTone = null;

  // Current tone number, # of components, and current tone display switch
  private int      tone    = 1;
  private int      toneSet = 0;      // 0 -> 110 Hzbase; 1 -> 160 Hz base
  public  boolean  display = false;

  // Animation thread and applet id
  private Thread  animator = null;
  private TT      toneWindow;

  // ToneView object for current tone
  private ToneView  currentTone;

    /*-----------------------  constructor  ------------------------*/

    TTPanel( TT toneWindow )
    {
        // Record original applet id
        this.toneWindow = toneWindow;

        // Create ToneView's and add with a grid layout
        setLayout( new GridLayout( 1, 1 ) );
        currentTone = new ToneView( 2, 6, -1 );
        add( currentTone );
    }

    /*---------------------------  run  ----------------------------*/

    public void run()
    {
      long  lastTime = System.currentTimeMillis();
      long  t;

        while ( true )
        {
            t = ( lastTime + (long)period ) - System.currentTimeMillis();
            if ( t > (long)1 )
                try {
                    animator.sleep( (int)t );
                } catch ( InterruptedException e ) {
                    System.out.println(
                        "Interrupted exception in TTPanel.run" );
                }
            lastTime = System.currentTimeMillis();
            step();
        }
    }

    /*---------------------------  step  ---------------------------*/

    public void step()
    {
        // Update frame number then draw current frame if necessary
        tone = ( tone + 1 ) % 2;

        if ( display )
        {
            currentTone.setTone( tone );
	    currentTone.repaint();
        }
        if ( TTone == null )
            setToneSet( toneSet );
	TTone[tone].play();
    }

    /*----------------------  startAnimation  ----------------------*/

    public void startAnimation()
    {
        if ( animator == null )
        {
            try {
                animator = new Thread( this );
                animator.start();
            } catch ( IllegalThreadStateException e ) {
                System.out.println( "Thread state exception in start" );
            }
        }
    }

    /*----------------------  stopAnimation  -----------------------*/

    public void stopAnimation()
    {
        if ( animator != null )
        {
            animator.stop();
            animator = null;
        }
    }

    /*------------------------  setToneSet  ------------------------*/

    public void setToneSet( int toneSet )
    {
        this.toneSet = toneSet;
        switch ( toneSet )
        {
            case 0:
                TTone = toneWindow.TTone110;
                break;

            case 1:
            default:
                TTone = toneWindow.TTone160;
                break;
        }
        repaint();
    }

    /*------------------------  setDisplay  ------------------------*/

    public void setDisplay( boolean display )
    {
        // Toggle the display if necessary
        if ( this.display != display )
        {
            if ( display )
                currentTone.setTone( tone );
            else
                currentTone.setTone( -1 );
            currentTone.update( currentTone.getGraphics() );
        }
        this.display = display;
    }
}

/*--------------------------  Class TTControls  --------------------------*/

class TTControls extends Panel
{
  Button     start, stop, step;
  Checkbox   display;
  Choice     toneSet;
  Panel      buttonPanel, toneSetPanel;
  Scrollbar  speedSlider;
  Thread     animator = null;
  TTPanel    canvas;  // The main drawing canvas

    /*-----------------------  constructor  ------------------------*/

    TTControls( TTPanel canvas )
    {
      GridBagLayout       gridbag;
      GridBagConstraints  c;
      CheckboxGroup       cbg;

        // Record id of drawing canvas
        this.canvas = canvas;

        // Use a grid bag to lay things out
        gridbag = new GridBagLayout();
        c = new GridBagConstraints();
        setLayout( gridbag );

        // Create slider
        speedSlider =
            new Scrollbar(
                Scrollbar.HORIZONTAL,
                canvas.maxPeriod + canvas.minPeriod - canvas.period,
                ( canvas.maxPeriod - canvas.minPeriod ) / 10,
                canvas.minPeriod, canvas.maxPeriod );

        // Create panel with animation buttons
        buttonPanel = new Panel();
        start = new Button( "Start" );
        stop  = new Button( "Stop" );
        step  = new Button( "Step" );
        buttonPanel.add( start );
        buttonPanel.add( stop );
        buttonPanel.add( step );

        // Create display check box
        display = new Checkbox( "Display" );

        // Create tone set choice gadget
        toneSetPanel = new Panel();
        toneSet = new Choice();
        toneSet.addItem( "110 Hz" );
        toneSet.addItem( "160 Hz" );
        toneSet.select( 0 );
        toneSetPanel.add( new Label( "Base frequency:" ) );
        toneSetPanel.add( toneSet );

        // Stop button is initially disabled
        stop.disable();

        // First row:  anim buttons -- speed label -- speed slider
        addControl( (Component)buttonPanel, 0.0,
            GridBagConstraints.NONE, 1, gridbag, c );
        addControl( (Component)(new Label( "Speed:", Label.RIGHT )),
            0.2, GridBagConstraints.NONE, 1, gridbag, c );
        addControl( (Component)speedSlider, 0.8,
            GridBagConstraints.HORIZONTAL,
            GridBagConstraints.REMAINDER, gridbag, c );

        // Second row:  display -- components
        addControl( (Component)display, 0.0,
            GridBagConstraints.NONE, 1, gridbag, c );
        addControl( (Component)toneSetPanel, 1.0,
            GridBagConstraints.HORIZONTAL,
            GridBagConstraints.REMAINDER, gridbag, c );
    }

    /*------------------------  addControl  ------------------------*/

    private void
    addControl( Component component, double wx, int fill, int gw,
        GridBagLayout gb, GridBagConstraints c )
    {
        c.weightx   = (float)wx;
        c.weighty   = 1.0;
        c.fill      = fill;
        c.gridwidth = gw;
        gb.setConstraints( component, c );
        add( component );
    }

    /*--------------------------  insets  --------------------------*/

    public Insets insets()
    {
        return( new Insets( 5, 5, 5, 5 ) );
    }

    /*-----------------------  enableStart  ------------------------*/

    public void enableStart()
    {
        start.enable();
        stop.disable();
        step.enable();
    }

    /*------------------------  enableStop  ------------------------*/

    public void enableStop()
    {
        start.disable();
        stop.enable();
        step.disable();
    }

    /*--------------------------  action  --------------------------*/

    public boolean action( Event event, Object obj )
    {
        if ( event.target == start )
        {
            canvas.startAnimation();
            enableStop();
            return true;
        }
        if ( event.target == stop )
        {
            canvas.stopAnimation();
            enableStart();
            return true;
        }
        if ( event.target == step )
        {
            canvas.step();
            return true;
        }
        if ( event.target == toneSet )
        {
            canvas.setToneSet( toneSet.getSelectedIndex() );
            return true;
        }
        if ( event.target == display )
        {
            canvas.setDisplay( display.getState() );
            return true;
        }
        return false;
    }

    /*-----------------------  handleEvent  ------------------------*/

    public boolean handleEvent( Event event )
    {
        if ( event.target == speedSlider )
        {
            // simply adjust animation speed
            canvas.period =
                canvas.maxPeriod + canvas.minPeriod - speedSlider.getValue();
            return true;
        }
        else
            return super.handleEvent( event );
    }
}

/*------------------------------------------------------------------------*/

