spectrum
Builtin index · Signal processing
result = spectrum(x, [fs], [window="hann"], [scaling="amplitude"])
Input: x, fs, window, scaling · Output: result
What it is
x: a length-N real vector or Signal; fs (number, Hz) — required, either passed or carried by a Signal (the result is a spectrum, whose point is its frequency axis; there is no default rate to mislabel it with). window (optional string: "hann" (default), "hamming", "blackman", "kaiser" (needs beta=), or "rectangular"/"boxcar"/"none" for no taper): the analysis window applied to the record before transforming. scaling (optional string: "amplitude" (default) or "rms"): the convention stamped on X.norm.
Overloads
One call shape — this function is not overloaded.
| Code | Input | Output | Description |
result = spectrum(x, [fs], [window="hann"], [scaling="amplitude"]) | x vector or Signalx: a length-N real vector or Signal.fs number or Signal, optionalfs (number, Hz) — required, either passed or carried by a Signal (the result is a spectrum, whose point is its frequency axis.window string, optional (default "hann")window (optional string: "hann" (default), "hamming", "blackman", "kaiser" (needs beta=), or "rectangular"/"boxcar"/"none" for no taper): the analysis window applied to the record before transforming.scaling string, optional (default "amplitude")scaling (optional string: "amplitude" (default) or "rms"): the convention stamped on X.norm. | A —A/sqrt(2) under "rms" | Returns a spectrum of floor(N/2)+1 one-sided bins (DC to Nyquist). The window is normalised to unit coherent gain (mean(w) == 1) before it is applied, so a bin-aligned tone of amplitude A reads exactly A (A/sqrt(2) under "rms") — an uncompensated Hann taper would read A/2. This is what spectrum adds over rfft(sig, scaling=), which applies no window at all; window="rectangular" makes the two agree bin for bin. Compensation is applied uniformly, not only when scaling="amplitude", so two calls differing only in scaling= differ only by the documented 1/sqrt(2). It corrects a discrete tone's peak, not a broadband level — noise spread across bins is governed by the window's noise-equivalent bandwidth (sum(w^2), 1.5 for Hann), which is what psd/welch are scaled for; integrate power with those, not this. scaling="raw" is deliberately refused: a spectrum carries no window field, so raw bins would be exactly what band_power integrates as if the record had not been tapered — use rfft(sig) for the unwindowed raw transform. |
Examples
No documentation example calls this form yet. The Signal processing chapter is where one belongs.
How it works
spectrum(x, [fs], [window="hann"], [scaling="amplitude"]) — toolkit-signal.md §2's windowed amplitude/RMS spectrum: taper the record, transform it, and return a Spectrum whose bins read a physical amplitude. WHAT THIS ADDS OVER rfft(sig, scaling=), which already exists and already stamps the same three conventions: a window. rfft transforms exactly the samples it is handed — it takes no window= argument — so today the taper is the caller's own x .* hann(N) by hand, and that hand-written form is where the bug below gets introduced. THE WINDOW GAIN DECISION (the reason this is not three lines). Tapering multiplies the record by w, and a bin-aligned tone of amplitude A then transforms to (A/2)*e^(j*phi)*sum(w). apply_spectrum_norm's Amplitude factor of 2/n turns that into A * (sum(w)/n) — A times the window's COHERENT GAIN, which for a Hann window is 0.5. A user who windowed by hand and read the peak would see half the amplitude they put in, with a plausible-looking number and nothing announcing the factor. toolkit-signal.md §2 states the contract this has to meet in one line: "Window gain ... is applied by the chosen scaling, not left as an exercise." So the analysis window is normalised to UNIT COHERENT GAIN (mean(w) == 1) before it is applied, unconditionally — not only when scaling= asks for amplitude. One rule that always holds beats a rule whose meaning depends on another keyword: a caller comparing two spectrum() calls that differ only in scaling= gets results that differ only by the documented 1/sqrt(2), not by a window factor as well. Verified numerically, not by argument — see windowed_spectrum_reads_true_amplitude below. WHAT COHERENT-GAIN COMPENSATION DOES *NOT* FIX, stated here because a half-true correction is the more dangerous kind: it makes a discrete TONE's peak bin read true. It does not make a BROADBAND level read true — noise spread across bins is governed by the window's noise-equivalent bandwidth (sum(w^2), not sum(w)), which is a different factor (1.5 for Hann). psd below is the estimator scaled for that, and it is what a caller integrating power over a band should be using. WHY scaling="raw" IS REFUSED HERE rather than passed through. Value::Spectrum carries no window field — SpectrumNorm's own doc comment explains why it deliberately does not, and adding one is a real change across every producer, not something to smuggle in here. A raw-scaled windowed spectrum is exactly the value that loss hurts: band_power accepts raw_transform bins and applies Parseval to them, so it would return a number biased by the window's noise bandwidth, silently, from a taper it cannot see. Amplitude/Rms are already refused by band_power's own exhaustive match, so restricting this producer to those two closes the hole with the mechanism that is already there. A caller who genuinely wants the unnormalised DFT of a tapered record still has rfft(signal(x .* hann(n), fs)), which says what it did.
From the note above this builtin in the interpreter, where the reasoning was written down as it was built.