cut
Builtin index · Signal processing
result = cut(sig, t_start, t_end)
Input: sig, t_start, t_end · Output: result
What it is
sig: a length-N Signal; t_start, t_end: numbers (seconds, t_start >= 0 and t_end >= t_start).
Overloads
One call shape — this function is not overloaded.
| Code | Input | Output | Description |
result = cut(sig, t_start, t_end) | sig Signalsig: a length-N Signal.t_start numbert_start, t_end: numbers (seconds, t_start >= 0 and t_end >= t_start).t_end numbert_start, t_end: numbers (seconds, t_start >= 0 and t_end >= t_start). | Fs —i = round(t*Fs), the exact inverse of sig.t's i/Fs | Returns a new Signal at the SAME Fs, shorter than sig. Trims sig to the time range [t_start, t_end] — INCLUSIVE of both ends, like every other range in the language, so cut(s, 0.2, 0.7) contains the sample AT 0.7. (The documented cost: abutting cuts no longer tile — cut(s, 0, 1) and cut(s, 1, 2) share the sample at t = 1. Step the second window's start on by one sample to partition.) t_start/t_end round to the nearest sample via sig's own Fs (i = round(t*Fs), the exact inverse of sig.t's i/Fs). A negative t_start or t_end < t_start is a clear error; t_end past sig's actual duration clamps to the last sample rather than erroring (matching ordinary slice hi-clamping, e.g. x[0:1000] on a 10-element vector). Composes with the @ self-mutation prefix like any other method chain: @s.cut(1.0, 2.0) needs no special-casing — it's an ordinary function under the hood. cut, signal_slice_time and the bracket form sig[t_start s : t_end s] are three spellings of ONE implementation (they share signal_time_span, so they can never silently diverge). |
Examples
From the documentation, and run by the build -- the output and the figure below are what the engine actually produced. Simplest first.
s = signal(0 to 9, 10) # 10 samples at 10 Hz, t = 0.0, 0.1, ..., 0.9s
trimmed = cut(s, 0.5, 100.0) # end clamps past the signal's duration
print("trimmed: {trimmed}")
@s.cut(0.2, 0.7) # self-mutating form: s = cut(s, 0.2, 0.7)
print("s after @cut: {s}")
trimmed: signal(Fs=10) [5, 6, 7, 8, 9] s after @cut: signal(Fs=10) [2, 3, 4, 5, 6, 7]
from signal-processing
fs = 10
s = signal(0 to 9, fs)
trimmed = cut(s, 0.5, 100.0)
print("trimmed = {trimmed}")
print("trimmed.Fs = {trimmed.Fs}")
t2 = signal(0 to 9, fs)
@t2.cut(0.2, 0.7)
print("t2 after @cut = {t2}")
from tour
How it works
signal_slice_time(sig, t0, t1) — slices a Signal by a TIME range in seconds (half-open [t0, t1), matching the ordinary x[a:b] index grammar's own half-open/0-based convention — see resolve_sel/exec_index_assign above) instead of raw sample positions, converting via the signal's own Fs (i = round(t * Fs), the exact inverse of the already-shipped sig.t accessor's i / Fs). Built as a builtin rather than new x[time: t0 .. t1] index syntax after reading qu-syntax's Idx grammar: index_one parses a:b by first parsing a as a plain expression, then checking for a following : — so time before a : is already meaningful today (it's an ordinary slice lower bound, e.g. x[time:end] where time is a variable holding an index). Special-casing a literal time/freq identifier immediately before : to mean something else entirely would silently change what any existing script using a variable named time or freq as a slice bound does — real risk to established indexing behavior for a feature explicitly scoped (BACKLOG.md) to stay small and self-contained, separate from the bigger named/labeled-axis-indexing design (already flagged there as needing its own design pass). A plain builtin needs no grammar change at all and is exactly as capable for this scope. cut(sig, t_start, t_end) (added alongside the 3 new § signal builtins, 2026-09) is the same operation under a friendlier public name — deliberately NOT a second, separately maintained implementation: a time-based trim only has one sane boundary convention already established in this codebase (INCLUSIVE [t_start, t_end], matching x[a:b]'s own inclusive slice convention and to's), so both names share this one match arm and its error messages via f (the matched name) rather than inventing a third convention or forking the logic. That parenthesis used to say HALF-OPEN, and said so for long enough that book/src/stdlib/signal-processing.md copied it ("the one range in the language that excludes its upper end") and help("cut") inherited it from the book. The arm's own inner comment below had already recorded the change to inclusive; this outer one was never updated to match, so the file contradicted itself and the docs followed the wrong half. Measured on the built binary before correcting it: cut(signal(0 to 9, 10), 0.2, 0.7) is six samples, 2..=7 -- inclusive. Both spellings corrected 2026-09-18, alongside the extraction of signal_time_span, which now makes the bracket form s[t0 s : t1 s] a THIRD spelling sharing this body. Boundary handling: t_start/t_end round to the nearest sample via the signal's own Fs (i = round(t*Fs), the exact inverse of sig.t's i/Fs); t_end past the signal's actual duration clamps to the last sample (matching ordinary slice hi clamping, e.g. x[0:1000] on a 10-element vector), while a negative t_start or t_end < t_start is a clear error rather than a silent empty result — a typo'd negative time is far more likely a mistake than an intentional empty cut.
From the note above this builtin in the interpreter, where the reasoning was written down as it was built.