first commit
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user