interpolate_at
Builtin index · Signal processing
result = interpolate_at(sig, t_query, [method="linear"|"spline"|"nearest"])
Input: sig, t_query, method · Output: result
What it is
sig: a length-N Signal; t_query: a number or length-M vector, query time(s) in seconds; method (optional string, interp1's own three). A Signal-aware interp1: instead of an explicit x, the signal's OWN implicit time axis (sig.t, i.e. i/Fs) is used as x and sig's samples as y. Shares interp1's exact interpolation/extrapolation kernel, so the two never silently diverge in behavior. sig.interpolate_at(t_query, [method]) also works via the generic method-chain sugar.
Overloads
One call shape — this function is not overloaded.
| Code | Input | Output | Description |
result = interpolate_at(sig, t_query, [method="linear"|"spline"|"nearest"]) | sig Signalsig: a length-N Signal.t_query number or vectort_query: a number or length-M vector, query time(s) in seconds.method string, optional (default "linear"|"spline"|"nearest")method (optional string, interp1's own three). | result number, vector, or Signal | Returns a number (if t_query is a number) or a length-M vector (if t_query is a vector) — never re-wrapped as a Signal: a Signal is a promise of uniform sampling at a known Fs, but the query times here are arbitrary caller-chosen instants with no guaranteed spacing, so there is no honest Fs to attach. |
Examples
From the documentation, and run by the build -- the output and the figure below are what the engine actually produced. Simplest first.
sig = signal([0, 10, 20, 30], 2) # samples at t = 0, 0.5, 1.0, 1.5s
mid = interpolate_at(sig, 0.75) # interior, halfway between samples 1 and 2
lo = interpolate_at(sig, -0.5) # extrapolated below t=0 via the boundary slope
hi = sig.interpolate_at(2.0) # extrapolated past the last sample
qs = sig.interpolate_at([0.0, 0.5, 1.0], "nearest")
print("mid={mid}, lo={lo}, hi={hi}, qs={qs}")
mid=15, lo=-10, hi=40, qs=[0, 10, 20]
from signal-processing
sig = signal([0, 10, 20, 30], 2) # samples at t = 0, 0.5, 1.0, 1.5s
mid = interpolate_at(sig, 0.75)
lo = interpolate_at(sig, -0.5)
hi = interpolate_at(sig, 2.0)
qs = sig.interpolate_at([0.0, 0.5, 1.0], "nearest")
print("mid = {mid}")
print("lo = {lo}")
print("hi = {hi}")
print("qs = {qs}")
from tour
How it works
interpolate_at(sig, t_query, [method]) (§ signal builtins, 2026-09) — a signal-aware interp1: instead of the caller supplying an explicit x vector, x is the signal's OWN implicit time axis (sig.t, i.e. [0, 1/Fs, 2/Fs, ...] — reused directly via eval_field's existing (Signal, "t") accessor rather than recomputed by hand here, so this can never drift from what sig.t itself reports), so the caller only supplies the query time(s) (a scalar or a vector — same shape rule as interp1's own xq) and, optionally, a method ("linear"/"spline"/"nearest", interp1's exact set, sharing the same interp1_eval dispatch so the two never silently diverge in behavior). Returns a plain Value::Num/Value::Vec — deliberately NEVER re-wrapped as a Value::Signal. A Signal is a promise of uniform sampling at a known Fs (that's the entire reason sig.t/resample_to/cut can compute a time axis from just Fs and a length); the query times here are arbitrary, caller-chosen instants with no guaranteed spacing at all, so there is no honest Fs to attach — wrapping the result in a Signal anyway would silently claim a sample rate that isn't real. interp1(x, y, xq)'s own return type has the identical reasoning already (it returns Num/Vec, never a Signal, even when y came from one) — this just makes explicit for a Signal-rooted caller what was already true.
From the note above this builtin in the interpreter, where the reasoning was written down as it was built.