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,96 @@
# `<MeterPeak>` — peak meter with hold + decay
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`<MeterPeak>` is a digital-style peak meter that shows the instantaneous
input level alongside a held-then-decaying peak marker. After every new
sample exceeds the held maximum, the marker freezes for `holdMs` and
then decays linearly at `decayPerSec` level-units per second. The
default 1000 ms hold + 1.4-per-second decay matches the legibility
profile common in commercial DAWs.
This component intentionally does not include ballistics — for analogue
VU response use `<MeterVU>`. `<MeterPeak>` is the right choice for
clip detection, true-peak indication, and any time the user must see
the actual recent maximum rather than a perceptual average.
## Features
- Independent `value` (filled bar) and `peak` (hold marker) channels
- Configurable hold window (`holdMs`) and decay rate (`decayPerSec`)
- Optional `peak` prop — when omitted, derived from `value`
- `role="meter"` with `aria-valuetext` describing both level and peak
- CSS-variable themed fill and hold marker
- `data-level` / `data-peak` deterministic E2E hooks
## Usage: Basic
```tsx
import { MeterPeak } from "@suaveplan/react-control-surface";
<MeterPeak value={0.4} peak={0.7} label="L" />;
```
## Usage: Advanced
```tsx
import { MeterPeak } from "@suaveplan/react-control-surface";
function ClipDetector({ stream }: { stream: AsyncIterable<number> }) {
const [v, setV] = useState(0);
useEffect(() => {
let cancelled = false;
(async () => {
for await (const s of stream) {
if (cancelled) return;
setV(s);
}
})();
return () => {
cancelled = true;
};
}, [stream]);
return (
<MeterPeak
value={v}
holdMs={2000}
decayPerSec={0.6}
label="Clip"
/>
);
}
```
## API Reference
| Prop | Type | Default | Description |
| ------------- | -------- | ------- | ---------------------------------------------------- |
| `value` | `number` | required| Instantaneous level in `[0, 1]`. |
| `peak` | `number` | derived | External peak source. Falls back to `value`. |
| `holdMs` | `number` | `1000` | Hold time after each new maximum. |
| `decayPerSec` | `number` | `1.4` | Linear decay rate once the hold expires. |
| `width` | `number` | `160` | Root width in CSS pixels. |
| `height` | `number` | `24` | Root height in CSS pixels. |
| `label` | `string` | — | Visible + accessible label. |
| `ariaLabel` | `string` | derived | Override the accessible label. |
| `className` | `string` | — | Appended to `rcs-meter-peak`. |
The exported `stepPeakHold(held, heldUntil, incoming, now, holdMs,
decayPerSec, dtMs)` helper is the pure-function hold envelope used
internally. It is exported for re-use and is unit-tested independently.
## Implementation Notes
State is held in a `useRef` (`{ held, heldUntil, last }`) so smoothing
does not require re-renders on every RAF tick — only the display value
updates via `setState`. Hold deadlines are stored as absolute timestamps
(`Date.now() + holdMs`) which makes the math identical between live
runs and the synchronous unit tests of `stepPeakHold`.
The hold marker is rendered as a 2-pixel-wide absolutely-positioned div
that tracks the held value's percentage position; the fill bar tracks
the instantaneous value. Both are themable via CSS variables.