feat: initial release
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user