CS 2, Winter 2008
Programming for Interactive Digital Arts

Jan 24: Periodicity


Sine (and cosine) curves

Sinusoids oscillate between -1 and 1, as the angle goes from 0 to 360 degrees. A sketch by Greenberg, modified here, illustrates.

screenshot[applet]

The top curve is a sine and the bottom a cosine. Note that this might look upside down, since the y axis increases from top to bottom here.

Processing provides the sin() and cos() functions to compute sines and cosines. They expect their parameters to be in radians (where 2π is 360 degrees); we can convert from degrees to radians with the radians() function. Since π is at the heart of radians, and you might not have it memorized, Processing provides PI (180 degrees), TWO_PI (360 degrees), and HALF_PI (90 degrees).

While they normally oscillate between -1 and 1, we can shift and scale the results of sines and cosines to give different behaviors. Likewise, while they normally repeat every 2π, we can scale their inputs so that they repeat more or less frequently. To make the period be n, we muliply the angle by 2π divided by n. To see this, notice that by dividing by n, we've calculated the fraction of the desired distance that we've gone. Then by multiplying by 2π, we make it so that when it's gone 100% of the way, it's completed a full period.

The following sketch extends the Processing example Basics | Math | SineWave to illustrate these ideas. We can set both the amplitude (the height of the wave, scaling above the normal -1 to 1 range) and the period (how quickly the wave repeats).

// Parameters controlling the sketch:
int r = 8;        // radius of the ellipses along the wave
float da = 0.02;  // angle step size
int period = 500;
int amplitude = 50;

// Current angle
float a = 0;

void setup()
{
  size(250,100);
  smooth();
  noStroke();
  fill(255,50);
}

void draw()
{
  background(0);
  // This is an attempt to get reasonably spaced ellipses,
  // according to the current period of the wave
  float dx = r*3*(period/TWO_PI)/width;
  if (dx<1) dx=1;
  
  // For each x coordinate, compute the y coordinate from the sin
  for (float x=0; x<=width; x+=dx)
    ellipse(x, height/2+amplitude*sin(a+x*TWO_PI/period), r*2,r*2);
  
  a += da;
}

void keyPressed()
{
  // Control the amplitude and period
  if (key=='a') {
    if (amplitude > 10) amplitude -= 10;
  }
  else if (key=='A') {
    amplitude += 10;
  }
  else if (key=='p') {
    if (period > 10) period -= 10;
  }
  else if (key=='P') {
    period += 10;
  }
}
screenshot[applet]

Sines, sines, everywhere sines

Let's first get at the heart of oscillating behavior, which is based on an increasing angle. Here's a simple variation on the ball moving around a circle idea from above, but drawing a wedge each time.

float a = 0;      // current angle (degrees)
float maxDa = 30; // biggest possible angular step

void setup()
{
  smooth();
  noStroke();
}

void draw()
{
  float a2 = a+random(maxDa); // end of wedge
  fill(random(200,255),random(200,255),0);
  arc(50,50, 75,75, radians(a), radians(a2));
  a = a2;                     // begin next wedge where this one ended
}
screenshot[applet]

With just a small tweak or two to the "StoringInput" example we saw before (Basics | Input | StoringInput), inspired by another example (Topics | Drawing | Pulses), we can have decaying ellipses circle around the mouse position.

int num = 60;
float mx[] = new float[num];
float my[] = new float[num];
int angle = 0;  // Variation: also keep track of angle (updated in draw)

void setup() 
{
  size(200, 200);
  smooth();
  noStroke();
  fill(255, 153); 
}

void draw() 
{
  background(51); 
  
  // Reads throught the entire array
  // and shifts the values to the left
  for(int i=1; i<num; i++) {
    mx[i-1] = mx[i];
    my[i-1] = my[i];
  } 
  // Add the new values to the end of the array
  // The main change: add in a sinusoidal offset (scaled by magic number 20)
  mx[num-1] = mouseX + 20*cos(radians(angle));
  my[num-1] = mouseY + 20*sin(radians(angle));
  
  for(int i=0; i<num; i++) {
    ellipse(mx[i], my[i], i/2, i/2);
  }
  
  angle += 10;
}
screenshot[applet]

Now let's use the value of a sine/cosine for the current angle to control the position of ellipses. As the value oscillates between -1 and 1, the ellipses move back and forth or up and down. This is just a simplified version of the Processing example Basics | Math | SineCosine.

float angle=0, da=0.1;

void setup() 
{
  size(200, 200);
  noStroke();
  smooth();
}

void draw() 
{
  background(0);
  
  fill(51);
  rect(60, 60, 80, 80);

  fill(255);
  ellipse(width/2 + 40*cos(angle), 36, 32, 32);
  ellipse(width/2 + 40*cos(-angle), 164, 32, 32);

  fill(153);
  ellipse(36, width/2 + 40*sin(angle), 32, 32);
  ellipse(164, width/2 + 40*sin(-angle), 32, 32);

  angle += da;
}
screenshot[applet]

We can similarly use the value to control ellipse sizes, making "pulsars". We'll keep a separate angle for each pulsar, have each oscillate according to its own period and own maximum size.

// Positions, sizes, angles, and frequencies of "pulsars"
int num = 200;
float[] x = new float[num], y = new float[num];
float[] sz = new float[num], angle = new float[num], da = new float[num];

void setup()
{
  size(400,400);
  background(0);
  noStroke();
  
  for (int i=0; i<num; i++) {
    x[i] = random(width); y[i] = random(width);
    sz[i] = random(20);
    angle[i] = random(TWO_PI);
    da[i] = random(1,5)/25;
  }
}

void draw()
{
  background(0);
  fill(255);
  for (int i=0; i<num; i++) {
    // Set the diameter according to the angle
    float d = sz[i] * (1+sin(angle[i]))/2.0;
    ellipse(x[i],y[i],d,d);
    angle[i] += da[i];
  }
}
screenshot[applet]

Finally, we can make our fireworks move out circularly. The only change to the code is the initialization of dx and dy -- we randomly compute the angle in which to shoot the particle, along with the speed to move along that direction, and then convert to the dx and dy step sizes.

float angle = random(0, TWO_PI);
float speed = random(1,2);
dx[i] = cos(angle)*speed; dy[i] = sin(angle)*speed;
screenshot[applet]

Practice problems

  1. Modify the wedge-drawing sketch to have two or more separate angles moving around the circle. [hints]
  2. Modify the decaying orbiting ellipses to allow user control over the size and period of the orbit.
  3. Modify the linear motion sketch to have the ellipses moving at different periods. [hints]
  4. Have a bunch of different ellipses moving randomly back and forth (or up and down, or both). [hints]