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:
- : time, represented on the x-axis. In the Python code,
tis a discrete array of sample points, not a true continuous variable. - : amplitude. The signal swings from to , so the full peak-to-peak range is . All formulas produce a bipolar (AC-coupled) signal centered on zero.
- : frequency in Hz
- : period
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()
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()
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()
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()
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()
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()
Sawtooth wave
pp.plot(t, a * 2 * (f * t - np.floor(f * t + 1/2)))
pp.grid()
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()