Depending on what you’re using the triangle wave for, there’s a few different ways to create them. You can get a pure triangle wave, or you can build a more natural one using sine waves. For both you’ll need:
- Sample Rate: how many slices per second. Usually audio is at a minimum 44100 samples per second.
- Phase: wrapped value from 0 to 1 to keep track of the position of the wave in time
- Frequency: frequency of the waveshape
- Phase Delta: How much the phase changes each increment of the sample rate (freq / sampleRate)
- A Phasor: Similar to a Sawtooth Oscillator, except it’s only positive, tracking the phase from 0 to 1.
Draw the Triangle
(pseudocode)
float sampleRate = 44100.0f; // Hz
float phase = 0.0f;
float freq = 440.0f; // Hz
float phaseDelta = freq / sampleRate; // Amount to increment phase each sample
// for each sample occurring at sampleRate, or
// continuously at sample rate, whichever suits your needs
phase += phaseDelta;
// Wrap phase
if (phase > 1.0f)
phase -= 1.0f;
This should make the Phasor: A ramp from 0 to 1 with the wavelength of your frequency. It starts at zero, and at 1 it is at the end of the cycle and wraps back to zero.
If we offset this by -0.5, we have a sawtooth wave that goes from -0.5 to 0.5
Multiplying by 2 will normalize between -1 and 1
To make that Saw into a Triangle, take the absolute value. All the negative values get “folded up” and it makes a triangle. But it’s all in the positive, so offset it again by -0.5 to get it from -0.5 to 0.5, and multiply by 2 to normalize between -1 and 1
// Step by step
float makeSaw = phase - 0.5f;
float normalizeSaw = makeSaw * 2.0f;
float makeTraingle = fabsf(normalizeSaw);
float offsetTriangle = makeTriangle - 0.5f;
float normalizeTriangle = offsetTriangle * 2.0f;
OR
// All in one
float outputValue = ( fabsf( 2 * (phase - 0.5f) ) - 0.5f ) * 2.0f;
The other option, to build a triangle wave by adding multiple sine waves together. Building waves using sine waves is considered more natural sounding, or less harsh, because we can limit the harmonic content, avoiding really harsh high frequencies.
To make a Sine wave is relatively easy. Just find the sine function of the phase time two pi
float sineSample = sin(phase * TWO_PI);
Now start adding sine waves up the harmonic series for that frequency. For a triangle wave, you’re only using the odd harmonics. Ever alternating harmonic is multiplied by -1. Then the amplitude of each harmonic is scaled by 1 / n^2
the harmonic series is:
fundamental = 1
2nd harmonic = 2/1
3rd harmonic= 3/2
4th harmonic= 4/3
5th harmonic = 5/4
etc...
That just keeps going up forever, so you choose where to stop. For the triangle it would be
// harmonic = (freq * ratio) * scale
fundamental = (freq * 1) * ( 1 / 1^2)
3rdHarmonic = (freq * 3/2) * (1 / 3^2) * -1
5thHarmonic = (freq * 5/4) * (1 / 5^2)
7thHarmonic = (freq * 7/6) * (1 / 7^2) * -1
etc...
Either way, you now have a Triangle Wave between -1 and 1. So to control the amplitude, just multiply all the samples by your gain factor.