muse-js JavaScript Library

repository·master·Indexed 43 days ago

https://github.com/urish/muse-js

A JavaScript library for interacting with Muse 1, Muse 2, and Muse S EEG headsets via Web Bluetooth. Version 3.3.0 provides real-time streams for EEG, telemetry, accelerometer, PPG, and event markers through the MuseClient class. It includes utilities for decoding raw Bluetooth data, managing device presets (such as enabling auxiliary electrodes or PPG sensors), and synchronizing asynchronous sensor readings into sample formats.

Tokens
5.3K
Snippets
28
Records
29
Agent score
55%

What's inside muse-js

  1. Use MuseClient in Node.js

    master

    Since muse-js relies on the Web Bluetooth API, you must use a package like bleat to emulate Web Bluetooth in a Node.js environment. This requires noble to handle the underlying Bluetooth hardware. You should wait for the noble state to be poweredOn before attempting to request a device and connecting the MuseClient to the GATT server.

    const noble = require('noble');
    const bluetooth = require('bleat').webbluetooth;
    
    async function connect() {
        let device = await bluetooth.requestDevice({
            filters: [{ services: [MUSE_SERVICE] }]
        });
        const gatt = await device.gatt.connect();
        const client = new MuseClient();
        await client.connect(gatt);
        await client.start();
        // Now do whatever with muse client...
    }
    
    noble.on('stateChange', (state) => {
        if (state === 'poweredOn') {
            connect();
        }
    });
  2. Basic usage of MuseClient

    master

    To use muse-js in a web environment, import MuseClient, instantiate it, connect, and start the stream. You can subscribe to eegReadings, telemetryData, and accelerometerData using an observer pattern to receive real-time data from the headset.

    import { MuseClient } from 'muse-js';
    
    async function main() {
      let client = new MuseClient();
      await client.connect();
      await client.start();
      client.eegReadings.subscribe(reading => {
        console.log(reading);
      });
      client.telemetryData.subscribe(telemetry => {
        console.log(telemetry);
      });
      client.accelerometerData.subscribe(acceleration => {
        console.log(acceleration);
      });
    }
    
    main();
  3. Inject timestamped Event Markers

    master

    The MuseClient provides an eventMarkers stream that allows you to inject custom, timestamped events into your data stream. Subscribe to the eventMarkers stream to receive these events, and use the injectMarker(value, [timestamp]) method to send them.

    async function main() {
        let client = new MuseClient();
        client.eventMarkers.subscribe((event) => {
            console.log(event);
        });
        client.injectMarker("house")
        client.injectMarker("face")
        client.injectMarker("dog")
    }
  4. Enable the Auxiliary Electrode

    master

    Muse 2016 EEG headsets support an additional Auxiliary electrode via the Micro USB port. To receive data from this channel, set the enableAux property to true on your MuseClient instance before calling the connect() method.

    async function main() {
      let client = new MuseClient();
      client.enableAux = true;
      await client.connect();
    }
  5. Use PPG (Photoplethysmography) / Optical Sensors

    master

    Muse 2 and Muse S headsets include PPG/optical blood sensors providing three signal streams: ppg1, ppg2, and ppg3.

    Warning: PPG is not present on Muse 1/1.5. Enabling it on those models may cause unexpected consequences.

    To use PPG, set enablePpg = true on the MuseClient instance before calling connect(), then subscribe to ppgReadings.

    async function main() {
      let client = new MuseClient();
      client.enablePpg = true;
      await client.connect();
    }
    
    // Subscribing to readings
    client.ppgReadings.subscribe((ppgreading) => {
        console.log(ppgreading);
    });
  6. Configure MuseClient sensor options

    master

    You can enable additional sensors on the MuseClient instance before calling connect():

    • enablePpg: Set to true to enable the Photoplethysmography (PPG) optical sensor. This changes the device preset to p50.
    • enableAux: Set to true to enable the auxiliary EEG channel. This changes the device preset to p20.

    By default, enablePpg and enableAux are false.

    const client = new MuseClient();
    client.enablePpg = true;
    client.enableAux = true;
    await client.connect();
  7. Parse control data with parseControl

    master

    Use parseControl to transform an Observable<string> of raw control characters into an Observable of parsed JSON objects. This function handles character buffering and splits data based on the } delimiter to ensure complete JSON objects are emitted.

    import { parseControl } from './muse-parse';
    import { Observable } from 'rxjs';
    
    // controlStream is an Observable<string> emitting raw characters
    const controlStream$ = parseControl(controlStream);
    
    controlStream$.subscribe(jsonObject => {
      console.log('Parsed control command:', jsonObject);
    });
  8. Access real-time sensor data streams

    master

    MuseClient exposes several RxJS Observables for real-time data. You can subscribe to these to process sensor readings:

    ObservableData TypeDescription
    eegReadingsEEGReadingElectroencephalogram data
    ppgReadingsPPGReadingOptical sensor (PPG) data
    accelerometerDataAccelerometerData3-axis accelerometer data
    gyroscopeDataGyroscopeData3-axis gyroscope data
    telemetryDataTelemetryDataBattery and device status
    controlResponsesMuseControlResponseResponses to sent commands
    connectionStatusBehaviorSubject<boolean>Current connection state

    Note: ppgReadings is only available if enablePpg is set to true before connecting.

  9. Zip EEG readings into EEGSample format with zipSamples()

    master

    The zipSamples function transforms a stream of raw EEGReading objects into a stream of EEGSample objects. This is useful for converting asynchronous, electrode-specific readings into a synchronized format where each sample contains data for all electrodes at a specific timestamp.

    Data Structure

    An EEGSample consists of:

    • index: The original index from the reading.
    • timestamp: The calculated timestamp in milliseconds since epoch.
    • data: An array of numbers representing the values for each electrode (e.g., [NaN, NaN, NaN, NaN, NaN]).

    How it works

    1. It buffers EEGReading objects that share the same timestamp.
    2. Once a new timestamp is detected, it processes the buffered readings.
    3. It maps the samples across the electrodes, filling the data array at the index corresponding to the reading.electrode.
    4. It calculates the timestamp for each sample based on the EEG_FREQUENCY constant.
    import { zipSamples, EEGSample } from './path-to-lib';
    import { Observable } from 'rxjs';
    import { EEGReading } from './muse-interfaces';
    
    // Assuming eegReadings is an Observable<EEGReading>
    const samples$: Observable<EEGSample> = zipSamples(eegReadings);
    
    samples$.subscribe(sample => {
        console.log(`Timestamp: ${sample.timestamp}, Data: ${sample.data}`);
    });
  10. Decode EEG samples with decodeEEGSamples

    master

    Use decodeEEGSamples to convert raw Uint8Array Bluetooth data into meaningful EEG readings. The function handles the 12-bit unsigned decoding and applies the necessary scaling factor to convert the raw values into a usable range.

    import { decodeEEGSamples } from './muse-parse';
    
    // rawData is a Uint8Array received from the Bluetooth device
    const eegReadings = decodeEEGSamples(rawData);
  11. Parse telemetry data with parseTelemetry

    master

    Use parseTelemetry to extract device status information from a DataView. This returns a TelemetryData object containing:

    • sequenceId: uint16
    • batteryLevel: float (0.0 to 1.0)
    • fuelGaugeVoltage: float
    • temperature: uint16
    import { parseTelemetry } from './muse-parse';
    
    // data is a DataView of the telemetry packet
    const telemetry = parseTelemetry(data);
    console.log(telemetry.batteryLevel);
  12. Inject custom event markers

    master

    You can manually inject event markers into the eventMarkers stream. This is useful for synchronizing external events with the sensor data stream.

    // Inject a marker with a value and a specific timestamp
    await client.injectMarker('stimulus_start', Date.now());
    await client.injectMarker('event_name', Date.now());