Lissajous Figures

Below you can (hopefully) see output of 4 separate instances of my first Java applet. It is based on my experimental Direct Digital Synthesizer class which generates two sinewaves of different frequencies which are used to draw Lissajous figures...

ddstest.java

import java.applet.*;

public class ddstest extends lissajous implements Runnable {

  Thread animator;
  int xf=1000,yf=2005;

  public void start() {
    xf=Integer.parseInt(getParameter("Xf"));
    yf=Integer.parseInt(getParameter("Yf"));
    System.out.println("Xf="+xf+" Yf="+yf);
    synthreset(xf,yf);
    animator=new Thread(this);
    animator.start();
  }

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

  public void run() {
    while (true) {
      try {
        Thread.sleep(16);
      } catch (Exception sleepProblem) {
      }
      repaint();
    }
  }
}

lissajous.java

/*
this code uses DDS class to create two sinewave generators, output
of which is used to draw lissajous figures.
*/

import java.awt.*;
import java.applet.*;

public class lissajous extends Applet {
  private static final int NSAMPLES = 256;
  private Image offScreenImage;
  private dds synthx,synthy;
  private int x[],y[];

  lissajous() {
    // create and initialize synthesizers
    synthx=new dds(1000);
    synthy=new dds(2005);
    // create a cooridnate array
    x=new int[NSAMPLES];
    y=new int[NSAMPLES];
  }

  public void init() {
    offScreenImage=createImage(size().width,size().height);
  }

  public void synthreset(int xf,int yf) {
    synthx.setfreq(xf);
    synthy.setfreq(yf);
  }

  // generates pixel coordinates for a next frame
  //
  void sample() {
    int i;
    for (i=0;i<NSAMPLES;i++) {
      x[i]=synthx.read();
      y[i]=synthy.read();
    }
  }

  // draw a lissajous figure
  //
  public void paint(Graphics g) {
    int i;
    for (i=0;i<(NSAMPLES-1);i++) {
      g.drawLine(x[i],y[i],x[i+1],y[i+1]);
    }
    sample();
  }

  // update window by drawing on offscreen image, then
  // copying it to screen
  //
  public void update(Graphics g) {
    Graphics os=offScreenImage.getGraphics();
    os.setColor(getBackground());
    os.fillRect(0,0,size().width,size().height);
    os.setColor(g.getColor());
    paint(os);
    g.drawImage(offScreenImage,0,0,this);
  }
}

dds.java


/*
Direct Digital Synthesizer demo.

the appearent unneeded complexy of the phase accumulator
calculation is caused by my desire to test out the feasibility
of DDS using only integer math.

Inside a microcontroller that I eventually plan to implement
it on, the scaling operations (multiplication and division with
65536) disappear, because the result can simply be read from 
correct memory locations.

In this example the sample rate has no real meaning, but in real
life this should reflect the actual rate at which the hardware
can pump out the data - the two must match for the output
frequency be correct. Higher sample rate allows to generate less
distorted sinewaves at higher frequencies. Using 200kHz sample
rate allows to output 20kHz sine represented by 10 samples which
is pretty good.

*/

public class dds {

  // some constants
  private static final int NSAMPLES = 1024;
  private static final int SAMPLEMAX = 127;
  private static final int SAMPLEOFFSET = 128;
  private static final int SAMPLERATE = 200000;
  private static final double PI = 3.141592653;
  private static final double PI2 = PI*2.0;
  private static final double  SSTEP = PI2/(double)NSAMPLES;

  // internally used variables
  //
  private int pa,pi;        // phase accumulator, phase increment
  private short sine[];     // sinewave table
  public int freq=0;        // current output freqency

  // constructor of the class builds sine table for lookup
  // in real life, the table will be pre-built into nonvolatile
  // storage
  //
  public dds(int f) {
    sine=new short[NSAMPLES];
    double f1,f2;
    pa=0;
    for (f1=(double)0.0;f1<PI2-(SSTEP/2.0);f1+=SSTEP,pa++) {
      f2=(double)Math.sin(f1)*(double)SAMPLEMAX;
      sine[pa]=(short)((short)f2+SAMPLEOFFSET);
    }
    init(f);
  }

  // this is a workhorse method, at each sample clock
  // this returns a value of a signal at that point
  // and updates a phase accumulator
  //  
  public short read()  {
    short s;
    s=sine[pa/65536];
    pa=pa+pi;
    if ((pa/65536)>(NSAMPLES-1))
      pa=pa-(NSAMPLES*65536);
    return s;
  }

  // initialization methods calculate a new phase shift
  // value and reset phase accumulator
  //
  public void init() {
    pa=0;
    setfreq(1000);
  }

  public void init(int f) {
    pa=0;
    setfreq(f);
  }

  // set phase increment to desired value
  // DDS should fetch a sine value from table
  // and move forward in phase.
  // the formula for calculating this phase increment is
  // phase_increment=number_of_samples*desired_freq/sample_freq
  //
  void setfreq(int f)
  {
    freq=f;
    pi=((NSAMPLES*65536)/SAMPLERATE*f);
  }

}