
/*------------------------------------------------------------------------*
 * 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 ToneView class displays a visual representation of a single tone
 *  from the set of Shepard's Tones in a rectangular window.
 *  It is used by the ST and TT demos.
 *------------------------------------------------------------------------*/

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

import java.awt.*;

/*---------------------------  Class ToneView  ---------------------------*/

public class ToneView extends Canvas
{
  // Private constants
  private final int  topOffset   = 20;   // Offset of graph from top
  private final int  otherOffset = 15;   // Offset of graph from other edges

  private final double  lMin = -34.0;
  private final double  lMax =   0.0;

  // Other private data
  private double  twopi;

  // Public tone parameters
  public int  tMax;   // Total number of tones to accommodate
  public int  cMax;   // Number of components in each tone
  public int  tone;   // Number of current tone, counting from 0
                      // ( tone < 0 ) requests a blank display

  // Drawing colours
  Color  envColour = new Color(  38, 69, 255 );
  Color  cmpColour = new Color( 255, 69,  38 );

  // Background image (blank with border and axes)
  Dimension  bgDimension;
  Image      bgImage;
  Graphics   bgGraphics = null;

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

    ToneView( int tMax, int cMax, int tone )
    {
        this.tMax = tMax;
        this.cMax = cMax;
        this.tone = tone;
        twopi = 8.0 * Math.atan( 1.0 );
    }

    /*--------------------------  paint  ---------------------------*/

    public void paint( Graphics g )
    {
        update( g );
    }

    /*--------------------------  update  --------------------------*/

    public void update( Graphics g )
    {
      Dimension  d = size();
      int        bx, by, bw, bh;    // Border frame
      int        gx, gy, gw, gh;    // Graph frame
      int        x1, y1, x2, y2;    // Temporary variables
      int        step;              // Pixels between components
      int        c, t;              // Loop counters
      int        w, h, n;

        // Just draw a frame on a white background if not showing a tone
        if ( tone < 0 )
        {
            g.setColor( Color.white );
            g.fillRect( 0, 0, d.width, d.height );
            g.setColor( Color.black );
            g.drawRect( 1, 1, d.width - 2, d.height - 2 );
            return;
        }

        // We have a tone to draw -- proceed
        // Compute frames of border and graph
        bx = by = 1;
        bw = d.width - 2;
        bh = d.height - 2;
        /* gx is initialized below */
        gy = by + topOffset;
        gw = bw - ( otherOffset << 1 );
        gh = bh - ( topOffset + otherOffset );

        // Adjust graph width to accomodate integral number of comp steps
        n = cMax * tMax + 1;
        step = gw / n;
        gw = step * n;
        n -= 1;

        // center graph at new width
        gx = ( bw - gw ) / 2;

        // Make sure background image is up to date
        if ( ( bgGraphics == null ) ||
             ( d.width  != bgDimension.width  ) ||
             ( d.height != bgDimension.height ) )
        {
            // Allocate background image
            bgDimension = d;
            bgImage = createImage( d.width, d.height );
            bgGraphics = bgImage.getGraphics();

            // Clear to background colour
            bgGraphics.setColor( Color.white );
            bgGraphics.fillRect( 0, 0, d.width, d.height );

            // Draw border
            bgGraphics.setColor( Color.black );
            bgGraphics.drawRect( 1, 1, d.width - 2, d.height - 2 );

            // Draw axes
            y1 = gy + gh;
            bgGraphics.drawLine( gx, gy, gx, y1 );
            bgGraphics.drawLine( gx, y1, gx + gw, y1 );

            // Draw envelope
            bgGraphics.setColor( envColour );
            h = gh - otherOffset;  // raise envelope from axis by offset
            x1 = gx + step;
            y1 = gy + (int)( ( 1.0 - fAmp( 0, 0 ) ) * h );
            for ( c = 0; c < cMax; c++ )
                for ( t = 0; t < tMax; t++ )
                    if ( c + t > 0 )
                    {
                        x2 = x1 + step;
                        y2 = gy + (int)( ( 1.0 - fAmp( t, c ) ) * h );
                        bgGraphics.drawLine( x1, y1, x2, y2 );
                        x1 = x2;
                        y1 = y2;
                    }
        }

        // Copy background image to screen
        g.drawImage( bgImage, 0, 0, this );

        // Draw tone number
        g.drawString( "Tone " + ( tone + 1 ), otherOffset, topOffset - 2 );

        // Draw partials
        g.setColor( cmpColour );
        x1 = gx + ( ( tone + 1 ) * step ) - 2;
        w = step * tMax;
        h = gh - otherOffset;
        for ( c = 0; c < cMax; c++ )
        {
            x2 = x1 + ( c * w );
            y2 = gy + (int)( ( 1.0 - fAmp( tone, c ) ) * h );
            g.fillRect( x2, y2, 5, gh - y2 + gy );
        }
    }

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

    public void setComponents( int cMax )
    {
        this.cMax = cMax;
        bgGraphics = null;
        update( getGraphics() );
    }

    /*-------------------------  setTone  --------------------------*/

    public void setTone( int tone )
    {
        this.tone = tone;
    }

    /*---------------------------  fAmp  ---------------------------*/

    public double fAmp( int t, int c )
    {
      double  theta;

        theta = twopi * ((double)( c * tMax + t )) / (double)( tMax * cMax );
        return(
            MKdB( lMin +
                  ( lMax - lMin ) * ( 1.0 - Math.cos( theta ) ) / 2.0 ) );
    }

    /*---------------------------  MKdB  ---------------------------*/

    public double MKdB( double db )
    {
        return( Math.pow( 10.0, db / 20.0 ) );
    }
}

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

