first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
# @suaveplan/audio-analysis-spectrum - Universal spectral analysis: real FFT, Hann/Hamming/Blackman windows, mel filterbank, DCT-II, SpectrumAnalyzer/Spectrogram/MFCC analysis ops with OTel instrumentation. Zero browser or Node dependencies.
Universal spectral analysis for the Suaveplan audio stack: real FFT, Hann/Hamming/Blackman windows, mel filterbank, DCT-II, and three high-level analyzers — `SpectrumAnalyzer`, `Spectrogram`, `MFCC`. Zero browser or Node-only dependencies.
<!-- begin:auto-docs -->
## Documentation tree
- [dct](./src/algorithms/dct.md) — The DCT-II module is the cepstral projection step of the MFCC pipeline: it takes the log mel-spectrum of one frame and…
- [fft](./src/algorithms/fft.md) — The FFT module is the numerical core every analyzer in this package depends on. It runs an in-place Cooley–Tukey radix-…
- [mel-scale](./src/algorithms/mel-scale.md) — The mel-scale module provides the perceptual frequency warp used by MFCC and any caller that wants a psychoacoustically…
- [window](./src/algorithms/window.md) — Window functions taper the endpoints of each analysis frame so the FFT sees a periodic-looking signal rather than the a…
- [dsp](./src/analyzer/dsp.md) — The dsp module is the per-frame DSP toolbox every analyzer in this package leans on. It owns the document→mono read pat…
- [mfcc](./src/analyzer/mfcc.md) — `MFCC` is the speech/music feature extractor of this package: per frame it builds the magnitude spectrum, applies a tri…
- [runtime](./src/analyzer/runtime.md) — The runtime module is the shared spine for `SpectrumAnalyzer`, `Spectrogram`, and `MFCC`. It owns option parsing throug…
- [spectrogram](./src/analyzer/spectrogram.md) — `Spectrogram` produces the ordered list of per-frame magnitude spectra for a document, slicing by the same `frameSize`/…
- [spectrum-analyzer](./src/analyzer/spectrum-analyzer.md) — `SpectrumAnalyzer` is the workhorse analysis op of this package: it slices an `AudioDocument` into overlapping frames,…
- [error-codes](./src/error-codes/error-codes.md) — The error-codes module owns the stable `AUDIO_ANALYSIS_SPECTRUM.*` code registry used by every analyzer in this package…
- [error-codes](./src/error-codes/error-codes.md) — The error-codes module owns the stable `AUDIO_ANALYSIS_SPECTRUM.*` code registry used by every analyzer in this package…
<!-- end:auto-docs -->
## What's in the box
- **Algorithms** (`./algorithms/*`)
- `fft` — Cooley–Tukey radix-2 real-input FFT for every power of two in `[256, 32768]`.
- `window` — Hann, Hamming, Blackman windows with per-`(name, size)` caching.
- `mel-scale` — Slaney/HTK hz↔mel mapping plus a cached triangular filterbank.
- `dct` — type-II DCT with cached kernel matrix.
- **Analyzers** (`./analyzer/*`)
- `SpectrumAnalyzer` — magnitude spectrum + centroid/rolloff/flux, `analyze` / `analyzeStream` / `analyzeBlock`.
- `Spectrogram` — time-frequency magnitude matrix, `analyze` / `analyzeStream`.
- `MFCC` — 13-coefficient mel-frequency cepstral coefficients per frame.
- **Error codes** (`./error-codes`) — `AUDIO_ANALYSIS_SPECTRUM.{INVALID_OPTIONS, INVALID_FRAME, ABORTED}` via `defineCodes`.
- **OTel instrumentation** — `audio.analysis.runs_total`, `audio.analysis.duration_ms`, `audio.analysis.block_duration_ms` counters/histograms; a span per `analyze`/`analyzeStream` invocation. Inert without an exporter.
## Quick start
```ts
import { SpectrumAnalyzer } from "@suaveplan/audio-analysis-spectrum";
import { AudioDocument } from "@suaveplan/audio-core/document/document";
const analyzer = new SpectrumAnalyzer();
const result = await analyzer.analyze(doc, {
bins: 128,
frameSize: 2048,
hopSize: 512,
window: "hann",
});
console.log(result.centroid, result.rolloff, result.flux);
```
## Universal guarantees
- Imports zero browser globals (`window`, `document`, `AudioContext`, `AnalyserNode`).
- Imports zero Node-only modules (`node:fs`, `node:path`, `process`, `Buffer`).
- Runtime deps: `@suaveplan/audio-core`, `@suaveplan/error`, `@suaveplan/types`, `@opentelemetry/api`, `tslib`, `zod`.
- Each analyzer is sidecar — analysis never mutates the source `AudioDocument`.
## Status
`0.1.0`. Pre-publish gate (lint + typecheck + test) and 100% coverage are required on every change.

View File

@@ -0,0 +1,44 @@
# `dct` — type-II discrete cosine transform
## Purpose
The DCT-II module is the cepstral projection step of the MFCC pipeline: it takes the log mel-spectrum of one frame and produces a compact vector of cepstral coefficients. The same routine is reusable anywhere a closed-form orthonormal-with-scale DCT is needed.
This implementation is unscaled (no `sqrt(2/N)` orthonormality factor) because downstream MFCC analysis is unit-agnostic: every coefficient is later compared to peers, not to an external reference. Callers that need an orthonormal form can post-multiply by the standard scaling factor.
## Features
- Closed-form `X_k = Σ x_n · cos((π/N)(n + ½)k)` for `0 ≤ k < coefficients`.
- Cached per `(inputSize, coefficients)` so repeated frames reuse the kernel matrix.
- Pure portable TypeScript over `Float32Array` inputs and outputs.
## Usage: Basic
```ts
import { dctII } from "@suaveplan/audio-analysis-spectrum/algorithms/dct";
const logMel = new Float32Array(40); // log mel-spectrum
const mfcc = dctII(logMel, 13);
```
## Usage: Advanced
```ts
import {
clearDctCache,
dctII,
} from "@suaveplan/audio-analysis-spectrum/algorithms/dct";
const coeffs = dctII(logMel, 20);
// later, before a benchmark sweep
clearDctCache();
```
## API Reference
- `dctII(input, coefficients): Float32Array` — first `coefficients` cepstral values.
- `clearDctCache(): void` — evict every cached kernel.
## Implementation Notes
The kernel is stored as a flat `Float64Array` of `coefficients * inputSize` doubles so the per-frame inner loop is a single linear pass with no nested allocation. Kernel construction is `O(coefficients · inputSize)` and is cached on the `(inputSize, coefficients)` pair; runtime cost is then a single matrix-vector multiply per frame. Boundary checks on `coefficients` (positive, `≤ melBands`) are enforced at the analyzer layer before this routine is called, so this module assumes a validated input.

View File

@@ -0,0 +1,50 @@
# `fft` — real-input FFT over power-of-two frames
## Purpose
The FFT module is the numerical core every analyzer in this package depends on. It runs an in-place Cooley–Tukey radix-2 decimation-in-time transform over a windowed real-valued frame and returns the non-redundant `frameSize / 2 + 1` magnitude bins (and optionally the matching phase array). Twiddle-factor tables and the bit-reverse index are built once per frame size and cached, so repeated frames pay only the per-call working-buffer allocation.
The transform is deterministic — the same input bytes always yield byte-identical magnitudes — and is portable TypeScript with no platform globals, satisfying the universal purity requirement.
## Features
- Radix-2 in-place Cooley–Tukey for every power of two in `[256, 32768]`.
- Cached twiddle (`cos`/`sin` over the unit circle) and bit-reverse tables.
- Optional phase output via `{ withPhase: true }`.
- Boundary `assertValidFrameSize` for callers that own their own input shape.
- `clearFFTCache` for cold-start benchmark measurement.
## Usage: Basic
```ts
import { transform } from "@suaveplan/audio-analysis-spectrum/algorithms/fft";
const frame = new Float32Array(2048);
// fill `frame` with a windowed signal...
const { magnitude } = transform(frame);
```
## Usage: Advanced
```ts
import {
assertValidFrameSize,
clearFFTCache,
transform,
} from "@suaveplan/audio-analysis-spectrum/algorithms/fft";
assertValidFrameSize(4096); // throws AUDIO_ANALYSIS_SPECTRUM.INVALID_FRAME if wrong
const { magnitude, phase } = transform(frame, { withPhase: true });
clearFFTCache(); // evict cached twiddle tables before a benchmark sweep
```
## API Reference
- `transform(frame, opts?): FFTResult` — per-bin `Float32Array` magnitude (length `frameSize/2+1`) plus optional phase.
- `assertValidFrameSize(frameSize): number` — throws `AUDIO_ANALYSIS_SPECTRUM.INVALID_FRAME` if not a power of two in `[256, 32768]`.
- `clearFFTCache(): void` — drop every cached twiddle/bit-reverse table.
- `FFT_FRAME_MIN`, `FFT_FRAME_MAX` — the inclusive bounds enforced above.
## Implementation Notes
Twiddle storage is `Float64Array` to keep the rotation pole-bound errors in float64 even though the output is downcast to `Float32Array`. The bit-reverse table is `Uint32Array` rather than computed per call. Each frame pays the cost of two length-`size` `Float64Array` working buffers, which dominates the per-call allocation budget but avoids leaking mutable state between calls. A pure sinusoid at a bin-centre frequency produces one dominant magnitude bin; an impulse produces a flat magnitude spectrum; both are tested as the floor of correctness.

View File

@@ -0,0 +1,58 @@
# `mel-scale` — hertz↔mel mapping and triangular filterbank
## Purpose
The mel-scale module provides the perceptual frequency warp used by MFCC and any caller that wants a psychoacoustically motivated reduction of an FFT magnitude spectrum. It exposes the Slaney/HTK closed-form `hzToMel` and its inverse `melToHz`, plus a builder that produces a triangular filterbank for a given `(frameSize, sampleRate, melBands, minHz, maxHz)` tuple.
Each filterbank is cached so repeated frames pay zero filter-construction cost; applying a filterbank to a magnitude spectrum is then a single dense matrix-vector multiply.
## Features
- Closed-form hertz↔mel mapping that round-trips to machine precision.
- Triangular filterbank with `melBands` bands evenly spaced on the mel scale.
- Per-spec caching keyed by `(frameSize, sampleRate, melBands, minHz, maxHz)`.
- Boundary validation that `melBands` is a positive integer.
## Usage: Basic
```ts
import {
applyMelFilterbank,
buildMelFilterbank,
} from "@suaveplan/audio-analysis-spectrum/algorithms/mel-scale";
const fb = buildMelFilterbank({
frameSize: 2048,
sampleRate: 48_000,
melBands: 40,
minHz: 0,
maxHz: 24_000,
});
const mel = applyMelFilterbank(spectrum, fb);
```
## Usage: Advanced
```ts
import {
clearMelFilterbankCache,
hzToMel,
melToHz,
} from "@suaveplan/audio-analysis-spectrum/algorithms/mel-scale";
const m = hzToMel(1000);
const back = melToHz(m); // ≈ 1000 within 1e-3
clearMelFilterbankCache(); // before a benchmark sweep
```
## API Reference
- `hzToMel(hz): number` / `melToHz(mel): number` — Slaney/HTK closed forms.
- `buildMelFilterbank(spec): MelFilterbank` — cached triangular filterbank.
- `applyMelFilterbank(spectrum, bank): Float32Array` — dense mel-band vector.
- `clearMelFilterbankCache(): void` — drop every cached filterbank.
## Implementation Notes
The builder allocates `melBands + 2` mel-spaced points so each band has both a lower and an upper neighbour centre. Bin frequencies are computed once as `(i * sampleRate) / frameSize` to avoid a per-band recomputation. Each band's weights peak at `1` and taper linearly to `0` at the neighbour centres; no per-band energy normalisation is applied because the downstream MFCC log+DCT step is insensitive to global scaling.

View File

@@ -0,0 +1,56 @@
# `window` — discrete window functions
## Purpose
Window functions taper the endpoints of each analysis frame so the FFT sees a periodic-looking signal rather than the abrupt discontinuities created by a rectangular slice. Tapering reduces spectral leakage at the cost of a small main-lobe widening, which is the standard trade-off used by every spectral analyzer in this package.
The module exposes three closed-form windows — Hann, Hamming, and Blackman — selectable by the `window` option of `SpectrumAnalyzerOptions`. Vectors are cached per `(name, size)` so the spectrum, spectrogram, and MFCC analyzers share a single allocation across every frame of the same shape.
## Features
- Hann, Hamming, Blackman generators producing `Float32Array` vectors.
- Cached per `(name, size)` to avoid re-allocation on every frame.
- `applyWindow` for in-place multiplication into a caller-provided buffer.
- `clearWindowCache` for benchmarks measuring cold-start cost.
## Usage: Basic
```ts
import {
applyWindow,
getWindow,
} from "@suaveplan/audio-analysis-spectrum/algorithms/window";
const w = getWindow("hann", 2048);
const frame = new Float32Array(2048);
const out = new Float32Array(2048);
applyWindow(frame, w, out);
```
## Usage: Advanced
```ts
import {
clearWindowCache,
getWindow,
} from "@suaveplan/audio-analysis-spectrum/algorithms/window";
// Pre-warm the cache for the working set.
for (const name of ["hann", "hamming", "blackman"] as const) {
for (const size of [1024, 2048, 4096]) getWindow(name, size);
}
// Later, before a benchmark, evict every vector.
clearWindowCache();
```
## API Reference
- `getWindow(name, size): Float32Array` — cached window vector.
- `applyWindow(frame, window, out): void` — element-wise multiply.
- `clearWindowCache(): void` — drop every cached vector.
- `WindowName = "hann" | "hamming" | "blackman"`.
## Implementation Notes
Each closed form is the standard periodic definition with `N - 1` in the denominator, so the endpoints of Hann and Blackman are exact zeros and the centre is exactly `1`. Hamming uses the `(0.54, 0.46)` coefficients from Hamming's 1989 paper. The cache key is `${name}:${size}`; since both inputs are tightly constrained by the analyzer layer the key set is small.

View File

@@ -0,0 +1,69 @@
# `dsp` — shared signal-processing helpers
## Purpose
The dsp module is the per-frame DSP toolbox every analyzer in this package leans on. It owns the document→mono read path, frame windowing+FFT, log-spaced bucket-mean reduction from the FFT bins down to the configured `bins`, and the three scalar spectral features (centroid, rolloff, flux). Centralising these primitives keeps `spectrum-analyzer.ts`, `spectrogram.ts`, and `mfcc.ts` thin orchestration layers focused on result-envelope construction.
## Features
- `readMono` — fold an `AudioDocument` into a single channel-0 `Float32Array`.
- `frameMagnitude` — slice + window + FFT in one call, returning the magnitude bin vector.
- `frameCount` — hop-arithmetic frame count for a given document length.
- `reduceToBins` — average FFT magnitude bins into `bins` log-spaced buckets.
- `spectralCentroid`/`spectralRolloff`/`spectralFlux` — closed-form scalar features.
- `logFloor` — `Math.log` floored to `1e-10` so empty mel bands don't produce `-Infinity`.
## Usage: Basic
```ts
import {
frameCount,
frameMagnitude,
readMono,
reduceToBins,
spectralCentroid,
} from "@suaveplan/audio-analysis-spectrum/analyzer/dsp";
const samples = await readMono(doc);
const count = frameCount(samples.length, 2048, 512);
for (let i = 0; i < count; i++) {
const mag = frameMagnitude(samples, i * 512, 2048, "hann");
const peak = reduceToBins(mag, 128);
const centroidHz = spectralCentroid(mag, doc.sampleRate, 2048);
// ...
}
```
## Usage: Advanced
```ts
import {
logFloor,
spectralFlux,
spectralRolloff,
} from "@suaveplan/audio-analysis-spectrum/analyzer/dsp";
let previous: Float32Array | undefined;
for (let i = 0; i < count; i++) {
const mag = frameMagnitude(samples, i * 512, 2048, "hann");
const flux = spectralFlux(mag, previous);
const rolloffHz = spectralRolloff(mag, doc.sampleRate, 2048, 0.9);
const logMel = mag.map(logFloor);
previous = mag;
}
```
## API Reference
- `readMono(doc): Promise<Float32Array>` — fold channels.
- `frameMagnitude(samples, offset, frameSize, window): Float32Array`.
- `frameCount(length, frameSize, hopSize): number`.
- `reduceToBins(magnitude, bins): Float32Array`.
- `spectralCentroid(magnitude, sampleRate, frameSize): number`.
- `spectralRolloff(magnitude, sampleRate, frameSize, threshold?): number`.
- `spectralFlux(magnitude, previous): number`.
- `logFloor(value): number`.
## Implementation Notes
`reduceToBins` falls back to a direct copy when `bins >= magnitude.length`, since a strict bucket mean would be ill-defined; this is exercised by the spectrum-analyzer tests that request `bins ≥ frameSize/2 + 1`. The flux helper handles a `previous = undefined` first frame by returning `0`, which matches every textbook definition where the first frame has no predecessor.

View File

@@ -0,0 +1,51 @@
# `MFCC` — mel-frequency cepstral coefficients
## Purpose
`MFCC` is the speech/music feature extractor of this package: per frame it builds the magnitude spectrum, applies a triangular mel filterbank, takes the natural log of the mel energies (floored to `1e-10` so empty bands do not produce `-Infinity`), and runs a type-II DCT to produce `coefficients` cepstral values.
The default is 13 coefficients over 40 mel bands — the canonical MFCC-13 configuration used in speaker-ID and music-genre pipelines.
## Features
- 40-band mel filterbank by default; configurable via `melBands`.
- 13 coefficients by default; configurable via `coefficients` (`≤ melBands`).
- `[minHz, maxHz]` configurable; `maxHz` defaults to the Nyquist frequency.
- `analyzeStream` yields one coefficient vector at a time.
## Usage: Basic
```ts
import { MFCC } from "@suaveplan/audio-analysis-spectrum/analyzer/mfcc";
const analyzer = new MFCC();
const result = await analyzer.analyze(doc);
console.log(result.mfcc.length, result.mfcc[0]?.length); // frame count, 13
```
## Usage: Advanced
```ts
const analyzer = new MFCC();
for await (const partial of analyzer.analyzeStream(doc, {
frameSize: 4096,
hopSize: 1024,
melBands: 26,
coefficients: 20,
minHz: 80,
maxHz: 8000,
window: "hamming",
})) {
// partial.mfcc is a one-element array — accumulate as needed
}
```
## API Reference
- `name`, `description`, `analyze(doc, opts?)`, `analyzeStream(doc, opts?)`.
- `opts` includes `melBands`, `coefficients`, `minHz`, `maxHz`, `frameSize`, `hopSize`, `window`, `signal`.
- Result extends `AnalysisResult` with `op: "mfcc"` and `mfcc: Float32Array[]`.
## Implementation Notes
`coefficients > melBands` is an unconditional `AUDIO_ANALYSIS_SPECTRUM.INVALID_OPTIONS` because the DCT projection would be ill-defined. The log floor is `1e-10` — small enough to be invisible to every downstream comparator, large enough to keep `Math.log` finite. The mel filterbank is keyed on `(frameSize, sampleRate, melBands, minHz, maxHz)` so repeated frames of the same document share a single matrix.

View File

@@ -0,0 +1,57 @@
# `runtime` — shared analyzer plumbing
## Purpose
The runtime module is the shared spine for `SpectrumAnalyzer`, `Spectrogram`, and `MFCC`. It owns option parsing through the canonical Zod schema, abort-signal short-circuits, OTel span+counter+histogram instrumentation, and construction of the audio-core `AnalysisResult` envelope. Centralising this scaffolding means every analyzer stays focused on its DSP and the package as a whole emits identical telemetry shapes.
## Features
- `parseOptions` — single boundary into `SpectrumAnalyzerOptionsSchema` that throws `AUDIO_ANALYSIS_SPECTRUM.INVALID_OPTIONS` with the Zod issues.
- `ensureNotAborted` — throws `AUDIO_ANALYSIS_SPECTRUM.ABORTED` if a caller-supplied `AbortSignal` is already aborted.
- `startRun` — opens a span, records sample-rate/duration attributes, and returns `end`/`fail` callbacks bound to the runs counter and duration histogram.
- `recordBlock` — bumps the block-duration histogram for `analyzeBlock`.
- `buildBase` — produces the `AnalysisResult` envelope (`analyserName`, `analyserVersion`, `sampleRate`, `durationSamples`, `computedAt`).
## Usage: Basic
```ts
import {
buildBase,
ensureNotAborted,
parseOptions,
startRun,
} from "@suaveplan/audio-analysis-spectrum/analyzer/runtime";
const opts = parseOptions(raw);
ensureNotAborted(opts.signal);
const handle = startRun("spectrum-analyzer", doc, "analyze");
try {
// ... run the DSP ...
handle.end();
} catch (err) {
handle.fail(err);
throw err;
}
```
## Usage: Advanced
```ts
import { recordBlock } from "@suaveplan/audio-analysis-spectrum/analyzer/runtime";
const startedAt = performance.now();
const result = computeBlock();
recordBlock("spectrum-analyzer", performance.now() - startedAt);
```
## API Reference
- `parseOptions(raw): SpectrumAnalyzerOptions` — schema-validated options.
- `ensureNotAborted(signal): void` — throws `ABORTED` on pre-aborted signals.
- `startRun(op, doc, mode): { end, fail }` — OTel run handle.
- `recordBlock(op, durationMs): void` — record a single block timing.
- `buildBase(op, doc): { analyserName, analyserVersion, sampleRate, durationSamples, computedAt }`.
## Implementation Notes
Instrumentation uses `@opentelemetry/api` which short-circuits to a no-op when no SDK is registered, so importing this module mutates no global state and runs free in suites that don't configure a `MeterProvider`. Tags on the counter and histograms are intentionally minimal — `op` and `mode` — so cardinality stays bounded even with high analyzer fan-out.

View File

@@ -0,0 +1,48 @@
# `Spectrogram` — time-frequency magnitude matrix
## Purpose
`Spectrogram` produces the ordered list of per-frame magnitude spectra for a document, slicing by the same `frameSize`/`hopSize`/`window` options as `SpectrumAnalyzer`. The result is the canonical input for downstream beat, pitch, and chromagram analyzers. Documents shorter than `frameSize` cleanly yield an empty matrix rather than throwing.
`analyze` returns the entire matrix in one allocation; `analyzeStream` yields one frame at a time so a visualiser can update during the scan. Concatenating the per-frame matrices yielded by the stream matches `analyze`'s `spectrogram` exactly.
## Features
- Frame count matches `floor((doc.length - frameSize) / hopSize) + 1` exactly.
- Each row is the full `frameSize / 2 + 1` magnitude vector.
- Empty result for documents shorter than one frame — no error thrown.
- Streamed frames are deep-equal to the single-shot matrix.
## Usage: Basic
```ts
import { Spectrogram } from "@suaveplan/audio-analysis-spectrum/analyzer/spectrogram";
const analyzer = new Spectrogram();
const result = await analyzer.analyze(doc);
console.log(result.spectrogram.length, result.spectrogram[0]?.length);
```
## Usage: Advanced
```ts
const analyzer = new Spectrogram();
const all: Float32Array[] = [];
for await (const partial of analyzer.analyzeStream(doc, {
frameSize: 4096,
hopSize: 1024,
window: "blackman",
})) {
for (const frame of partial.spectrogram) all.push(frame);
}
```
## API Reference
- `name`, `description`, `analyze(doc, opts?)`, `analyzeStream(doc, opts?)`.
- `opts` includes `frameSize`, `hopSize`, `window`, `signal`.
- Result extends `AnalysisResult` with `op: "spectrogram"` and `spectrogram: Float32Array[]`.
## Implementation Notes
`analyzeStream` yields one `SpectrogramResult` per frame whose `spectrogram` array has length `1`. The empty-result case yields a single zero-frame result so callers can rely on the generator producing at least one event. Like `SpectrumAnalyzer`, the abort signal is checked before each frame to honour the mid-scan abort semantics.

View File

@@ -0,0 +1,62 @@
# `SpectrumAnalyzer` — magnitude spectrum + scalar features
## Purpose
`SpectrumAnalyzer` is the workhorse analysis op of this package: it slices an `AudioDocument` into overlapping frames, windows each frame, runs the real FFT, and reduces the per-bin magnitudes into a fixed-length `spectrum` plus three scalar features — centroid, rolloff, flux. The default 128-bin output is the standard size used by visualisers and feature pipelines.
The class implements `analyze` (single-shot whole document), `analyzeStream` (per-frame async generator suitable for real-time visualisation), and `analyzeBlock` (one already-windowed block via a `BlockContext`).
## Features
- Slices `doc` into `frameSize`/`hopSize` overlapping frames (defaults `2048`/`512`).
- Hann window by default; Hamming or Blackman selectable via `window`.
- Log-spaced bucket-mean reduction to the configured `bins` (default `128`).
- Centroid, 85% rolloff, and positive-flux scalars per result.
- `analyzeStream` yields per-frame results; the final frame's vectors match `analyze`.
- `analyzeBlock` runs a single FFT-sized block with `BlockContext.sampleRate`.
## Usage: Basic
```ts
import { SpectrumAnalyzer } from "@suaveplan/audio-analysis-spectrum/analyzer/spectrum-analyzer";
const analyzer = new SpectrumAnalyzer();
const result = await analyzer.analyze(doc);
console.log(result.centroid, result.rolloff, result.flux, result.spectrum);
```
## Usage: Advanced
```ts
const analyzer = new SpectrumAnalyzer();
const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 50);
try {
for await (const frame of analyzer.analyzeStream(doc, {
frameSize: 4096,
hopSize: 1024,
bins: 256,
window: "blackman",
signal: ctrl.signal,
})) {
updateVisualiser(frame.spectrum);
}
} catch (err) {
// AUDIO_ANALYSIS_SPECTRUM.ABORTED expected on early cancel.
}
const block = analyzer.analyzeBlock([new Float32Array(2048)], {
sampleRate: 48_000,
startSample: 0,
});
```
## API Reference
- `name`, `description`, `analyze(doc, opts?)`, `analyzeStream(doc, opts?)`, `analyzeBlock(block, ctx)`.
- `opts` includes `bins`, `frameSize`, `hopSize`, `window`, `signal`; defaults from the Zod schema.
- Result extends `AnalysisResult` with `op: "spectrum-analyzer"`, `spectrum`, `centroid`, `rolloff`, `flux`.
## Implementation Notes
The DSP pipeline lives in the `dsp` module so `SpectrumAnalyzer` stays an orchestrator. `analyze` retains only the final frame's `spectrum`/`magnitude` and the previous frame's `magnitude` so flux is well-defined. Each frame revalidates the abort signal between iterations, satisfying the mid-scan abort scenario. `analyzeBlock` does not consult an `AbortSignal` — block calls are bounded-time and the call site owns its own cancellation.

View File

@@ -0,0 +1,57 @@
# Error codes — `@suaveplan/audio-analysis-spectrum`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The error-codes module owns the stable `AUDIO_ANALYSIS_SPECTRUM.*` code registry used by every analyzer in this package. Each error thrown by the FFT, window, mel filterbank, DCT, `SpectrumAnalyzer`, `Spectrogram`, or `MFCC` carries one of these codes so consumers can branch on machine-readable identifiers rather than message strings.
The module is import-time pure — `registerAudioAnalysisSpectrumErrorCodes` is a no-op because metadata is already frozen at module load — but it is exported so OTel registries and report builders can declare an explicit dependency on the codes existing.
## Features
- Three stable codes: `INVALID_OPTIONS`, `INVALID_FRAME`, `ABORTED`.
- Frozen metadata record with `description` and `severity` for every code.
- `AudioAnalysisSpectrumError` subclass of `SuaveplanError` with a public constructor and a `category: "audio-analysis-spectrum"` discriminator.
## Usage: Basic
```ts
import {
AudioAnalysisSpectrumError,
AUDIO_ANALYSIS_SPECTRUM_CODES,
} from "@suaveplan/audio-analysis-spectrum/error-codes";
throw new AudioAnalysisSpectrumError(
AUDIO_ANALYSIS_SPECTRUM_CODES.ABORTED,
"audio-analysis-spectrum.aborted",
);
```
## Usage: Advanced
```ts
import {
AUDIO_ANALYSIS_SPECTRUM_CODES,
AUDIO_ANALYSIS_SPECTRUM_ERROR_METADATA,
} from "@suaveplan/audio-analysis-spectrum/error-codes";
for (const code of Object.values(AUDIO_ANALYSIS_SPECTRUM_CODES)) {
const meta = AUDIO_ANALYSIS_SPECTRUM_ERROR_METADATA[code];
console.log(code, meta.severity, meta.description);
}
```
## API Reference
- `AUDIO_ANALYSIS_SPECTRUM_CODES` — frozen record produced by `defineCodes`.
- `AudioAnalysisSpectrumErrorCode` — string-literal union of the three codes.
- `AUDIO_ANALYSIS_SPECTRUM_ERROR_METADATA` — `Record<code, { description; severity }>`.
- `AudioAnalysisSpectrumError` — `SuaveplanError` subclass with `category` and a public constructor.
- `registerAudioAnalysisSpectrumErrorCodes(): void` — idempotent no-op for startup hooks.
## Implementation Notes
The class widens `SuaveplanError`'s `protected` constructor to `public` via subclass declaration. The `category` literal field defeats biome's "useless class" rule without requiring an inline suppression — see CLAUDE.md rule 9. Every code in `AUDIO_ANALYSIS_SPECTRUM_ERROR_METADATA` must be reachable from at least one test (`error-codes.test.ts` walks the metadata and asserts each is thrown from a real call site).