Skip to Content
FeaturesDetectors

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.

FieldTypeDescription
absoluteBandPowersAbsolute power per band (µV²/Hz) — Delta, Theta, Alpha, Beta, Gamma
relativeBandPowersNormalized to sum = 1
totalPowernumberSum across all bands
dominantFrequencynumberPeak PSD bin (Hz)

Config: { updateHz?, channels?, smoothing? }

useBlink(eegData, config?)

Ocular artifact detector. Amplitude-threshold state machine on frontal channels (Fp1/Fp2).

FieldTypeDescription
blinkedbooleantrue for exactly one poll cycle per blink
countnumberCumulative blink count
amplitudenumberCurrent peak-to-peak µV
lastBlinkTimenumberEpoch ms of last confirmed blink

Config: { channels?, threshold?, windowMs?, minDurationMs?, maxDurationMs?, refractoryMs?, pollHz? }

useFocus(eegData, config?)

Cortical engagement index — (Beta + Gamma) / (Alpha + Theta + Delta).

FieldTypeDescription
focusnumber0 (relaxed) – 1 (highly focused), smoothed
rawnumberUnsmoothed, uncalibrated ratio
calibratedbooleanWhether 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.

FieldTypeDescription
relaxationnumber0 (alert) – 1 (deeply relaxed), smoothed
alphaRelativenumberAlpha / total power (0–1)
thetaBetaRationumberθ / β raw ratio
calibratedbooleanWhether 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); }, []);