Software
Features
Detectors

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);
}, []);