Low Level LSL Framework - kirtonBCIlab/bci-essentials-unity GitHub Wiki

In addition to the provided paradigm controller behaviours, you can use the LSL framework to communicate directly with BCI Essentials python.

See the back end marker api.

Writing Markers

BCI Essentials is controlled using marker strings sent as LSL samples, which come in two broad types: Command Markers and Event Markers. The LSLMarkerWriter component and related types are provided to streamline this process and improve clarity by explicitly defining the supported set of markers. To use it yourself, simply add the component to a unity scene and use any of the public Push..Marker methods using a Marker class or an explicit helper overload:

using BCIEssentials.LSLFramework;

LSLMarkerWriter OutStream = GetComponent<LSLMarkerWriter>();

OutStream.PushTrialStartedMarker();
// or
OutStream.PushCommandMarker<TrialStartedMarker>();
// or
OutStream.PushMarker(new TrialStartedMarker());

OutStream.PushMITrainingMarker(1, 0, 2.5f);
//or
MIEventMarker marker = new(1, 0, 3.2f);
marker.EpochLength = 2.5f;
OutStream.PushMarker(marker);

OutStream.PushMIClassificationMarker(1, 1.5f);

Push..TrainingMarker methods will not trigger predictions.
PushString can also be used to send a raw marker directly, outside of the defined types.

Object/Class Indexing

Provided marker classes and write methods are 0-indexed in regards to objects/classes, and will "translate" to the 1-indexed markers and response python back-end expects. This "translation" has to be done manually if working with raw marker strings.

Reading Markers

Though the python back end will provide a number of meaningful responses, in most cases we only care about predictions from the classifier. Two levels of a stream reader class are provided at different levels of utility. In most cases, the LSLResponseProvider should be the most helpful. Adding this component to a Unity scene will allow you to subscribe to response callbacks by type, dynamically opening and polling an inlet, providing parsed responses. Incoming responses will be provided to all subscribers but are not otherwise be saved. This means that any incoming responses not relevant to any active subscriber will be discarded upon reception.

using BCIEssentials.LSLFramework;

LSLResponseProvider InStream = GetComponent<LSLResponseProvider>();

void OnPredictionReceived(LSLPredictionResponse prediction)
{
    Debug.Log($"Prediction received for object #{prediction.Value}");
}

InStream.SubscribePredictions(OnPredictionReceived);
// or
InStream.Subscribe<LSLPredictionResponse>(OnPredictionReceived);
// or
InStream.SubscribePredictions(prediction => {
    Debug.Log($"Prediction received for object #{prediction.Value}");
}
// or
InStream.SubscribeAll(response => {
    if (response is LSLPredictionResponse prediction) {
        Debug.Log($"Prediction received for object #{prediction.Value}");
    }
}
// or
InStream.Subscribe<SingleChannelLSLResponse>(response => {
    if (response is LSLPredictionResponse prediction) {
        Debug.Log($"Prediction received for object #{prediction.Value}");
    }
}

// then
InStream.UsubscribePredictions(OnPredictionReceived);
// The unsubscribe method does not need to match the subscribe helper used.
// Invalidated callbacks (from destroyed components) will be automatically unsubscribed.
// This enabled the safe use of lambdas regardless of component lifetimes,
// but they cannot be unsubscribed manually unless a reference is stored somewhere.

The basic LSLStreamReader can also be used to manually pull typed responses if desired.

Response Types

Type Description
LSLResponse Base class for all responses, indicates parsing failure if not a more specific type
EmptyLSLResponse LSL sample with no content
SingleChannelLSLResponse Base class for all expected responses, indicates parsing failure if not a more specific type
LSLPing "ping"
LSLPredictionResponse Prediction from the classifier indicating the selection of a specific object
LSLMarkerReceipt Confirmation of receipt for a command or event marker with inherited classes for each type of marker, indicates parsing failure if not a more specific type