build-a-wav-pcm-toolkit / lesson-28.md
Lesson 28 · Synthesis and the capstone

The sawtooth wave

A sawtooth ramps steadily from the negative peak up to the positive peak each cycle, then snaps back. It reuses the phase fraction from last lesson as a linear ramp. Today you generate one.

The goal

Generate a sawtooth wave by mapping the phase fraction to a linear ramp.

Start here - the target
TO DO
Scenario: A sawtooth ramps linearly across each cycle
Givenfrequency 1000 Hz, amplitude 10000, sample rate 4000, and 4 samples, with sample = round(amp * (2*frac - 1)) and frac = (f*n/sampleRate) mod 1
Whenthe wave is generated
Thenthe samples are [-10000, -5000, 0, 5000]
Andfrac 0 maps to -amp (the ramp bottom) and frac approaching 1 maps toward +amp
Background

A sawtooth rises in a straight line from the negative peak to the positive peak across each cycle, then drops instantly back - the shape that names it. Like the square it is harmonically rich (it contains every harmonic), which is why it is the classic starting point for subtractive synthesis. And it falls straight out of the phase fraction you already have.

Map the fraction, which runs 0 to 1 within a cycle, onto the amplitude range -1 to +1 with 2*frac - 1, then scale by amplitude and round. At frac = 0 that is -amp (the bottom of the ramp) and as frac approaches 1 it climbs toward +amp. With f = 1000, sr = 4000 the fractions 0, 0.25, 0.5, 0.75 give [-10000, -5000, 0, 5000] - a clean rising ramp. Three waveforms in, you have a small oscillator bank; next you shape their loudness over time with an envelope.

Make it work
func sawtooth(freq, amp, sampleRate, count int) []int {
out := make([]int, count)
for n := 0; n < count; n++ {
frac := math.Mod(float64(freq)*float64(n)/float64(sampleRate), 1.0)
out[n] = int(math.Round(float64(amp) * (2*frac - 1))) // -1..+1 ramp
}
return out
}
CheckpointDONE
You can synthesize a sawtooth wave. Commit and stop here.