Skip to main content

HeadbandFrameV1

The canonical data frame emitted by all headband transports. Every implementation (BLE, native bridge, synthetic) produces this same shape:
interface HeadbandFrameV1 {
  schemaVersion: "v1";
  source: string;                        // e.g., "muse-ble"
  sequenceId: number;
  emittedAtMs: number;
  eeg: HeadbandSignalBlock;              // always present
  ppgRaw?: HeadbandSignalBlock;          // PPG (if available)
  optics?: HeadbandSignalBlock;          // Athena optics
  accgyro?: HeadbandSignalBlock;         // Athena accelerometer/gyroscope
  battery?: HeadbandBatteryBlock;        // battery level
}

HeadbandSignalBlock

A block of time-series samples for one or more channels:
interface HeadbandSignalBlock {
  sampleRateHz: number;
  channelNames: string[];                // e.g., ["TP9", "AF7", "AF8", "TP10"]
  channelCount: number;
  samples: number[][];                   // rows of [ch0, ch1, ch2, ch3]
  timestampsMs?: number[];
  clockSource?: "device" | "local";
}

HeadbandTransportState

Transport lifecycle states:
StateDescription
IdleTransport created, not connected
ConnectingConnection in progress
ConnectedConnected, not streaming
StreamingActively receiving frames
DegradedConnected but experiencing issues
ReconnectingAttempting to reconnect
DisconnectedDisconnected from device
ErrorUnrecoverable error

HeadbandTransport Interface

All transports implement this interface:
interface HeadbandTransport {
  onFrame?: (frame: HeadbandFrameV1) => void;
  onStatus?: (status: HeadbandTransportStatus) => void;
  connect(): Promise<void>;
  disconnect(): Promise<void>;
  start(): Promise<void>;
  stop(): Promise<void>;
}
Lifecycle:

HeadbandTransportStatus

Status updates emitted via onStatus:
interface HeadbandTransportStatus {
  state: HeadbandTransportState;
  atMs: number;
  reason?: string;
  errorCode?: string;
  recoverable?: boolean;
  details?: Record<string, unknown>;
}

Usage Pattern

const transport: HeadbandTransport = /* BleTransport or other */;

transport.onFrame = (frame) => {
  const eegSamples = frame.eeg.samples;
  // Process each row: [tp9, af7, af8, tp10]
};

transport.onStatus = (status) => {
  console.log(`State: ${status.state}`, status.reason);
};

await transport.connect();
await transport.start();

// ... later
await transport.stop();
await transport.disconnect();