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,94 @@
# `<MeterSpectral>` — multi-band spectrum visualiser
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`<MeterSpectral>` renders a real-time bar graph of magnitude bins. Each
bin is drawn as a vertical bar whose height is proportional to the
bin's magnitude in `[0, 1]`. The component is deliberately tier-pure:
it has no dependency on `@suaveplan/audio-analysis-spectrum` or any
audio code at all. Bins are pre-computed by the caller — wire it up to
whatever analyser is appropriate (web-audio `AnalyserNode`, the
spectrum-analysis package, a network stream, or a synthetic generator).
This separation keeps the React rendering layer free of audio
processing and makes it trivial to use the same component for any
1-D magnitude signal — FFT magnitudes, wavelet bands, frequency-warped
mel bins, or live envelope follower output.
## Features
- Static `Float32Array` or streaming `AsyncIterable<Float32Array>` source
- Configurable `width`, `height`, and inter-bar `gap`
- Per-bin `data-value` attributes for E2E and screen-reader inspection
- `role="img"` with a single `aria-label` describing the visualisation
- CSS-variable themed bars (`--cs-meter-spectral-bar-color`)
- Out-of-range bin values are clamped to `[0, 1]` automatically
## Usage: Basic
```tsx
import { MeterSpectral } from "@suaveplan/react-control-surface";
<MeterSpectral
bins={new Float32Array([0.1, 0.5, 0.9, 0.3])}
label="Output spectrum"
/>;
```
## Usage: Advanced
```tsx
import { MeterSpectral } from "@suaveplan/react-control-surface";
async function* spectrumStream(
analyser: AnalyserNode
): AsyncIterable<Float32Array> {
const buf = new Float32Array(analyser.frequencyBinCount);
while (true) {
analyser.getFloatFrequencyData(buf);
const out = new Float32Array(buf.length);
for (let i = 0; i < buf.length; i++) {
out[i] = Math.max(0, Math.min(1, (buf[i] + 100) / 100));
}
yield out;
await new Promise((r) => requestAnimationFrame(() => r(null)));
}
}
<MeterSpectral
bins={spectrumStream(analyserNode)}
width={480}
height={160}
label="Master spectrum"
/>;
```
## API Reference
| Prop | Type | Default | Description |
| ----------- | ------------------------------------------------- | ------- | ---------------------------------------- |
| `bins` | `Float32Array \| AsyncIterable<Float32Array>` | req | Magnitude bins in `[0, 1]`. |
| `width` | `number` | `240` | Total width in CSS pixels. |
| `height` | `number` | `80` | Total height in CSS pixels. |
| `gap` | `number` | `1` | Inter-bar gap in CSS pixels. |
| `label` | `string` | — | Visible + accessible label. |
| `ariaLabel` | `string` | derived | Override the accessible label. |
| `className` | `string` | — | Appended to `rcs-meter-spectral`. |
## Implementation Notes
When `bins` is a `Float32Array`, the component sets its frame state
directly on mount and whenever the prop identity changes. When `bins`
is an `AsyncIterable`, an effect subscribes via `for await … of` and
calls `setFrame` for every yielded frame. The subscription is torn down
via a `cancelled` flag in the cleanup function; errors thrown from the
iterator are swallowed because visualisation is best-effort.
Bars are absolutely-positioned `div`s aligned to the bottom of the
container; bar width is `(width - totalGap) / count`. This keeps the
layout pure CSS and avoids `<canvas>` — every bar is independently
inspectable in DOM and accessible tooling.