reference

Waveforms Formulas

The four waveforms below, sine, square, triangle, and sawtooth, are the building blocks of subtractive synthesis. This page collects their mathematical definitions along with the equivalent NumPy expressions, which are useful for studying waveform shapes, experimenting with filters in a Jupyter notebook, or as a reference when implementing a signal generator on a microcontroller.

Variables

In the following formulas, the variables are:

To add a phase offset (in radians) to any formula, replace with .

Python setup

We start by importing the numpy and matplotlib packages and defining some variables:

import matplotlib.pyplot as pp
import numpy as np

f = 1            # [Hz]
p = 1/f          # period in [s]
a = 3            # amplitude; signal swings from -a to +a, peak-to-peak = 2*a
periods = 2      # show 2 periods
samples = 1000   # sample points (resolution of the graph)

t = np.linspace(0, periods/f, samples)    # evenly spaced time values over the interval

Sine wave

pp.plot(t, a * np.sin(2 * np.pi * f * t))
pp.grid()
Sine wave
Sine wave

The sine wave contains only the fundamental frequency, no harmonics. It is the only waveform that is already its own Fourier component, which is why it sounds the most pure and is used as the baseline for spectral analysis.

Square wave

The (signum) function returns for negative inputs, for positive inputs, and at exactly zero. In practice the zero case is instantaneous and has no audible effect. The formula works by generating a sine wave and then hard-clipping it to .

pp.plot(t, a * np.sign(np.sin(2 * np.pi * f * t)))
pp.grid()
Square wave
Square wave

A square wave contains only odd harmonics (), with amplitudes decreasing as where is the harmonic number. This gives it a hollow, reedy timbre, characteristic of clarinets and certain organ pipes.

Alternative with the floor function

pp.plot(t, a * (2 * (2*np.floor(f * t) - np.floor(2 * f * t)) + 1))
pp.grid()
Square wave (floor formula)
Square wave (floor formula)

The floor-based formula avoids trigonometric functions entirely and maps well to integer arithmetic on microcontrollers, where sin() may be unavailable or too slow.

Triangle wave

pp.plot(t, a * abs(((t-p/4) % p) - (p/2)) * 4 / p - a)
# '%' can be replaced by the numpy mod function:
# a * abs(np.mod((t-p/4), p) - (p/2)) * 4 / p - a
pp.grid()
Triangle wave
Triangle wave

Like the square wave, the triangle contains only odd harmonics (), but their amplitudes fall off much faster, as . The softer slope transitions produce a mellower, more flute-like sound, and the waveform is significantly easier to low-pass filter into a sine than a square wave.

Alternative as the absolute value of a shifted sawtooth

pp.plot(t, a * (4 * abs(f * t - np.floor(f * t + 1/2)) - 1))
pp.grid()
Triangle wave (sawtooth-derived)
Triangle wave (sawtooth-derived)

This form produces a triangle with a phase offset relative to the previous formula. If we correct the phase:

With phase corrected

pp.plot(t, a * (4 * abs(f * (t+1/4) - np.floor(f * (t+1/4) + 1/2)) - 1))
pp.grid()
Triangle wave (phase corrected)
Triangle wave (phase corrected)

Sawtooth wave

pp.plot(t, a * 2 * (f * t - np.floor(f * t + 1/2)))
pp.grid()
Sawtooth wave
Sawtooth wave

The sawtooth contains all harmonics, both odd and even (), with amplitudes decreasing as . This makes it the spectrally richest of the four waveforms and the most common starting point for subtractive synthesis, as it provides the most material for filters to shape.

Inverse sawtooth

pp.plot(t, 2 * a * (np.floor(f * t - 1/2) - f * t + 1))
pp.grid()
Inverse sawtooth wave
Inverse sawtooth wave