A synth from a sine wave, and the aliasing nobody warns you about
A sawtooth wave, the bright buzzy staple of every synth, is not a fundamental thing. It is a sum of sine waves, and once you know the recipe you can build one from scratch in a few lines. You can also, with the exact same code, make it sound horrible, and the reason why is one of the most important facts in digital audio.
A saw is sines at one over n
Fourier's result is that any periodic wave is a sum of sine waves at integer multiples of its base frequency, the harmonics. For a sawtooth the recipe is simple: harmonic n has amplitude 1/n.
import numpy as np
def saw(freq, dur, sr=44100, n_harmonics=40):
t = np.linspace(0, dur, int(sr * dur), endpoint=False)
wave = np.zeros_like(t)
for n in range(1, n_harmonics + 1):
wave += (1 / n) * np.sin(2 * np.pi * n * freq * t)
return wave
Start with one harmonic and you have a pure sine. Add the second and third and it gets reedier. Keep going and the shape sharpens into the ramp-and-drop of a sawtooth, and the sound brightens into that classic buzz. A square wave is the same idea with only the odd harmonics; a triangle, odd harmonics that fall off as 1/n². Three lines and you have a small sound-design lab.
This works. Play a low note, say 110 Hz, and it sounds like a real saw. Then play a high one, and it falls apart.
Why the high note sounds like glass
Play that same saw at 4000 Hz with 40 harmonics and you get a metallic, detuned, inharmonic mess that no acoustic instrument has ever made. The code is not buggy. You have hit the sampling theorem.
A digital signal sampled at sr samples per second can only represent frequencies up to sr / 2, the Nyquist frequency, about 22 kHz at CD rate. Ask for a frequency above that and it does not vanish or error. It folds back, or aliases, appearing as a different, lower frequency given by reflecting it under Nyquist. A 23 kHz tone at a 44.1 kHz rate comes back as roughly 21 kHz.
Now look at the saw. Its harmonics are at n × freq. At 4000 Hz, the 6th harmonic is already at 24 kHz, past Nyquist, and the code dutifully tries to synthesize it. It folds back to an audible frequency that is not an integer multiple of 4000, an inharmonic partial, dissonant against the real ones. Harmonics 7, 8, 9 fold to their own wrong frequencies. The higher the note, the more harmonics cross the line, and the more garbage piles into the sound. That is the broken-glass timbre: a fundamental surrounded by a cloud of aliases that have no business being there.
The fix is to know when to stop
The cure is to refuse to synthesize any harmonic above Nyquist. Only add the ones that fit:
def saw_bandlimited(freq, dur, sr=44100):
t = np.linspace(0, dur, int(sr * dur), endpoint=False)
wave = np.zeros_like(t)
n = 1
while n * freq < sr / 2: # stop at Nyquist
wave += (1 / n) * np.sin(2 * np.pi * n * freq * t)
n += 1
return wave
At 110 Hz this includes about 200 harmonics, a rich saw. At 4000 Hz it includes only five, a duller but clean saw, no aliases. The number of harmonics now depends on the note, which is exactly right: a band-limited oscillator naturally thins out as it climbs, because there is less room under Nyquist. This is why real synthesizers do not just sum harmonics blindly; they use band-limited oscillators (techniques like BLEP and BLIT) to get the bright edge of a saw without the aliasing.
The trap is broader than this one function. Anyone who has written np.sign(np.sin(...)) to make a "square wave" has made an aliasing machine: a hard edge has infinitely many harmonics, all of them folding. The naive waveform looks right on a plot and sounds wrong in your ears, and the gap between those two is the whole reason digital audio is harder than it appears.
What you actually learned
Additive synthesis is the gentle half of the lesson: timbre is the recipe of harmonics, and you can dial a sound by choosing them. Aliasing is the sharp half: a digital signal is not a continuous one, it is samples, and the sampling theorem is a hard wall you cannot build through, only around. Most synth tutorials teach the first half and skip the second, which is how you end up with code that demos fine on a bass note and embarrasses you on a lead. Build both, play the high note before and after the fix, and you will hear the sampling theorem instead of just reading about it.