Last active
November 15, 2019 23:51
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// 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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// 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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// 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