In the previous article of this series, I discussed terminology and some basic math that must be understood before moving forward. In this article we are going to apply what we learned by building electronic music components entirely in software.

Before we do, however, we have one more important topic that needs discussion. You may recall me mentioning the MiniMoog and PSynth synthesizers in the first article. What these devices have in common is they both utilize subtractive synthesis as their means of sound production. With subtractive synthesis, you start out with a harmonically rich signal source (typically a square or sawtooth wave) and you apply a low-pass filter to selectively remove higher frequency harmonics with the result being a musically useful effect. The harmonic complexity of the signal source and the cut-off frequency and resonance of the filter are controlled in order to simulate the timbre of instruments. Of course, both the MiniMoog and PSynth also provide a sine wave signal source which, while not harmonically rich, can be used for musical purposes.

The Basic Waveforms

Joseph Fourier (1768–1830) proved that complex, harmonically rich, periodic waveforms comprise a series of sine and cos (a cos wave is a sine wave with a phase shift of 90 degrees) terms of increasing frequency. This Wikipedia page has an instructive animation of how, by adding harmonic terms to a base sine wave, a sawtooth wave shape can be produced.

For our use here, we will create an oscillator signal source that can produce three waveforms / wave shapes: sine wave, square wave and sawtooth wave. Each of these wave shapes has characteristics that make it suitable for use in electronic music.

A sine wave is a very pure sound source that has little in the way of harmonic complexity. A sine wave has its fundamental frequency and depending upon harmonic distortion has few (if any), low amplitude harmonics. The sine wave our oscillator will produce looks exactly like the textbook examples of sine waves.

A square wave , unlike a sine wave, contains odd harmonics (which are integer multiples of the fundamental frequency) which gives it more harmonic complexity and the richness required for subtractive synthesis. Square waves are often described as hollow sounding and are useful in electronic music.

As the name implies, sawtooth wave shapes resemble a saw blade. They rises linearly to maximum value then drops straight down to minimum value only to start again. A sawtooth wave shape contains both even and odd integer harmonics of the fundamental frequency and is good for simulating bowed string instruments like violins and cellos.

As we generate complex wave shapes in software, the Nyquist limit discussed in the previous article must be kept in mind because both square and sawtooth wave shapes have higher order harmonics that can exceed the available bandwidth. As you may recall, if we try to reproduce a frequency above the Nyquist limit (greater than or equal to one half the sampling rate), false low frequency signal components to be added into the original signal. This results in distortions that can be detrimental to signal fidelity.

There are two approaches for dealing with this problem. The first, is to generate our complex wave shapes from a Fourier series which by design doesn't have any terms above the Nyquist limit and the second approach is to ignore the problem completely. In our application it is safe to ignore the problem because:

Any distortion caused by aliasing only adds to the complexity of our signal sources which can be considered a good thing.

The frequencies we typically generate for musical purposes are relatively low which means that many of the harmonics making up the square and sawtooth wave shapes have a low enough amplitude to not be disruptive.

A Basic Oscillator in Java

A basic oscillator is just that; an oscillator without frills and implemented by the BasicOscillator class (Listing One). A BasicOscillator has two fundamental features: the ability to control its frequency and to control the wave shape it produces. The BasicOscillator provide samples, as it is the source of samples for other electronic music components. Being a sample provider means that whenever its getSamples method is called, it must fill the sample buffer with samples of the required type and frequency.

Listing One: A basic oscillator written in Java.



public class BasicOscillator implements SampleProviderIntfc { /** * Waveshape enumeration */ public enum WAVESHAPE { SIN, SQU, SAW } /** * Basic Oscillator Class Constructor * * Default instance has SIN waveshape at 1000 Hz */ public BasicOscillator() { // Set defaults setOscWaveshape(WAVESHAPE.SIN); setFrequency(1000.0); } /** * Set waveshape of oscillator * * @param waveshape Determines the waveshape of this oscillator */ public void setOscWaveshape(WAVESHAPE waveshape) { this.waveshape = waveshape; } /** * Set the frequency of the oscillator in Hz. * * @param frequency Frequency in Hz for this oscillator */ public void setFrequency(double frequency) { periodSamples = (long)(SamplePlayer.SAMPLE_RATE / frequency); } /** * Return the next sample of the oscillator's waveform * * @return Next oscillator sample */ protected double getSample() { double value; double x = sampleNumber / (double) periodSamples; switch (waveshape) { default: case SIN: value = Math.sin(2.0 * Math.PI * x); break; case SQU: if (sampleNumber < (periodSamples / 2)) { value = 1.0; } else { value = -1.0; } break; case SAW: value = 2.0 * (x - Math.floor(x + 0.5)); break; } sampleNumber = (sampleNumber + 1) % periodSamples; return value; } /** * Get a buffer of oscillator samples * * @param buffer Array to fill with samples * * @return Count of bytes produced. */ public int getSamples(byte [] buffer) { int index = 0; for (int i = 0; i < SamplePlayer.SAMPLES_PER_BUFFER; i++) { double ds = getSample() * Short.MAX_VALUE; short ss = (short) Math.round(ds); buffer[index++] = (byte)(ss >> 8); buffer[index++] = (byte)(ss & 0xFF); } return SamplePlayer.BUFFER_SIZE; } // Instance data private WAVESHAPE waveshape; private long periodSamples; private long sampleNumber; }