first commit
This commit is contained in:
62
docs/media/audio/audio-analysis-spectrum/README.md
Normal file
62
docs/media/audio/audio-analysis-spectrum/README.md
Normal 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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
69
docs/media/audio/audio-analysis-spectrum/src/analyzer/dsp.md
Normal file
69
docs/media/audio/audio-analysis-spectrum/src/analyzer/dsp.md
Normal 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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
Reference in New Issue
Block a user