@shiihaa/breath-detection
v1.0.0
Published
Real-time breath detection using microphone energy analysis, spectral centroid classification, and BLE heart rate data. Works in browsers and Capacitor apps.
Maintainers
Readme
@shiihaa/breath-detection
Real-time breath cycle detection using microphone energy analysis, spectral centroid classification, and optional BLE heart rate data. Works in browsers and Capacitor apps.
By shii · haa — a breathwork and biofeedback app built by a physician.
Features
- Microphone-based breath detection — detects inhale/exhale cycles from audio energy
- Spectral centroid classification — distinguishes inhale (higher frequency, turbulent airflow) from exhale (lower frequency, laminar airflow)
- Dual-path detection — threshold-based state machine for paused breathing + peak-based fallback for continuous breathing
- Auto-recalibration — adapts to changing ambient noise every 10 seconds
- Refractory period — prevents double-counting with configurable minimum cycle gap
- Optional BLE heart rate input — accepts external HR data for enhanced analysis
- TypeScript types included
- Zero dependencies
Installation
npm install @shiihaa/breath-detectionQuick Start
import { BreathDetector } from '@shiihaa/breath-detection';
const detector = new BreathDetector();
// Listen for completed breath cycles
detector.onCycle((cycle) => {
console.log(`Breath: ${cycle.inhaleMs}ms in, ${cycle.exhaleMs}ms out`);
console.log(`Rate: ${(60000 / cycle.cycleMs).toFixed(1)} breaths/min`);
console.log(`Method: ${cycle.method}`); // 'threshold' or 'peak'
});
// Listen for real-time phase changes
detector.onPhase((event) => {
console.log(`Phase: ${event.phase}, Energy: ${event.energy.toFixed(2)}`);
});
// Start microphone
const ok = await detector.start();
if (!ok) { console.error('Mic access denied'); return; }
// Calibrate (user breathes for 6 seconds)
const cal = await detector.calibrate();
console.log(`Noise: ${cal.noiseFloor.toFixed(4)}, Breath: ${cal.breathMax.toFixed(4)}`);
// Start detection loop
detector.startDetection();
// Later: stop
detector.stop();How It Works
Detection Pipeline
Microphone → FFT → Energy + Centroid → State Machine → Breath Cycles
↑
Peak Counter (fallback)- Audio Capture: Microphone via Web Audio API (
getUserMedia) - Spectral Analysis: FFT computes energy in 150–2500 Hz band + spectral centroid
- Energy Smoothing: EMA filter removes noise spikes
- State Machine: Tracks phases (active → silent → active → silent = 1 cycle)
- Peak Fallback: If energy never drops to silence (continuous breathing), counts energy peaks instead — two peaks = one breath cycle
- Centroid Classification: Higher centroid → inhale (turbulent "shii..."), lower centroid → exhale (laminar "...haa")
- Auto-Recalibration: Noise floor and breath max adapt every 10s
Inhale vs. Exhale Classification
The spectral centroid (frequency center of mass) differs between inhale and exhale:
| Phase | Airflow | Centroid | |-------|---------|----------| | Inhale | Turbulent, through nasal passages | ~800–2500 Hz | | Exhale | Laminar, relaxed | ~200–800 Hz |
This is a novel approach — to our knowledge, no other breathwork app uses spectral analysis for breath phase classification.
API Reference
new BreathDetector(options?)
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| fftSize | number | 4096 | FFT window size |
| smoothingAlpha | number | 0.25 | Energy EMA smoothing (0–1) |
| minCycleGapSeconds | number | 2.5 | Minimum seconds between cycles |
| minPhaseSeconds | number | 1.5 | Minimum seconds per breath phase |
| thresholdFactor | number | 0.35 | Detection sensitivity (0=sensitive, 1=strict) |
| freqLow | number | 150 | Low frequency bound (Hz) |
| freqHigh | number | 2500 | High frequency bound (Hz) |
| enableCentroid | boolean | true | Enable spectral centroid classification |
| centroidThreshold | number | 40 | Hz difference for confident in/out labeling |
Methods
| Method | Returns | Description |
|--------|---------|-------------|
| start() | Promise<boolean> | Start microphone capture |
| stop() | void | Stop capture, release resources |
| calibrate() | Promise<CalibrationResult> | 6-second calibration (2s noise + 4s breath) |
| startDetection() | void | Start the detection loop |
| stopDetection() | void | Stop detection (keeps mic open) |
| setHeartRate(bpm) | void | Feed external HR for enhanced analysis |
| getState() | object | Current state (phase, energy, centroid, etc.) |
Events
| Event | Callback | Description |
|-------|----------|-------------|
| onCycle(cb) | (cycle: BreathCycle) => void | Full breath cycle detected |
| onPhase(cb) | (event: BreathPhaseEvent) => void | Phase change (every tick) |
| onCalibration(cb) | (result: CalibrationResult) => void | Calibration complete |
| onEnergy(cb) | (energy, centroid) => void | Raw energy + centroid per tick |
BreathCycle
{
inhaleMs: number; // Inhale duration
exhaleMs: number; // Exhale duration
holdInMs: number; // Hold after inhale (0 if none)
holdOutMs: number; // Hold after exhale (0 if none)
cycleMs: number; // Total cycle duration
peakEnergy: number; // Peak energy (0–1)
confidence: number; // Detection confidence (0–100)
labelSwapped: boolean; // Whether in/out was corrected by centroid
centroidA1: number; // Spectral centroid phase 1 (Hz)
centroidA2: number; // Spectral centroid phase 2 (Hz)
method: string; // 'threshold' or 'peak'
timestamp: number; // Completion time
}Use with Capacitor / iOS
On iOS, WKWebView's Web Audio API AnalyserNode returns garbage data with getUserMedia. Use our companion plugin @shiihaa/capacitor-audio-analysis for native AVAudioEngine audio capture, then feed the energy values to BreathDetector.
Background
This library was extracted from shii · haa, a breathwork and biofeedback app. The breath detection algorithm was developed to solve a specific challenge: real-time breath phase detection using only a smartphone microphone, without any wearable sensor.
The spectral centroid approach for inhale/exhale classification emerged from the observation that inhaled air creates turbulent flow (higher frequencies) while exhaled air creates laminar flow (lower frequencies) — a well-known principle in respiratory physiology that hadn't been applied to mobile breath detection before.
Related
@shiihaa/capacitor-audio-analysis— Native iOS audio capture plugin (AVAudioEngine) for Capacitor- shii · haa — The breathwork app that uses this library
- Blog: How We Solved iOS Audio Analysis — Technical deep-dive
License
MIT © Felix Zeller / shii · haa
