{
  "access": "public",
  "type": "reference",
  "format": "markdown",
  "title": "Signal Tools Reference",
  "chunked": true,
  "url": "https://library.datagrout.ai/signal-tools",
  "summary": "Digital signal processing for any numeric series — no LLM, no credits, fully deterministic.",
  "content_markdown": "# Signal Tools\n\nDigital signal processing for any numeric series — no LLM, no credits, fully deterministic.\n\nSignal tools operate entirely in-process. There are no external calls, no AI generation, and no credit cost. The transforms (`fft`, `ifft`, `spectral`) run in a native Rust NIF (rustfft, O(n log n)); `filter` and `convolve` are pure Elixir. Every response includes a deterministic receipt under `_meta.datagrout`, and outputs carry both `values` and `records` so results drop straight into `prism.chart` / `prism.refract`. All Signal tools are available at `data-grout@1/signal.*@1`.\n\n### Binary payload input\n\nAlongside JSON arrays, every numeric input `X` also accepts `X_b64` — a base64 string of packed **little-endian** IEEE-754 floats. The `dtype` argument selects `float64` (default, 8 bytes) or `float32` (4 bytes). This is a compact, exact alternative for large signals that avoids a giant JSON array. An explicit JSON array under `X` always wins over `X_b64`.\n\nThe suite covers two areas:\n\n- **Transforms**: `fft`, `ifft`, `spectral` — move between the time and frequency domains and read off the spectrum.\n- **Filtering**: `filter`, `convolve` — isolate or remove frequency bands, smooth, and apply custom kernels.\n\nSignal inputs resolve from explicit arguments (`data`/`real`/`imag`/`a`/`b`) or from `payload`/`cache_ref`, so you can pipe a series from a Data/Frame tool directly in.\n\n### Frequency conventions\n\nCutoff frequencies for `filter` are **normalized** to the sample rate, in the range `(0, 0.5]`, where `0.5` is the Nyquist frequency. If you have a real sample rate `fs`, pass `cutoff = f_hz / fs`. `spectral` and `fft` accept a `sample_rate` (Hz) and report each bin's `freq` in Hz; with the default `sample_rate` of 1.0, frequencies are in cycles/sample.\n\n---\n\n## `signal.fft@1`\n\nForward Fast Fourier Transform of a real or complex signal (native, rustfft, O(n log n)). Returns the complex spectrum (`real`, `imag`), `magnitude`, `phase`, and — with `sample_rate` — per-bin `freq`, plus a `records` list. Set `one_sided: true` to return only bins `0..n/2` (the non-redundant half for real input). Max 1,048,576 samples.\n\n### Parameters\n\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| `data` | number[] | one of | Real signal samples |\n| `real` / `imag` | number[] | one of | Explicit real/imaginary parts (`imag` defaults to zeros) |\n| `sample_rate` | number | no | Sampling rate (Hz) for per-bin frequency |\n| `one_sided` | boolean | no | Return only bins 0..n/2 (default `false`) |\n\n### Example\n\n```json\n{\n  \"name\": \"data-grout@1/signal.fft@1\",\n  \"arguments\": { \"data\": \"$sensor.readings\", \"sample_rate\": 1000, \"one_sided\": true }\n}\n```\n\nResponse: `{ \"n\": 1024, \"real\": [...], \"imag\": [...], \"magnitude\": [...], \"phase\": [...], \"records\": [{ \"index\": 4, \"freq\": 3.9, \"magnitude\": 512.0, ... }] }`.\n\n---\n\n## `signal.ifft@1`\n\nInverse FFT (native, rustfft). Reconstructs a time/space-domain signal from a complex spectrum, normalized by 1/n so `ifft(fft(x)) == x`. Returns the complex output (`real`, `imag`) and, as `values`, the real part — the recovered signal for spectra that came from real input. Max 1,048,576 bins.\n\n### Parameters\n\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| `real` | number[] | yes | Real part of the spectrum |\n| `imag` | number[] | no | Imaginary part (defaults to zeros) |\n\n### Example\n\n```json\n{\n  \"name\": \"data-grout@1/signal.ifft@1\",\n  \"arguments\": { \"real\": \"$fft.real\", \"imag\": \"$fft.imag\" }\n}\n```\n\nResponse: `{ \"n\": 8, \"values\": [3, 1, 4, 1, 5, 9, 2, 6], \"real\": [...], \"imag\": [...] }`.\n\n---\n\n## `signal.spectral@1`\n\nOne-sided power spectrum of a real signal, computed via the native FFT (O(n log n)). Returns, per frequency bin, the `freq`, `magnitude`, `power`, and `phase`, plus the top `peaks` (excluding DC) and the `dominant_frequency`. An optional `window` (`hann` / `hamming`) reduces spectral leakage. Max 1,048,576 samples.\n\n### Parameters\n\n| Parameter | Type | Required | Default | Description |\n|-----------|------|----------|---------|-------------|\n| `data` | number[] | yes | -- | Real signal samples (or via payload/cache_ref) |\n| `sample_rate` | number | no | `1.0` | Sampling rate (Hz); default reports cycles/sample |\n| `window` | string | no | `none` | `none` \\| `hann` \\| `hamming` |\n| `top_peaks` | integer | no | `5` | Number of spectral peaks to return |\n\n### Example\n\n```json\n{\n  \"name\": \"data-grout@1/signal.spectral@1\",\n  \"arguments\": { \"data\": \"$sensor.readings\", \"sample_rate\": 1000, \"window\": \"hann\" }\n}\n```\n\nResponse: `{ \"n\": 512, \"dominant_frequency\": 60.0, \"peaks\": [{ \"freq\": 60.0, \"power\": 812.4, ... }], \"records\": [...] }`.\n\n---\n\n## `signal.filter@1`\n\nFIR frequency filter built from a windowed-sinc kernel (Hamming window), convolved with the signal to give a same-length, phase-delay-corrected output. Types: `lowpass`, `highpass`, `bandpass`, `bandstop`. `lowpass`/`highpass` take a single `cutoff`; `bandpass`/`bandstop` take `low` and `high`. All cutoffs are normalized to `(0, 0.5]`. Max 100,000 samples, 2,001 taps.\n\n### Parameters\n\n| Parameter | Type | Required | Default | Description |\n|-----------|------|----------|---------|-------------|\n| `data` | number[] | yes | -- | Input signal samples |\n| `type` | string | yes | -- | `lowpass` \\| `highpass` \\| `bandpass` \\| `bandstop` |\n| `cutoff` | number | low/high | -- | Normalized cutoff for lowpass/highpass |\n| `low`, `high` | number | band | -- | Normalized band edges (`low` < `high`) |\n| `num_taps` | integer | no | `51` | FIR kernel length (forced odd); higher = sharper transition |\n\n### Example\n\n```json\n{\n  \"name\": \"data-grout@1/signal.filter@1\",\n  \"arguments\": { \"data\": \"$sensor.readings\", \"type\": \"lowpass\", \"cutoff\": 0.05 }\n}\n```\n\nResponse: `{ \"values\": [...], \"records\": [...], \"count\": 1000, \"type\": \"lowpass\", \"num_taps\": 51 }`.\n\n---\n\n## `signal.convolve@1`\n\nDiscrete convolution of two numeric sequences (a * b). Modes: `full` (length `len(a)+len(b)-1`, default), `same` (length `len(a)`, centered), `valid` (overlap only). Useful for smoothing, feature detection, and applying custom kernels. Max 50,000 per input (and ≤ 5,000,000 total operations).\n\n### Parameters\n\n| Parameter | Type | Required | Default | Description |\n|-----------|------|----------|---------|-------------|\n| `a` | number[] | yes | -- | First sequence (the signal) |\n| `b` | number[] | yes | -- | Second sequence (the kernel) |\n| `mode` | string | no | `full` | `full` \\| `same` \\| `valid` |\n\n### Example\n\n```json\n{\n  \"name\": \"data-grout@1/signal.convolve@1\",\n  \"arguments\": { \"a\": [1, 2, 3], \"b\": [0, 1, 0.5], \"mode\": \"full\" }\n}\n```\n\nResponse: `{ \"values\": [0, 1, 2.5, 4, 1.5], \"records\": [...], \"count\": 5, \"mode\": \"full\" }`.\n\n---\n\n---\n\n## `signal.correlate@1`\n\nCross-correlation of two sequences by lag — the DSP sliding dot-product, **not** the Pearson coefficient (that's `math.correlate`). Omit `b` and it defaults to `a`, giving the **auto-correlation** (peaks away from lag 0 mark repeat intervals — periodicity detection). Set `normalize: true` to scale by the signal energies so values fall in [-1, 1] (auto-correlation is then 1.0 at lag 0). Modes: `full` (default), `same`, `valid`. Returns `values`, `{lag, value}` records, and the `peak_lag`. Max 50,000 per input.\n\n### Parameters\n\n| Parameter | Type | Required | Default | Description |\n|-----------|------|----------|---------|-------------|\n| `a` | number[] | yes | -- | First sequence |\n| `b` | number[] | no | `a` | Second sequence; omit for auto-correlation |\n| `mode` | string | no | `full` | `full` \\| `same` \\| `valid` |\n| `normalize` | boolean | no | `false` | Scale to [-1, 1] by signal energies |\n\n### Example\n\n```json\n{\n  \"name\": \"data-grout@1/signal.correlate@1\",\n  \"arguments\": { \"a\": \"$sensor.readings\", \"normalize\": true }\n}\n```\n\nResponse: `{ \"values\": [...], \"records\": [{ \"lag\": -6, \"value\": 0.12 }, ...], \"peak_lag\": 0, \"count\": 13 }`.\n\n---\n\n## Composition\n\nPull a series through [Data](data-tools) / [Frame](frame-tools) tools, transform it here, then chart the spectrum or waveform with [Prism Chart](prism-tools) — the `records` (frequency, magnitude, phase) and filtered `values` are chart-ready. Pair with [Math](math-tools) for descriptive statistics on the magnitudes, or with [Linalg](linalg-tools) when spectral features feed clustering or similarity. A common pipeline is `fft` → edit the spectrum → `ifft` for frequency-domain processing.\n"
}