Skip to content

Instantly share code, notes, and snippets.

@neurochems
Last active November 15, 2019 23:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save neurochems/ccb9bbd6d50b8b29929416400cf6e3a4 to your computer and use it in GitHub Desktop.
Save neurochems/ccb9bbd6d50b8b29929416400cf6e3a4 to your computer and use it in GitHub Desktop.
Introduction to Unity + Biosignals workshop given at InterAccess July 17 + 23 2019. Help from @netgrind.
/// ANimateMuse
/// A script to allow properties of a GameObject's components to be manipulated by the activation scores.
/// Change the component object to effect or add others
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AnimateMuse : MonoBehaviour
{
// selector for activation score type
public MuseData.MuseDataType type;
// component object to effect
public Transform output; // change me
// add more here
// smoothing options
public AnimationCurve curve;
public Vector2 minmax = Vector2.up;
void Update()
{
// update current activation score value
float value;
if (type == MuseData.MuseDataType.FOCUS)
{
value = MuseData.instance.focus;
}
else if (type == MuseData.MuseDataType.AWARENESS)
{
value = MuseData.instance.awareness;
}
else
{
value = MuseData.instance.meaning;
}
// apply to component object
output.localScale = new Vector3(Mathf.Lerp(minmax.x, minmax.y, curve.Evaluate(value)), output.localScale.y, output.localScale.z);
// add more changes here
}
}
/// FirstPersonController
/// 2019 Brendan Lehman
/// A simple script to create a first-person player in Unity
/// ** Make the Main Camera a child of the Player object
/// ** Lock the X and Z rotation of the Player object's Rigidbody, under Constraints
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FirstPersonController : MonoBehaviour
{
[Header("settings")]
public float mouseSensitivityX = 1;
public float mouseSensitivityY = 1;
public float walkSpeed = 6;
public float jumpForce = 220;
public LayerMask groundedMask;
[Header("states")]
public bool grounded, jumped;
private Vector3 moveAmount;
private Vector3 smoothMoveVelocity;
private Rigidbody rb;
private Transform cameraTransform;
private float verticalLookRotation;
void Awake()
{
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
cameraTransform = Camera.main.transform;
rb = GetComponent<Rigidbody>();
}
void Update()
{
// Look rotation [MOUSE] \\
// rotate player on input mouse x
transform.Rotate(Vector3.up * Input.GetAxis("Mouse X") * mouseSensitivityX);
// rotate camera on input mouse y
verticalLookRotation += Input.GetAxis("Mouse Y") * mouseSensitivityY;
verticalLookRotation = Mathf.Clamp(verticalLookRotation, -60, 60);
cameraTransform.localEulerAngles = Vector3.left * verticalLookRotation;
// Calculate movement [KEYBOARD] \\
float inputX = Input.GetAxisRaw("Horizontal");
float inputY = Input.GetAxisRaw("Vertical");
Vector3 moveDir = new Vector3(inputX, 0, inputY).normalized;
Vector3 targetMoveAmount = moveDir * walkSpeed;
moveAmount = Vector3.SmoothDamp(moveAmount, targetMoveAmount, ref smoothMoveVelocity, .15f);
// Grounded check
Ray ray = new Ray(transform.position, -transform.up);
if (Physics.Raycast(ray, out RaycastHit hit, 1 + .1f, groundedMask))
{
grounded = true;
}
else
{
grounded = false;
}
// Jump \\
if (Input.GetButtonDown("Jump"))
{
if (grounded)
{
jumped = true;
}
}
}
void FixedUpdate()
{
// Apply movement to rigidbody
Vector3 localMove = transform.TransformDirection(moveAmount) * Time.fixedDeltaTime;
rb.MovePosition(rb.position + localMove);
if (jumped) rb.AddForce(transform.up * jumpForce);
}
}
/// MuseData
/// 2019 Brendan Lehman
/// A script to receive OSC streams of data from MuseLab, parse the stream contents,
/// and use the contents to compute baseline and activation scores.
///
using System.Collections.Generic;
using UnityEngine;
using UnityOSC;
public class MuseData : MonoBehaviour
{
// public access to this class
public static MuseData instance;
// osc variables and objects
[Header("osc bits")]
public int port = 8000; // osc port
private OSCReciever reciever; // osc reciever
// data matrices
public float[,] dataAbsolute = new float[4, 2]; // absolute eeg data: channel x band
public float[,] baselineAbsolute = new float[4, 2]; // absolute baseline eeg data: channel x band
// addresses
private string addressAlphaA = "/muse/elements/alpha_absolute"; // ffff
private string addressBetaA = "/muse/elements/beta_absolute"; // ffff
// baseline variables
[Header("baseline")]
public float baselineDuration = 10.0f;
public bool collectingBaseline;
private float[,] sumAbsolute = new float[4, 2];
private int samples = 0;
private float baselineTimer = 0f;
// enum for selecting score type in AnimateMuse
public enum MuseDataType
{
FOCUS,
AWARENESS,
MEANING
}
// activation scores
[Header("activation scores")]
public float focus = 0; // sustained frontal alpha that is higher than baseline (discrete value)
public float awareness = 0; // sustained frontal beta that is higher than baseline (discrete value)
public float meaning = 0; // sustained frontal alpha and frontal beta each higher than their baseline (discrete value; correlates w/ anterior cingulate))
// activation score change settings
[Header("sensitivity")]
public float smoothingIn = 0.05f;
public float smoothingOut = 0.5f;
private void Awake()
{
instance = this; // reference to this class for AnimateMuse
}
void Start()
{
reciever = new OSCReciever(); // init reciever
reciever.Open(port); // open reciever
collectingBaseline = false; // move to some point triggered in the menu
}
void Update()
{
// if there is a message
if (reciever.hasWaitingMessages())
{
//Debug.Log("has message");
// get message
OSCMessage msg = reciever.getNextMessage();
// read message
ParseOSCData(msg.Data, msg.Address);
// baseline
if (collectingBaseline) // if collecting baseline
{
baselineTimer += Time.deltaTime; // start timer
CollectBaseline(); // collect baseline
if (baselineTimer >= baselineDuration) // if timer = duration
{
collectingBaseline = false; // reset flag
baselineTimer = 0f; // reset timer
}
}
}
// analysis
if (!collectingBaseline) AnalyzeData();
}
/// load osc stream data into float array \\\
private void ParseOSCData(List<object> data, string address)
{
float dataF = 0.0f; // float data buffer
int j = 0;
// for every item in osc list
for (int i = 0; i < data.Count; i++)
{
// parse float
dataF = float.Parse(data[i].ToString());
// reset channel counter
if (j > 3) j = 0;
// store alpha data
if (address == addressAlphaA)
{
dataAbsolute[j, 0] = dataF;
j++;
}
// store beta data
else if (address == addressBetaA)
{
dataAbsolute[j, 1] = dataF;
j++;
}
}
}
/// <summary>
/// baseline calculation
/// average of each channel and band over baseline duration
/// </summary>
private void CollectBaseline()
{
samples++;
// sum
for (int i = 0; i < 4; i++) // channel
{
for (int j = 0; j < 2; j++) // band
{
sumAbsolute[i, j] += dataAbsolute[i, j];
//sumRelative[i, j] += dataRelative[i, j];
}
}
// division
for (int i = 0; i < 4; i++) // channel
{
for (int j = 0; j < 2; j++) // band
{
baselineAbsolute[i, j] = sumAbsolute[i, j] / samples;
//baselineRelative[i, j] = sumRelative[i, j] / samples;
}
}
}
/// <summary>
/// compute activation scores
/// </summary>
private void AnalyzeData()
{
// average alpha of both frontal electrodes: session data
float frontalAlphaAbsolute = (dataAbsolute[2, 0] + dataAbsolute[3, 0]) / 2;
// average alpha of both frontal electrodes: baseline data
float frontalAlphaAbsoluteBaseline = (baselineAbsolute[2, 0] + baselineAbsolute[3, 0]) / 2;
// baseline-corrected alpha of both frontal electrodes
float frontalAlphaCorrected = frontalAlphaAbsolute - frontalAlphaAbsoluteBaseline;
// average beta of both frontal electrodes: session data
float frontalBetaAbsolute = (dataAbsolute[2, 1] + dataAbsolute[3, 1]) / 2;
// average beta of both frontal electrodes: baseline data
float frontalBetaAbsoluteBaseline = (baselineAbsolute[2, 1] + baselineAbsolute[3, 1]) / 2;
// baseline-corrected beta of both frontal electrodes
float frontalBetaCorrected = frontalBetaAbsolute - frontalBetaAbsoluteBaseline;
// compute focus score
if (frontalAlphaCorrected > focus) focus = Mathf.Lerp(focus, frontalAlphaCorrected, smoothingIn);
else focus = Mathf.Lerp(focus, frontalAlphaCorrected, smoothingOut);
// compute awareness score
if (frontalBetaCorrected > awareness) awareness = Mathf.Lerp(focus, frontalBetaCorrected, smoothingIn);
else awareness = Mathf.Lerp(focus, frontalBetaCorrected, smoothingOut);
// compute meaning score (reward multi-state coherences)
if (focus > .5 && awareness > .5) meaning = Mathf.Lerp(meaning, 1, smoothingIn);
else meaning = Mathf.Lerp(meaning, 0, smoothingIn);
// optional normalization
//focus = Mathf.Clamp(focus, 0, 1);
//awareness = Mathf.Clamp(awareness, 0, 1);
//meaning = Mathf.Clamp(meaning, 0, 1);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment