Standard library · Encoding and data

audio/synth

Making sound from nothing: oscillators, envelopes and voices.

import audio.synth

Everything else in this family reads sound that already exists. This package makes it. Six waveforms, an envelope to shape a note, three colours of noise, and four ways of building a voice that sounds like something rather than like a test tone.

The quickest way in is Tone for a bare waveform or ToneEnvelope for a note that starts and stops the way an instrument does. To play a phrase, fill a vector of Note and hand it to RenderNotes, which mixes and pans them into one clip.

Things to know

  • An oscillator carries its own phase, so stepping it with OscNext in a loop gives a continuous tone. WaveValue is the stateless form.
  • A note lasts longer than you asked: ADSRTotal adds the release onto the hold, and that is the length ToneEnvelope gives you.
  • Chord divides by the number of notes, so three notes together do not clip. RenderNotes does not, so give its notes a gain that leaves room.
  • For which notes to play, see audio/music, which also carries the Persian dastgah; to play a file of notes, see audio/midi.

Types

Osc

struct Osc
An oscillator: a waveform, a pitch, and where in the cycle it is.
NameTypeDescription
waveint = 0Which waveform it makes.
freqf64 = 440Its pitch in hertz.
phasef64 = 0Where in the cycle it is, from 0 to 1.
dutyf64 = 0.5How much of a pulse cycle is high.
rateint = 44100The sample rate it is stepping at.
synth-Osc.salam
import audio
import audio.synth
import math

func main:
    mut o := synth.NewOsc(synth.WaveSine, 440.0, 44100)
    println o.wave, o.freq, o.rate, o.phase
    first := synth.OscNext(o)
    println math.Abs(first) < 0.0001, o.phase > 0.0

    println synth.WaveValue(synth.WaveSine, 0.0, 0.5)
    println synth.WaveValue(synth.WaveSaw, 0.5, 0.5)
    println synth.WaveValue(synth.WaveSquare, 0.25, 0.5), synth.WaveValue(synth.WaveSquare, 0.75, 0.5)
    println synth.WaveValue(synth.WaveTriangle, 0.25, 0.5)
    println synth.WaveValue(synth.WavePulse, 0.1, 0.25)

    t := synth.Tone(440.0, 0.05, 22050, synth.WaveSine)
    println t.frames, t.rate, audio.Peak(t) > 0.9

    e := synth.ADSR { attack = 0.1, decay = 0.1, sustain = 0.5, release = 0.2 }
    println e.attack, e.sustain
    println synth.ADSRAt(e, 0.0, 1.0), synth.ADSRAt(e, 0.05, 1.0), synth.ADSRAt(e, 0.1, 1.0)
    println synth.ADSRAt(e, 0.2, 1.0), synth.ADSRAt(e, 0.5, 1.0)
    println synth.ADSRAt(e, 1.2, 1.0) < 0.0001, synth.ADSRAt(e, 5.0, 1.0)
    println synth.ADSRTotal(e, 1.0)

    shaped := synth.ToneEnvelope(440.0, 0.05, 22050, synth.WaveSine, e, 0.8)
    println audio.Sample(shaped, 0, 0), audio.Peak(shaped) <= 0.81

    mut d := t
    audio.Free(d)
    mut d2 := shaped
    audio.Free(d2)
end
Output0 440 44100 0 true true 0 0 1 -1 0 1 1103 22050 true 0.1 0.5 0 0.5 1 0.5 0.5 true 0 1.2 0 true

ADSR

struct ADSR
An envelope: how a note rises, settles, holds and falls.
NameTypeDescription
attackf64 = 0.01Seconds from silence to full.
decayf64 = 0.05Seconds from full down to the sustain level.
sustainf64 = 0.7The level a held note rests at.
releasef64 = 0.1Seconds from the sustain level back to silence.

The example for this item is shown with the ones above: synth-Osc.salam

Note

struct Note
One note in a phrase: which, when, how long and how loud.
NameTypeDescription
noteint = 60The MIDI note number.
startf64 = 0When it starts, in seconds.
lengthf64 = 0.5How long it is held, in seconds.
gainf64 = 0.5How loud it is.
waveint = 0Which waveform plays it.
panf64 = 0.5Where it sits between the speakers, 0 left to 1 right.
synth-Voice.salam
import audio
import audio.music
import audio.synth

func main:
    mut freqs := music.DastgahFreqs(57, music.DastgahShur)
    ch := synth.Chord(freqs, 0.05, 22050, synth.WaveSine)
    println ch.frames, audio.Peak(ch) > 0.1, audio.IsClipped(ch)
    freqs.free()

    wn := synth.NoiseWhite(0.05, 22050)
    pn := synth.NoisePink(0.05, 22050)
    bn := synth.NoiseBrown(0.05, 22050)
    println audio.RMS(wn) > 0.3, audio.RMS(pn) > 0.01, audio.RMS(bn) > 0.001

    fm := synth.FMTone(220.0, 2.0, 3.0, 0.05, 22050)
    println fm.frames, audio.Peak(fm) > 0.5
    add := synth.Additive(220.0, 6, 0.6, 0.05, 22050)
    println add.frames, audio.Peak(add) > 0.1
    ks := synth.KarplusStrong(220.0, 0.05, 22050, 0.99)
    println ks.frames, audio.Peak(ks) > 0.1

    one := synth.NoteAt(60, 0.0, 0.1, 0.4, synth.WaveSine)
    println one.note, one.start, one.length, one.gain, one.wave

    mut phrase := Vector {} as Vector<synth.Note>
    mut scale := music.ScaleNotes(60, music.ScaleMajor, 4)
    mut k := 0
    until k < scale.len():
        phrase.push(synth.NoteAt(scale.get(k), (k as f64) * 0.05, 0.04, 0.4, synth.WaveSine))
        k += 1
    end
    scale.free()
    e := synth.ADSR { attack = 0.005, decay = 0.01, sustain = 0.6, release = 0.02 }
    song := synth.RenderNotes(phrase, 22050, 2, e)
    println song.channels, song.frames > 0, audio.Peak(song) > 0.1, audio.IsClipped(song)
    phrase.free()
    nothing := synth.RenderNotes(Vector {} as Vector<synth.Note>, 22050, 1, e)
    println audio.IsEmpty(nothing)

    mut a := ch
    audio.Free(a)
    mut b := wn
    audio.Free(b)
    mut c := pn
    audio.Free(c)
    mut d := bn
    audio.Free(d)
    mut f := fm
    audio.Free(f)
    mut g := add
    audio.Free(g)
    mut h := ks
    audio.Free(h)
    mut i := song
    audio.Free(i)
end
Output1103 true false true true true 1103 true 1103 true 1103 true 60 0 0.1 0.4 0 2 true true false true

Constants and variables

Waveform numbers Wave…

Which waveform an oscillator or a note should use.
NameValue
WaveSine0
WaveSquare1
WaveSaw2
WaveTriangle3
WaveNoise4
WavePulse5

The example for this item is shown with the ones above: synth-Osc.salam

Functions

NewOsc

func NewOsc(wave: int, freq: f64, rate: int): Osc
An oscillator ready to step.

The example for this item is shown with the ones above: synth-Osc.salam

OscNext

func OscNext(o &: Osc): f64
The next sample, moving the oscillator on by one.

The example for this item is shown with the ones above: synth-Osc.salam

WaveValue

func WaveValue(wave: int, phase: f64, duty: f64): f64
One point of a waveform at a phase, without an oscillator.

The example for this item is shown with the ones above: synth-Osc.salam

ADSRAt

func ADSRAt(e: ADSR, t: f64, held: f64): f64
The envelope level at a time, given how long the note is held.

The example for this item is shown with the ones above: synth-Osc.salam

ADSRTotal

func ADSRTotal(e: ADSR, held: f64): f64
How long a note really lasts, including the release after the hold.

The example for this item is shown with the ones above: synth-Osc.salam

Tone

func Tone(freq: f64, seconds: f64, rate: int, wave: int): audio.Clip
A clip of one waveform at one pitch.

The example for this item is shown with the ones above: synth-Osc.salam

ToneEnvelope

func ToneEnvelope(freq: f64, seconds: f64, rate: int, wave: int, e: ADSR, gain: f64): audio.Clip
A clip of one note with an envelope and a gain, which is how a note should sound rather than a test tone.

The example for this item is shown with the ones above: synth-Osc.salam

Chord

func Chord(freqs: Vector<f64>, seconds: f64, rate: int, wave: int): audio.Clip
A clip of several frequencies together, scaled so they do not clip.

The example for this item is shown with the ones above: synth-Voice.salam

NoiseWhite

func NoiseWhite(seconds: f64, rate: int): audio.Clip
White noise, flat across the spectrum.

The example for this item is shown with the ones above: synth-Voice.salam

NoisePink

func NoisePink(seconds: f64, rate: int): audio.Clip
Pink noise, falling off with frequency.

The example for this item is shown with the ones above: synth-Voice.salam

NoiseBrown

func NoiseBrown(seconds: f64, rate: int): audio.Clip
Brown noise, falling off faster still.

The example for this item is shown with the ones above: synth-Voice.salam

FMTone

func FMTone(carrier: f64, ratio: f64, index: f64, seconds: f64, rate: int): audio.Clip
Frequency modulation: one oscillator bending another, which gets you bells and basses cheaply.

The example for this item is shown with the ones above: synth-Voice.salam

Additive

func Additive(freq: f64, partials: int, decay: f64, seconds: f64, rate: int): audio.Clip
A tone built by adding harmonics, each quieter than the one below.

The example for this item is shown with the ones above: synth-Voice.salam

KarplusStrong

func KarplusStrong(freq: f64, seconds: f64, rate: int, damping: f64): audio.Clip
A plucked string, made by filtering a burst of noise around a delay line.

The example for this item is shown with the ones above: synth-Voice.salam

RenderNotes

func RenderNotes(notes: Vector<Note>, rate: int, channels: int, e: ADSR): audio.Clip
Renders a whole phrase into one clip, mixing the notes and panning them.

The example for this item is shown with the ones above: synth-Voice.salam

NoteAt

func NoteAt(note: int, start: f64, length: f64, gain: f64, wave: int): Note
Shorthand for one Note in a phrase.

The example for this item is shown with the ones above: synth-Voice.salam