toxiclibs

Additive waves

The December release cycle has seen a number of new demos, including this one below using the built-in wave generators to create a dynamic 3D terrain. These generators are actually some of the oldest & most re-usable components of the core library, but they never got much attention on the demo front. This is of course not right, especially since they also nicely demonstrate the object oriented “building block” approach also underlying most of the other available classes.

new toxiclibs demo: AdditiveWaves017 new toxiclibs demo: AdditiveWaves011

new toxiclibs demo: AdditiveWaves003 new toxiclibs demo: AdditiveWaves005

The Processing source code for generating these meshes is available with the library download, with an excerpt shown also below. But first, here’s a brief overview of how to use these classes:

Waves & oscillation in general are very flexible & powerful tools for many different design tasks and shouldn’t always be thought of in an audio context. For example, a square wave can be used to flick between 2 values at a given frequency, without the need for any if() statements. I also often use sine waves to create cyclical, soft, harmonious value changes, be it for interface design, movement, animation or even the time based modulation of behavioural parameters of agents. The core library comes with the following pre-defined wave types:

  • Sine (pure, frequency modulated (FM), amplitude & frequency modulated (AMFM))
  • Sawtooth (FM)
  • Square (FM and FM with harmonics)
  • Triangle (FM)
  • Constant (only provides a static value, e.g. useful as static source for input as FM)

All of these types are expressed as implementations of the AbstractWave class in the toxi.math.waves package. This abstract class defines all commonalities of what constitutes a wave: a phase, a frequency, an amplitude, a centre offset and a bunch of accessors and converters. The actual wave function then needs to be implemented by a concrete sub-class. Using poly-morphism and having an abstract super class makes a lot of sense in this case, since it allows for all this:

  1. It frees our main application from having to know which exact wave type we’re going to use and so allows for easier/faster experimentation and generally more flexibility.
  2. It enables us to plug-in any of the available wave types as input for frequency or amplitude modulation and so create highly (theoretically infinitely) complex waveforms.
  3. It provides the basics of an extensible framework for creating new wave types.

These points are hopefully confirmed by you when looking at this method below (taken from the AdditveWaves demo, bundled since toxiclibscore-0015):

AbstractWave createRandomWave() {

  AbstractWave result=null;

  // create a randomized sine wave to be used as
  // FM input for the main wave chosen below
  // the order of parameters is:
  // start phase, frequency (in radians), amplitude, offset value
  AbstractWave fmod=new SineWave(0, random(0.005, 0.02), random(0.1, 0.5), 0);

  // pick a random frequency
  float freq=random(0.005, 0.05);

  // choose a wave type
  switch((int)random(7)) {
  case 0:
    result = new FMTriangleWave(0, freq, 1, 0, fmod);
    break;
  case 1:
    result = new FMSawtoothWave(0, freq, 1, 0, fmod);
    break;
  case 2:
    result = new FMSquareWave(0, freq, 1, 0, fmod);
    break;
  case 3:
    // harmonic square waves are created by
    // adding sine waves upto a number of octaves
    // above the base frequency
    result = new FMHarmonicSquareWave(0, freq, 1, 0, fmod);
    ((FMHarmonicSquareWave)result).maxHarmonics=(int)random(3,30);
    break;
  case 4:
    result = new FMSineWave(0, freq, 1, 0, fmod);
    break;
  case 5:
    AbstractWave amod=new SineWave(0, random(0.01,0.2), random(2, 3), 0);
    result = new AMFMSineWave(0, freq, 0, fmod, amod);
    break;
  case 6:
    result = new ConstantWave(random(-1,1));
    break;
  }
  return result;
}

The next snippet then quickly shows how this method would be used:

import toxi.math.waves.*;

AbstractWave wave=createRandomWave();

// updates the wave phase and computes the new function value
// this code doesn't care which wave type we're dealing with
float currValue=wave.update();

The generators have another useful feature, though: A state stack. If you’ve used Processing or OpenGL before, you have likely come across the concept in the form of pushMatrix()/popMatrix() calls to save and restore the state of the graphical transformation matrix. The wave generators implement the same idea and so can be easily used to create the 3D meshes above.

// save current wave state
wave.push();

for(int i=0; i<10; i++) {
  float lookahead=wave.update();
  // do something clever
}

// restore wave to previous state
wave.pop();

Leave a Reply