
/*------------------------------------------------------------------------*
 * 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 ST class implements the Shepard's Tones demonstration.  It is
 *    meant to be run in a separate window using AppletStarter.
 *------------------------------------------------------------------------*/

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

/*------------------------------  Class ST  ------------------------------*/

public class ST extends WindowApplet
{
  private STPanel      canvas;
  private STControls   controls;
  private Button       dismiss;

  public  AudioClip[]  STone2 = null;        // Two-component tones
  public  AudioClip[]  STone3 = null;        // Three-component tones
  public  AudioClip[]  STone6 = null;        // Six-component tones

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

    ST()
    {
      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 STPanel( 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 STControls( 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;
      int     i, n;

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

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

        // load two-component clips
        STone2 = new AudioClip[12];
        for ( i = 0; i < 12; i++ )
        {
            applet.setLabel( "Loading audio clip " + n + " of 36 ..." );
            n += 1;
            toneName = "st-c2/st" + (i + 1) + ".au";
            STone2[i] = applet.getAudioClip( urlBase, toneName );
            if ( STone2[i] == null )
                System.out.println(
                    "Couldn't load audio clip " + urlBase + toneName );
        }

        // load three-component clips
        STone3 = new AudioClip[12];
        for ( i = 0; i < 12; i++ )
        {
            applet.setLabel( "Loading audio clip " + n + " of 36 ..." );
            n += 1;
            toneName = "st-c3/st" + (i + 1) + ".au";
            STone3[i] = applet.getAudioClip( urlBase, toneName );
            if ( STone3[i] == null )
                System.out.println(
                    "Couldn't create audio clip " + urlBase + toneName );
        }

        // load six-component clips
        STone6 = new AudioClip[12];
        for ( i = 0; i < 12; i++ )
        {
            applet.setLabel( "Loading audio clip " + n + " of 36 ..." );
            n += 1;
            toneName = "st-c6/st" + (i + 1) + ".au";
            STone6[i] = applet.getAudioClip( urlBase, toneName );
            if ( STone6[i] == null )
                System.out.println(
                    "Couldn't create audio clip " + urlBase + toneName );
        }

        // 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 STPanel  ----------------------------*/

class STPanel 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[]     STone = null;

  // Current tone number, # of components, and current tone display switch
  private int      tone    = 11;
  private int      comp    = 2;  // 0->2, 1->3, 2->6
  public  boolean  display = false;

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

  // ToneView objects for reference and current tone
  private ToneView  refTone;
  private ToneView  currentTone;

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

    STPanel( ST toneWindow )
    {
        // Record id of parent window for accessing tones
        this.toneWindow = toneWindow;

        // Create ToneView's and add with a grid layout
        setLayout( new GridLayout( 2, 1 ) );
        refTone     = new ToneView( 12, 6,  0 );
        currentTone = new ToneView( 12, 6, -1 );
        add( refTone );
        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 STPanel.run" );
                }
            lastTime = System.currentTimeMillis();
            step();
        }
    }

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

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

        if ( display )
        {
            currentTone.setTone( tone );
	    currentTone.repaint();
        }
        if ( STone == null )
            setComponents( comp );
	STone[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;
        }
    }

    /*----------------------  setComponents  -----------------------*/

    public void setComponents( int comp )
    {
        this.comp = comp;
        switch ( comp )
        {
            case 0:
                refTone.setComponents( 2 );
                currentTone.setComponents( 2 );
                STone = toneWindow.STone2;
                break;

            case 1:
                refTone.setComponents( 3 );
                currentTone.setComponents( 3 );
                STone = toneWindow.STone3;
                break;

            case 2:
            default:
                refTone.setComponents( 6 );
                currentTone.setComponents( 6 );
                STone = toneWindow.STone6;
                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 STControls  --------------------------*/

class STControls extends Panel
{
  Button     start, stop, step;
  Checkbox   up, down, display;
  Choice     components;
  Panel      buttonPanel, updownPanel, numCompPanel;
  Scrollbar  speedSlider;
  Thread     animator = null;
  STPanel    canvas;  // The main drawing canvas

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

    STControls( STPanel 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 panel with up/down check boxes
        updownPanel = new Panel();
        cbg  = new CheckboxGroup();
        up   = new Checkbox( "Up",   cbg, true );
        down = new Checkbox( "Down", cbg, false );
        updownPanel.add( up );
        updownPanel.add( down );

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

        // Create component-number choice gadget
        numCompPanel = new Panel();
        components = new Choice();
        components.addItem( "2" );
        components.addItem( "3" );
        components.addItem( "6" );
        components.select( 2 );
        numCompPanel.add( new Label( "Components:" ) );
        numCompPanel.add( components );

        // 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:  up/down -- display -- components
        addControl( (Component)updownPanel, 0.0,
            GridBagConstraints.NONE, 1, gridbag, c );
        addControl( (Component)display, 0.2,
            GridBagConstraints.NONE, 1, gridbag, c );
        addControl( (Component)numCompPanel, 0.8,
            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 == up )
        {
            canvas.goingUp = true;
            return true;
        }
        if ( event.target == down )
        {
            canvas.goingUp = false;
            return true;
        }
        if ( event.target == components )
        {
            canvas.setComponents( components.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 );
    }
}

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

