Standard library · Encoding and data

audio/dsp

A Fourier transform, windows, filters and resampling for sound.

import audio.dsp

This is the arithmetic behind sound: a Fourier transform to see what frequencies are in a block, windows to taper the block first, filters to shape it and resampling to move it between rates. The codecs in this family lean on the same code, which is why it lives here rather than inside any one of them.

Everything works on a clip or on plain arrays of f64. The transform is radix-2, so a length has to be a power of two: use NextPow2 to pad up to one.

Things to know

  • FFT and IFFT work in place on two arrays, the real parts and the imaginary parts. IFFT already divides by the length, so it undoes FFT exactly.
  • A real signal shows the same strength at bin k and at bin n - k, so only the first half of a spectrum tells you anything new.
  • Taper a block with ApplyWindow before transforming it, or a tone that does not fit a whole number of cycles will smear across every bin.
  • The filters follow the well known cookbook formulas. A Biquad remembers two samples, so BiquadReset it before a different clip, and prefer BiquadProcessAll, which gives each channel its own copy.
  • BiquadResponse tells you what a filter does at a frequency without running any sound through it, which makes filters easy to test.
  • ResampleSinc is the one to use on music; ToRate in audio is the quick linear version.

Types

Spectrum

struct Spectrum
A spectrogram: one magnitude row per analysis frame.
NameTypeDescription
binsint = 0How many frequency bins each row holds, which is half the window.
framesint = 0How many rows there are.
rateint = 0The sample rate the clip had, needed to turn a bin into a frequency.
hopint = 0How many samples apart the rows are.
datavoid*The magnitudes, row after row. Release them with FreeSpectrum.
dsp-Analyze.salam
import audio
import audio.dsp
import audio.synth
import math

func main:
    low := synth.Tone(110.0, 0.1, 22050, synth.WaveSine)
    high := synth.Tone(1760.0, 0.1, 22050, synth.WaveSine)
    println dsp.ZeroCrossingRate(high) > dsp.ZeroCrossingRate(low) * 4.0
    println dsp.ZeroCrossingRate(audio.Clip {})

    each f in [220.0, 440.0]:
        t := synth.Tone(f, 0.1, 22050, synth.WaveSine)
        got := dsp.DetectPitch(t, 0, 2048)
        println math.Abs(got - f) / f < 0.05
        mut d := t
        audio.Free(d)
    end
    println dsp.DetectPitch(audio.Clip {}, 0, 2048)

    rich := synth.Additive(220.0, 8, 0.7, 0.12, 22050)
    mut s := dsp.STFT(rich, 1024, 256, dsp.WindowHann)
    println s.bins, s.rate, s.hop, s.frames > 1
    println dsp.SpectrumAt(s, 0, 9999)
    cen := dsp.SpectralCentroid(s, 1)
    println cen > 100.0, cen < 5000.0
    println dsp.SpectralRolloff(s, 1, 0.85) > 0.0
    flat := dsp.SpectralFlatness(s, 1)
    println flat >= 0.0, flat <= 1.0
    println dsp.SpectralFlux(s, 0)

    noise := synth.NoiseWhite(0.12, 22050)
    mut ns := dsp.STFT(noise, 1024, 256, dsp.WindowHann)
    println dsp.SpectralFlatness(ns, 1) > flat

    loud := synth.Tone(440.0, 0.1, 22050, synth.WaveSine)
    lu := dsp.LoudnessLUFS(loud)
    println lu < 0.0, lu > -40.0
    mut soft := audio.Clone(loud)
    audio.Gain(soft, -20.0)
    println math.Abs((dsp.LoudnessLUFS(soft) - lu) + 20.0) < 1.0
    mut norm := audio.Clone(loud)
    dsp.NormalizeLUFS(norm, -16.0)
    println math.Abs(dsp.LoudnessLUFS(norm) + 16.0) < 1.0
    println dsp.TruePeakDb(loud) >= audio.PeakDb(loud) - 0.01

    dsp.FreeSpectrum(s)
    dsp.FreeSpectrum(ns)
    audio.Free(low)
    audio.Free(high)
    audio.Free(rich)
    audio.Free(noise)
    audio.Free(loud)
    audio.Free(soft)
    audio.Free(norm)
end
Outputtrue 0 true true 0 512 22050 256 true 0 true true true true true 0 true true true true true true

Complex

struct Complex
A complex number, used by the Fourier transform.
NameTypeDescription
ref64 = 0The real part.
imf64 = 0The imaginary part.
dsp-Complex.salam
import audio.dsp
import math

func main:
    a := dsp.Complex { re = 3.0, im = 4.0 }
    b := dsp.Complex { re = 1.0, im = -2.0 }
    println a.re, a.im
    println dsp.CAbs(a)
    println dsp.CAdd(a, b).re, dsp.CAdd(a, b).im
    println dsp.CSub(a, b).re, dsp.CSub(a, b).im
    println dsp.CMul(a, b).re, dsp.CMul(a, b).im
    println dsp.CScale(a, 2.0).re, dsp.CScale(a, 2.0).im
    println dsp.CConj(a).re, dsp.CConj(a).im
    up := dsp.Complex { re = 0.0, im = 1.0 }
    println math.Abs(dsp.CArg(up) - math.PI / 2.0) < 0.000000001
    p := dsp.CFromPolar(2.0, 0.0)
    println math.Abs(p.re - 2.0) < 0.000000001, math.Abs(p.im) < 0.000000001
    println dsp.CAbs(dsp.CFromPolar(5.0, 1.0))
end
Output3 4 5 4 2 2 6 11 -2 6 8 3 -4 true true true 5

Compressor

struct Compressor
How hard to squeeze a signal and how fast to react.
NameTypeDescription
thresholdf64 = -20The level in decibels above which the squeezing starts.
ratiof64 = 4How much of the excess to keep. A ratio of 4 lets a quarter of it through.
attackf64 = 0.005How many seconds to take hold once the signal goes over.
releasef64 = 0.1How many seconds to let go once it comes back under.
makeupf64 = 0Decibels of gain added afterwards, to win back what the squeezing cost.
dsp-Effect.salam
import audio
import audio.dsp
import audio.synth
import math

func main:
    src := synth.Tone(440.0, 0.1, 22050, synth.WaveSine)
    println src.frames, src.rate

    e := dsp.Echo(src, 0.02, 2, 0.5)
    println e.frames, e.frames == src.frames + 2 * 441
    println math.Abs(audio.Sample(e, 451, 0) - (audio.Sample(src, 451, 0) + audio.Sample(src, 10, 0) * 0.5)) < 0.001

    rv := dsp.Reverb(src, 0.5, 0.3)
    println rv.frames, audio.Peak(rv) <= 1.0
    dry := dsp.Reverb(src, 0.5, 0.0)
    println math.Abs(audio.Sample(dry, 50, 0) - audio.Sample(src, 50, 0)) < 0.0001

    ch := dsp.Chorus(src, 0.5, 2.0, 0.5)
    fl := dsp.Flanger(src, 0.5, 1.0, 0.5)
    println ch.frames, fl.frames
    println dsp.Correlation(src, ch) < 0.999, dsp.Correlation(src, fl) < 0.9999

    mut tr := audio.Clone(src)
    dsp.Tremolo(tr, 10.0, 1.0)
    println audio.RMS(tr) < audio.RMS(src), audio.Peak(tr) <= audio.Peak(src) + 0.0001

    mut dl := audio.Clone(src)
    dsp.Delay(dl, 0.01, 0.5, 0.5)
    println dl.frames == src.frames

    k := dsp.Compressor { threshold = -20.0, ratio = 8.0, attack = 0.001, release = 0.05 }
    mut hard := audio.Clone(src)
    dsp.CompressorProcess(k, hard)
    println audio.RMSDb(hard) < audio.RMSDb(src) - 6.0

    mut hot := audio.Clone(src)
    audio.Scale(hot, 4.0)
    dsp.LimiterProcess(hot, -1.0)
    println audio.PeakDb(hot) <= -1.0 + 0.001

    mut gated := audio.Clone(src)
    audio.Gain(gated, -60.0)
    dsp.GateProcess(gated, -40.0, 0.001)
    println audio.Peak(gated) < 0.0001

    longer := dsp.TimeStretch(src, 2.0)
    println longer.frames == src.frames * 2, longer.rate
    up := dsp.PitchShift(src, 12.0)
    println up.frames, up.rate
    println dsp.DetectPitch(up, 0, 2048) > 700.0

    audio.Free(src)
    audio.Free(e)
    audio.Free(rv)
    audio.Free(dry)
    audio.Free(ch)
    audio.Free(fl)
    audio.Free(tr)
    audio.Free(dl)
    audio.Free(hard)
    audio.Free(hot)
    audio.Free(gated)
    audio.Free(longer)
    audio.Free(up)
end
Output2205 22050 3087 true true 2205 true true 2205 2205 true true true true true true true true true 22050 2205 22050 true

Biquad

struct Biquad
A two pole, two zero filter: five coefficients and the two samples it remembers.
NameTypeDescription
b0f64 = 1The first feed forward coefficient.
b1f64 = 0The second feed forward coefficient.
b2f64 = 0The third feed forward coefficient.
a1f64 = 0The first feedback coefficient, already divided through by a0.
a2f64 = 0The second feedback coefficient, already divided through by a0.
z1f64 = 0The first remembered sample. Reset it before filtering a new clip.
z2f64 = 0The second remembered sample.
dsp-Filter.salam
import audio
import audio.dsp
import math

func tone(rate: int, freq: f64, frames: int): audio.Clip:
    mut c := audio.New(rate, 1, frames)
    mut i := 0
    until i < frames:
        audio.SetSample(c, i, 0, math.Sin(2.0 * math.PI * freq * (i as f64) / (rate as f64)))
        i += 1
    end
    ret c
end

func main:
    lp := dsp.LowPass(1000.0, 0.707, 48000)
    println dsp.BiquadResponse(lp, 100.0, 48000) > 0.99
    println math.Abs(dsp.BiquadResponse(lp, 1000.0, 48000) - 0.7071) < 0.01
    println dsp.BiquadResponse(lp, 10000.0, 48000) < 0.02
    hp := dsp.HighPass(1000.0, 0.707, 48000)
    println dsp.BiquadResponse(hp, 10000.0, 48000) > 0.99, dsp.BiquadResponse(hp, 100.0, 48000) < 0.02
    bp := dsp.BandPass(1000.0, 1.0, 48000)
    println math.Abs(dsp.BiquadResponse(bp, 1000.0, 48000) - 1.0) < 0.01, dsp.BiquadResponse(bp, 100.0, 48000) < 0.2
    nt := dsp.Notch(1000.0, 1.0, 48000)
    println dsp.BiquadResponse(nt, 1000.0, 48000) < 0.01, dsp.BiquadResponse(nt, 100.0, 48000) > 0.9
    ap := dsp.AllPass(1000.0, 0.707, 48000)
    println math.Abs(dsp.BiquadResponse(ap, 500.0, 48000) - 1.0) < 0.000001
    pk := dsp.Peaking(1000.0, 1.0, 6.0, 48000)
    println dsp.BiquadResponse(pk, 1000.0, 48000) > 1.98, dsp.BiquadResponse(pk, 1000.0, 48000) < 2.01
    ls := dsp.LowShelf(1000.0, 0.707, 6.0, 48000)
    println dsp.BiquadResponse(ls, 50.0, 48000) > 1.9, math.Abs(dsp.BiquadResponse(ls, 20000.0, 48000) - 1.0) < 0.05
    hs := dsp.HighShelf(1000.0, 0.707, 6.0, 48000)
    println dsp.BiquadResponse(hs, 20000.0, 48000) > 1.9

    mut hi := tone(48000, 10000.0, 2048)
    before := audio.RMS(hi)
    dsp.BiquadProcessAll(lp, hi)
    println audio.GainToDb(audio.RMS(hi) / before) < -20.0
    mut lo := tone(48000, 100.0, 2048)
    lo_before := audio.RMS(lo)
    dsp.BiquadProcessAll(lp, lo)
    println audio.GainToDb(audio.RMS(lo) / lo_before) > -1.0

    mut one := dsp.LowPass(1000.0, 0.707, 48000)
    dsp.BiquadStep(one, 1.0)
    println one.z1 != 0.0
    dsp.BiquadReset(one)
    println one.z1, one.z2
    println math.Abs(one.b0 - lp.b0) < 0.000000001

    mut ch := tone(8000, 100.0, 400)
    audio.Scale(ch, 0.5)
    mut k := 0
    until k < ch.frames:
        audio.AddSample(ch, k, 0, 0.25)
        k += 1
    end
    println audio.DcOffset(ch) > 0.2
    dsp.DCRemove(ch)
    println math.Abs(audio.DcOffset(ch)) < 0.000001
    mut single := dsp.LowPass(1000.0, 0.707, 8000)
    dsp.BiquadProcess(single, ch, 0)
    println ch.frames
    audio.Free(hi)
    audio.Free(lo)
    audio.Free(ch)
end
Outputtrue true true true true true true true true true true true true true true true true true 0 0 true true true 400

Constants and variables

Resampling quality Quality…

How carefully to resample. Linear is quickest, sinc is best on music.
NameValue
QualityLinear0
QualityCubic1
QualitySinc2
dsp-Resample.salam
import audio
import audio.dsp
import math

func tone(rate: int, freq: f64, frames: int): audio.Clip:
    mut c := audio.New(rate, 1, frames)
    mut i := 0
    until i < frames:
        audio.SetSample(c, i, 0, math.Sin(2.0 * math.PI * freq * (i as f64) / (rate as f64)))
        i += 1
    end
    ret c
end

func main:
    src := tone(44100, 440.0, 4410)
    up := dsp.ResampleSinc(src, 48000, 16)
    println up.rate, up.frames
    back := dsp.ResampleSinc(up, 44100, 16)
    println back.rate, back.frames
    inner := audio.Slice(src, 64, src.frames - 128)
    inner2 := audio.Slice(back, 64, back.frames - 128)
    println dsp.Correlation(inner, inner2) > 0.999
    cub := dsp.ResampleCubic(src, 48000)
    cback := dsp.ResampleCubic(cub, 44100)
    inner3 := audio.Slice(cback, 64, cback.frames - 128)
    println cub.rate, dsp.Correlation(inner, inner3) > 0.999
    println dsp.ResampleTo(src, 48000, dsp.QualityLinear).rate
    println dsp.ResampleTo(src, 48000, dsp.QualityCubic).rate
    println dsp.ResampleTo(src, 48000, dsp.QualitySinc).rate
    same := dsp.ResampleSinc(src, 44100, 16)
    println math.Abs(dsp.Correlation(src, same) - 1.0) < 0.000001
    println dsp.Correlation(src, audio.Clip {})
    println dsp.KaiserBeta
    down := dsp.ResampleSinc(src, 22050, 16)
    println down.rate, down.frames
    audio.Free(src)
    audio.Free(up)
    audio.Free(back)
    audio.Free(inner)
    audio.Free(inner2)
    audio.Free(cub)
    audio.Free(cback)
    audio.Free(inner3)
    audio.Free(same)
    audio.Free(down)
end
Output48000 4800 44100 4410 true 48000 true 48000 48000 48000 true 0 8.6 22050 2205

KaiserBeta

const KaiserBeta: f64 = 8.6
The beta the resampler uses for its Kaiser window.

The example for this item is shown with the ones above: dsp-Resample.salam

Window shapes Window…

Which window to taper a block with before a transform. Rectangular is no window at all.
NameValue
WindowRectangular0
WindowHann1
WindowHamming2
WindowBlackman3
WindowBlackmanHarris4
WindowSine5
WindowVorbis6
WindowKaiser7
dsp-Window.salam
import audio.dsp
import math

func main:
    println dsp.BesselI0(0.0)
    hann := dsp.MakeWindow(dsp.WindowHann, 9, 0.0)
    println hann.len(), hann.get(0) < 0.000000001, math.Abs(hann.get(4) - 1.0) < 0.000000001
    println hann.get(8) < 0.000000001
    println dsp.WindowValue(dsp.WindowRectangular, 0, 9, 0.0)
    println math.Abs(dsp.WindowValue(dsp.WindowHamming, 0, 9, 0.0) - 0.08) < 0.000000001
    println math.Abs(dsp.MakeWindow(dsp.WindowBlackman, 9, 0.0).get(4) - 1.0) < 0.000000001
    println math.Abs(dsp.MakeWindow(dsp.WindowBlackmanHarris, 9, 0.0).get(4) - 1.0) < 0.000000001
    println math.Abs(dsp.MakeWindow(dsp.WindowSine, 9, 0.0).get(4) - 1.0) < 0.000000001
    println dsp.MakeWindow(dsp.WindowVorbis, 9, 0.0).get(0) < 0.000000001
    println math.Abs(dsp.MakeWindow(dsp.WindowKaiser, 9, 8.6).get(4) - 1.0) < 0.000000001
    println dsp.WindowName(dsp.WindowHann), dsp.WindowName(dsp.WindowKaiser), dsp.WindowName(99)
    println dsp.WindowSum(dsp.WindowRectangular, 8, 0.0)
    mut buf := Vector {} as Vector<f64>
    mut i := 0
    until i < 8:
        buf.push(1.0)
        i += 1
    end
    dsp.ApplyWindow(buf.ref(0), 8, dsp.WindowHann, 0.0)
    println buf.get(0) < 0.000000001, buf.get(4) > 0.9
end
Output1 9 true true true 1 true true true true true true hann kaiser unknown 8 true true

Functions

ZeroCrossingRate

func ZeroCrossingRate(c: audio.Clip): f64
How often the signal crosses zero per sample. It rises with pitch and with noise.

The example for this item is shown with the ones above: dsp-Analyze.salam

FreeSpectrum

func FreeSpectrum(s &: Spectrum)
Releases the magnitudes a spectrogram holds.

The example for this item is shown with the ones above: dsp-Analyze.salam

STFT

func STFT(c: audio.Clip, size: int, hop: int, window: int): Spectrum
A spectrogram of a clip: a window slid along it, each position transformed. Release it with FreeSpectrum.

The example for this item is shown with the ones above: dsp-Analyze.salam

SpectrumAt

func SpectrumAt(s: Spectrum, frame: int, bin: int): f64
One magnitude, by row and bin. Outside the spectrogram it is 0.

The example for this item is shown with the ones above: dsp-Analyze.salam

SpectralCentroid

func SpectralCentroid(s: Spectrum, frame: int): f64
Where the weight of a row sits, in hertz. It tracks how bright a sound is.

The example for this item is shown with the ones above: dsp-Analyze.salam

SpectralRolloff

func SpectralRolloff(s: Spectrum, frame: int, share: f64): f64
The frequency below which a share of the energy sits.

The example for this item is shown with the ones above: dsp-Analyze.salam

SpectralFlatness

func SpectralFlatness(s: Spectrum, frame: int): f64
How noise-like a row is, from 0 for a pure tone to 1 for white noise.

The example for this item is shown with the ones above: dsp-Analyze.salam

SpectralFlux

func SpectralFlux(s: Spectrum, frame: int): f64
How much a row grew over the one before it, which is what onset detection is built on.

The example for this item is shown with the ones above: dsp-Analyze.salam

DetectPitch

func DetectPitch(c: audio.Clip, from_frame: int, size: int): f64
The pitch in hertz at a position, by autocorrelation. It takes the first strong peak rather than the biggest, which is what keeps it from answering an octave too low.

The example for this item is shown with the ones above: dsp-Analyze.salam

DetectOnsets

func DetectOnsets(c: audio.Clip, size: int, hop: int): Vector<int>
The frames where something starts, found from spectral flux.

The example for this item is shown with the ones above: dsp-Analyze.salam

DetectBPM

func DetectBPM(c: audio.Clip): f64
The tempo in beats per minute, by scoring how well each candidate lines up with the onsets.

The example for this item is shown with the ones above: dsp-Analyze.salam

LoudnessLUFS

func LoudnessLUFS(c: audio.Clip): f64
Loudness the way broadcasters measure it, after the ITU-R BS.1770 weighting.

The example for this item is shown with the ones above: dsp-Analyze.salam

NormalizeLUFS

func NormalizeLUFS(c &: audio.Clip, target: f64)
Scales a clip so its loudness lands on a target in LUFS.

The example for this item is shown with the ones above: dsp-Analyze.salam

TruePeakDb

func TruePeakDb(c: audio.Clip): f64
The peak after oversampling, which catches peaks that fall between two samples.

The example for this item is shown with the ones above: dsp-Analyze.salam

ResampleCubic

func ResampleCubic(c: audio.Clip, rate: int): audio.Clip
Changes the sample rate with a Catmull-Rom curve.

The example for this item is shown with the ones above: dsp-Resample.salam

ResampleSinc

func ResampleSinc(c: audio.Clip, rate: int, taps: int): audio.Clip
Changes the sample rate with a windowed sinc, filtering out what the new rate cannot hold.

The example for this item is shown with the ones above: dsp-Resample.salam

ResampleTo

func ResampleTo(c: audio.Clip, rate: int, quality: int): audio.Clip
Changes the sample rate, picking the method from a Quality number.

The example for this item is shown with the ones above: dsp-Resample.salam

Correlation

func Correlation(a: audio.Clip, b: audio.Clip): f64
How alike two clips are, from 1 for the same to 0 for unrelated. Handy for checking that a round trip kept the sound.

The example for this item is shown with the ones above: dsp-Resample.salam

IsPow2

func IsPow2(n: int): bool
True when the number is a power of two, which is what FFT needs.
dsp-FFT.salam
import audio.dsp
import math

func main:
    println dsp.IsPow2(64), dsp.IsPow2(63), dsp.NextPow2(1000), dsp.NextPow2(64)
    n := 64
    k := 5
    mut re := Vector {} as Vector<f64>
    mut im := Vector {} as Vector<f64>
    mut i := 0
    until i < n:
        ang := 2.0 * math.PI * (k as f64) * (i as f64) / (n as f64)
        re.push(math.Cos(ang))
        im.push(math.Sin(ang))
        i += 1
    end
    println dsp.FFTVec(re, im)
    mags := dsp.Magnitude(re.ref(0), im.ref(0), n)
    println dsp.PeakBin(mags), mags.get(k) > 63.9, mags.get(k) < 64.1
    mut leak := 0.0
    mut b := 0
    until b < n:
        if b != k and mags.get(b) > leak:
            leak = mags.get(b)
        end
        b += 1
    end
    println leak < 0.000000001
    pw := dsp.PowerSpectrum(re.ref(0), im.ref(0), n)
    println pw.get(k) > 4095.0
    ph := dsp.Phase(re.ref(0), im.ref(0), n)
    println math.Abs(ph.get(k)) < 0.000000001
    println dsp.BinFrequency(k, n, 48000), dsp.BinFrequency(0, n, 48000)
    println dsp.IFFTVec(re, im)
    println math.Abs(re.get(0) - 1.0) < 0.000000001, math.Abs(im.get(0)) < 0.000000001
    println dsp.FFT(re.ref(0), im.ref(0), 15)
    println dsp.PeakBin(Vector {} as Vector<f64>)
end
Outputtrue false 1024 64 true 5 true true true true true 3750 0 true true true false -1

NextPow2

func NextPow2(n: int): int
The first power of two at least this big, for padding a window out to a length FFT accepts.

The example for this item is shown with the ones above: dsp-FFT.salam

CAdd

func CAdd(a: Complex, b: Complex): Complex
Two complex numbers added.

The example for this item is shown with the ones above: dsp-Complex.salam

CSub

func CSub(a: Complex, b: Complex): Complex
One complex number taken from another.

The example for this item is shown with the ones above: dsp-Complex.salam

CMul

func CMul(a: Complex, b: Complex): Complex
Two complex numbers multiplied.

The example for this item is shown with the ones above: dsp-Complex.salam

CScale

func CScale(a: Complex, k: f64): Complex
A complex number times a plain number.

The example for this item is shown with the ones above: dsp-Complex.salam

CConj

func CConj(a: Complex): Complex
The conjugate, the same number with the imaginary part negated.

The example for this item is shown with the ones above: dsp-Complex.salam

CAbs

func CAbs(a: Complex): f64
How far the number is from zero.

The example for this item is shown with the ones above: dsp-Complex.salam

CArg

func CArg(a: Complex): f64
The angle of the number, in radians.

The example for this item is shown with the ones above: dsp-Complex.salam

CFromPolar

func CFromPolar(mag: f64, angle: f64): Complex
A complex number built from a size and an angle.

The example for this item is shown with the ones above: dsp-Complex.salam

FFT

func FFT(re: f64*, im: f64*, n: int): bool
The Fourier transform in place over two arrays, the real parts and the imaginary parts. The length must be a power of two.

The example for this item is shown with the ones above: dsp-FFT.salam

IFFT

func IFFT(re: f64*, im: f64*, n: int): bool
The inverse transform in place, already divided by the length so that it undoes FFT exactly.

The example for this item is shown with the ones above: dsp-FFT.salam

FFTVec

func FFTVec(re &: Vector<f64>, im &: Vector<f64>): bool
The Fourier transform over two vectors, which saves you reaching for pointers.

The example for this item is shown with the ones above: dsp-FFT.salam

IFFTVec

func IFFTVec(re &: Vector<f64>, im &: Vector<f64>): bool
The inverse transform over two vectors.

The example for this item is shown with the ones above: dsp-FFT.salam

Magnitude

func Magnitude(re: f64*, im: f64*, n: int): Vector<f64>
How strong each bin is, which is what a spectrum usually means.

The example for this item is shown with the ones above: dsp-FFT.salam

Phase

func Phase(re: f64*, im: f64*, n: int): Vector<f64>
The angle of each bin.

The example for this item is shown with the ones above: dsp-FFT.salam

PowerSpectrum

func PowerSpectrum(re: f64*, im: f64*, n: int): Vector<f64>
The square of each bin's strength, which adds up like energy does.

The example for this item is shown with the ones above: dsp-FFT.salam

BinFrequency

func BinFrequency(bin: int, n: int, rate: int): f64
Which frequency a bin stands for, given the length and the sample rate.

The example for this item is shown with the ones above: dsp-FFT.salam

PeakBin

func PeakBin(mags: Vector<f64>): int
Which bin is strongest, or -1 when there are none. Ties go to the lower bin.

The example for this item is shown with the ones above: dsp-FFT.salam

BesselI0

func BesselI0(x: f64): f64
The modified Bessel function of the first kind, order zero, which the Kaiser window is built from.

The example for this item is shown with the ones above: dsp-Window.salam

WindowValue

func WindowValue(kind: int, i: int, n: int, param: f64): f64
One point of a window. The parameter is the Kaiser beta and is ignored by the others.

The example for this item is shown with the ones above: dsp-Window.salam

WindowName

func WindowName(kind: int): str
The short name of a window.

The example for this item is shown with the ones above: dsp-Window.salam

MakeWindow

func MakeWindow(kind: int, n: int, param: f64): Vector<f64>
A whole window as a vector.

The example for this item is shown with the ones above: dsp-Window.salam

ApplyWindow

func ApplyWindow(p: f64*, n: int, kind: int, param: f64)
Multiplies an array by a window in place, which is what you do before a transform.

The example for this item is shown with the ones above: dsp-Window.salam

WindowSum

func WindowSum(kind: int, n: int, param: f64): f64
Every point of a window added up, for undoing the gain a window costs you.

The example for this item is shown with the ones above: dsp-Window.salam

Delay

func Delay(c &: audio.Clip, seconds: f64, mix: f64, feedback: f64)
Mixes a delayed copy back in, in place.

The example for this item is shown with the ones above: dsp-Effect.salam

Echo

func Echo(c: audio.Clip, seconds: f64, repeats: int, decay: f64): audio.Clip
A new clip with repeats trailing off behind it, longer than the one you gave it.

The example for this item is shown with the ones above: dsp-Effect.salam

Reverb

func Reverb(c: audio.Clip, room: f64, mix: f64): audio.Clip
A new clip with a room around it, built from four comb delays.

The example for this item is shown with the ones above: dsp-Effect.salam

Chorus

func Chorus(c: audio.Clip, depth: f64, speed: f64, mix: f64): audio.Clip
A new clip thickened by a copy whose delay wanders, which sounds like more than one player.

The example for this item is shown with the ones above: dsp-Effect.salam

Flanger

func Flanger(c: audio.Clip, depth: f64, speed: f64, mix: f64): audio.Clip
A new clip with a short wandering delay mixed in, the sweeping sound of a jet.

The example for this item is shown with the ones above: dsp-Effect.salam

Tremolo

func Tremolo(c &: audio.Clip, speed: f64, depth: f64)
Moves the level up and down in place, leaving the pitch alone.

The example for this item is shown with the ones above: dsp-Effect.salam

CompressorProcess

func CompressorProcess(k: Compressor, c &: audio.Clip)
Runs a compressor over a clip, in place.

The example for this item is shown with the ones above: dsp-Effect.salam

LimiterProcess

func LimiterProcess(c &: audio.Clip, ceiling_db: f64)
Holds a clip under a ceiling. Unlike a compressor it guarantees the ceiling, by clamping what the fast attack still let past.

The example for this item is shown with the ones above: dsp-Effect.salam

GateProcess

func GateProcess(c &: audio.Clip, threshold_db: f64, release: f64)
Silences a clip wherever it sits below a threshold, which is how a noise gate works.

The example for this item is shown with the ones above: dsp-Effect.salam

TimeStretch

func TimeStretch(c: audio.Clip, factor: f64): audio.Clip
A new clip the same pitch but a different length, by overlapping and adding windows.

The example for this item is shown with the ones above: dsp-Effect.salam

PitchShift

func PitchShift(c: audio.Clip, semitones: f64): audio.Clip
A new clip the same length but a different pitch, by stretching and then resampling.

The example for this item is shown with the ones above: dsp-Effect.salam

LowPass

func LowPass(freq: f64, q: f64, rate: int): Biquad
A filter that passes what is below the frequency. Q of 0.707 is flat with no bump.

The example for this item is shown with the ones above: dsp-Filter.salam

HighPass

func HighPass(freq: f64, q: f64, rate: int): Biquad
A filter that passes what is above the frequency.

The example for this item is shown with the ones above: dsp-Filter.salam

BandPass

func BandPass(freq: f64, q: f64, rate: int): Biquad
A filter that passes a band around the frequency and nothing else.

The example for this item is shown with the ones above: dsp-Filter.salam

Notch

func Notch(freq: f64, q: f64, rate: int): Biquad
A filter that removes a narrow band and passes the rest, for killing a hum.

The example for this item is shown with the ones above: dsp-Filter.salam

AllPass

func AllPass(freq: f64, q: f64, rate: int): Biquad
A filter that passes everything at full strength but shifts the phase.

The example for this item is shown with the ones above: dsp-Filter.salam

Peaking

func Peaking(freq: f64, q: f64, gain_db: f64, rate: int): Biquad
Lifts or cuts a band around the frequency by so many decibels.

The example for this item is shown with the ones above: dsp-Filter.salam

LowShelf

func LowShelf(freq: f64, q: f64, gain_db: f64, rate: int): Biquad
Lifts or cuts everything below the frequency.

The example for this item is shown with the ones above: dsp-Filter.salam

HighShelf

func HighShelf(freq: f64, q: f64, gain_db: f64, rate: int): Biquad
Lifts or cuts everything above the frequency.

The example for this item is shown with the ones above: dsp-Filter.salam

BiquadReset

func BiquadReset(f &: Biquad)
Forgets the two remembered samples. Do this before filtering a different clip.

The example for this item is shown with the ones above: dsp-Filter.salam

BiquadStep

func BiquadStep(f &: Biquad, x: f64): f64
Pushes one sample through the filter and returns the result.

The example for this item is shown with the ones above: dsp-Filter.salam

BiquadResponse

func BiquadResponse(f: Biquad, freq: f64, rate: int): f64
How much the filter passes at a frequency, as a plain multiplier. Useful for checking a filter without any sound.

The example for this item is shown with the ones above: dsp-Filter.salam

BiquadProcess

func BiquadProcess(f &: Biquad, c &: audio.Clip, ch: int)
Runs the filter over one channel of a clip, in place.

The example for this item is shown with the ones above: dsp-Filter.salam

BiquadProcessAll

func BiquadProcessAll(f: Biquad, c &: audio.Clip)
Runs a fresh copy of the filter over every channel, so the channels do not bleed into one another.

The example for this item is shown with the ones above: dsp-Filter.salam

DCRemove

func DCRemove(c &: audio.Clip)
Shifts the clip so its average is zero.

The example for this item is shown with the ones above: dsp-Filter.salam