Detectors
Browser-side EEG processing hooks — React refs, zero re-renders. Read .current in your requestAnimationFrame loop.
import { useBandPowers, useBlink, useFocus, useRelax } from "../../hooks/detectors";useBandPowers(eegData, config?)
Foundation layer. Single FFT instance, averaged spectral band powers.
| Field | Type | Description |
|---|---|---|
absolute | BandPowers | Absolute power per band (µV²/Hz) — Delta, Theta, Alpha, Beta, Gamma |
relative | BandPowers | Normalized to sum = 1 |
totalPower | number | Sum across all bands |
dominantFrequency | number | Peak PSD bin (Hz) |
Config: { updateHz?, channels?, smoothing? }
useBlink(eegData, config?)
Ocular artifact detector. Amplitude-threshold state machine on frontal channels (Fp1/Fp2).
| Field | Type | Description |
|---|---|---|
blinked | boolean | true for exactly one poll cycle per blink |
count | number | Cumulative blink count |
amplitude | number | Current peak-to-peak µV |
lastBlinkTime | number | Epoch ms of last confirmed blink |
Config: { channels?, threshold?, windowMs?, minDurationMs?, maxDurationMs?, refractoryMs?, pollHz? }
useFocus(eegData, config?)
Cortical engagement index — (Beta + Gamma) / (Alpha + Theta + Delta).
| Field | Type | Description |
|---|---|---|
focus | number | 0 (relaxed) – 1 (highly focused), smoothed |
raw | number | Unsmoothed, uncalibrated ratio |
calibrated | boolean | Whether baseline has been captured |
Config: { channels?, updateHz?, smoothing?, scaleDivisor? }
Returns: { state, calibrate(), resetCalibration(), calibrating }
useRelax(eegData, config?)
Alpha-dominance + theta-beta ratio composite relaxation index.
| Field | Type | Description |
|---|---|---|
relaxation | number | 0 (alert) – 1 (deeply relaxed), smoothed |
alphaRelative | number | Alpha / total power (0–1) |
thetaBetaRatio | number | θ / β raw ratio |
calibrated | boolean | Whether baseline has been captured |
Config: { channels?, updateHz?, smoothing?, alphaWeight?, tbrCeiling? }
Returns: { state, calibrate(), resetCalibration(), calibrating }
Usage Pattern
All detectors use the same ref-based pattern for zero-rerender reads:
const { state: focus } = useFocus(eegData);
useEffect(() => {
let raf: number;
function loop() {
const f = focus.current.focus; // read directly, no re-render
// use f to drive animation, game logic, etc.
raf = requestAnimationFrame(loop);
}
raf = requestAnimationFrame(loop);
return () => cancelAnimationFrame(raf);
}, []);