Cookbook

A sine, and a saw

The smallest thing that makes a sound, and the smallest thing that makes an interesting one.

Anyone starting here.

An oscillator does not need incoming audio. It needs a clock and a memory of where it is in its cycle. Every frame it moves a little farther around that cycle and turns its position into a sample.

This example uses a parabolic approximation to a sine. It is a rounded wave made from a ramp, so it contains more harmonics than a mathematical sine. We will use that distinction to hear how a waveform's shape changes its tone.

Controls#

ParameterDefaultRangeWhat you hear
freq220 Hz20–8,000 HzThe number of cycles per second; doubling it raises the pitch one octave.
level0.60–1Output amplitude: 0 is silent and 1 is full level.

There is no input stream. out is the generated mono signal.

The complete patch#

A sine, and a sawPlayground
processor Sine
{
    param  float freq  = 220.0f [20.0f, 8000.0f] smooth 10.0f;
    param  float level = 0.6f   [0.0f, 1.0f]     smooth 20.0f;
    output stream float out;

    float phase = 0.0f;

    void main()
    {
        loop
        {
            let step = freq / processor.frequency;
            let p    = phase + step;
            phase    = p > 1.0f ? p - 1.0f : p;
            let t = phase * 2.0f - 1.0f;
            let a = t > 0.0f ? t : -t;
            out <- 4.0f * t * (1.0f - a) * level;
            advance();
        }
    }
}

Follow one frame#

  1. freq / processor.frequency converts cycles per second to cycles per frame. At 220 Hz and 48 kHz the step is about 0.00458.
  2. phase advances and wraps back into one cycle. Because it is processor state, the next frame starts where this one finished.
  3. phase * 2.0f - 1.0f converts a 0…1 phase into a -1…1 ramp, called t.
  4. 4.0f * t * (1.0f - abs(t)) bends that ramp into the rounded waveform. The patch spells out the absolute value with a conditional.
  5. Multiplication by level controls amplitude before the sample is written.

The single-subtraction wrap assumes the phase step is less than one cycle. That holds for these frequency settings at common audio sample rates.

Try three waveforms#

Replace the output expression with sin(6.2831853f * phase) * level for a true sine, or t * level for a saw. The phase machinery stays the same; only the mapping from position to amplitude changes.

Listen at 220 Hz, then increase the frequency. The saw is brighter because of its harmonics. It is also not band-limited: harmonics above half the sample rate fold back as aliasing. This is a useful learning oscillator, not an alias-free oscillator design.

If it is silent: check that phase is state outside main(). A local phase reset to zero on each frame never travels around a cycle.

Next: put the saw through the filter.