Files
tensor/signal/chirp.go
T
petrbalvin af4ee19703
Release / gates (push) Successful in 4m38s
Test / test (push) Successful in 5m16s
Release / release (push) Successful in 35s
feat: initial release
Assisted-by: GLM 5.3 Flash
2026-09-03 10:00:00 +02:00

61 lines
2.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: MIT
package signal
import (
"math"
"sourcedock.dev/petrbalvin/tensor/internal/base"
)
// Chirp returns n samples of a linear frequency sweep from f0 through
// f1, sampled at rate samples per unit of time. The sweep runs over
// the whole sample span and reaches f1 exactly at the last sample; the
// first sample sits at phase zero. Every phase comes from the closed
// form 2π·(f0·t + (f1−f0)·t²/(2T)) at that sample's own time, never
// from a recursive oscillator, so the samples carry no accumulated
// drift: sample k is as accurate as the formula at t_k and nothing
// that happened at earlier samples can bend it. That property is what
// aliasing studies of a swept tone need, which is where the transform
// side of this package keeps meeting chirps in the wild.
//
// The sweep edges live strictly below the Nyquist frequency rate/2: an
// edge on or beyond it folds onto lower frequencies, and the error
// names both. The edges may be negative, which reverses the direction
// of rotation without weakening the guard, which watches their
// magnitudes.
//
// Errors: n below 1, a non-positive or non-finite rate, a non-finite
// sweep edge, an edge at or beyond Nyquist.
func Chirp(n int, f0, f1, rate float64) ([]float64, error) {
if n < 1 {
return nil, base.Errf("Chirp: n must be at least 1, got %d", n)
}
if math.IsNaN(rate) || math.IsInf(rate, 0) || rate <= 0 {
return nil, base.Errf("Chirp: the sample rate must be finite and positive, got %g", rate)
}
if math.IsNaN(f0) || math.IsInf(f0, 0) || math.IsNaN(f1) || math.IsInf(f1, 0) {
return nil, base.Errf("Chirp: the sweep edges must be finite, got %g and %g", f0, f1)
}
nyquist := rate / 2
edge := math.Max(math.Abs(f0), math.Abs(f1))
if edge >= nyquist {
return nil, base.Errf("Chirp: the sweep edge %g reaches the Nyquist frequency %g at rate %g", edge, nyquist, rate)
}
out := make([]float64, n)
// The span up to the last sample; a one-sample chirp has no span
// and no slope, and its single sample is the phase-zero zero.
span := float64(n-1) / rate
slope := 0.0
if n > 1 {
slope = (f1 - f0) / span
}
const twoPi = 2 * math.Pi
for i := range out {
t := float64(i) / rate
out[i] = math.Sin(twoPi * (f0*t + 0.5*slope*t*t))
}
return out, nil
}