Mind Map for Emotiv EPOC + Processing/Arduino

I have made a mind map with MindMeister, which shows how to connect the Emotiv EPOC to Arduino and/or Processing for developing your own mind-controlled applications including interactive art, music, data visualizations, games, neurofeedback, and robot controllers. Link to map (in case embedded map does not work for you): http://www.mindmeister.com/214762396/emotiv-epoc-processing-arduino

Generative Sigils

Related articles: TAD2011.06 Lorenz Attractor | Processing + EPOC via OSC

In his book, Chaos in Wonderland: Visual Adventures in a Fractal World, Clifford Pickover describes methods for generating beautiful, complex images from certain chaotic equations. In the context of the book’s narrative, these images are the dreams of a species of inorganic, computer-like entities called the Latööcarfians — the “dream-weavers of Ganymede.” Here I consider using these images as algorithmically generated magical sigils (cf., generative art).

The images are generated by recursively plotting:

xt + 1 = sin(ytb) + c sin(xtb)
yt + 1 = sin(xta) + d sin(yta)

(There are variant equations that produce “mutations” — see “Appendix A: Mutations of Equations”, pp. 209–210.) Here is a sketch that will draw the following image in Processing:

/** Generative Sigil 1
 * Joshua Madara, hyperRitual.com
 * Based on code on pg. 26 of _Chaos in Wonderland_
 * by Clifford A. Pickover
 * Good ranges for a, b, c, and d:
 * (-3 < a, b < 3)
 * (0.5 < c, d < 1.5)
 */

float a = 1.5641136;
float b = 2.7102947;
float c = 0.9680385;
float d = 0.995141;
float x, y = 0.1;
int counter = 0;
int iterations = 500000;

void setup() {
  size(700,700,P2D); // remove P2D for Processing v2.0
  background(0); // black
  stroke(255,255,255,90); // white, semi-transparent
}

void draw() {
  println(counter);
  translate(width/2, height/2); // draw from center of window
  float xNew = sin(y*b) + c * (sin(x*b));
  float yNew = sin(x*a) + d * (sin(y*a));
  x = xNew; y = yNew;
  point(x*100, y*100);
  counter++;
  if(counter >= iterations) {
    println("done!");
    noLoop();
  }
}

Generative Sigil 1

I hypothesize that the key to using such images successfully as magical sigils is to assign non-trivial values to the inputs, a, b, c, and d. E.g., one could randomly generate the values at an auspicious moment, or acquire values from some act or object, and map those to the optimal ranges for the algorithm’s inputs. The magician could wear the Emotiv EPOC during a magical ritual and at the ritual’s apex a Processing sketch could map data from the EPOC to the a, b, c, and d values for generating the image. The images could subsequently be used for divination or evocation.

N.b., even while keeping the input values within optimal ranges, not all sets of values produce interesting images. Here is a Processing function to calculate the set’s Lyapunov exponent (based on the code on p. 62 of Chaos in Wonderland) — values >= 0.5 tend to be more interesting:

float calcLyapunovExponent(float a, float b, float c, float d) {
  float Lsum = 0;
  float n = 0;
  float x = 0.1;
  float y = 0.1;
  float  xe = x + 0.000001;
  float ye = y;
  float xx, yy, xsave, ysave, dLx, dLy, dL2, df, rs, L = 0;
  float bigNumber = 2139095039; /* Pickover's algorithm calls 
     for a long int (1000000000000) here, but I often get NaN returned 
     when using it in Processing, and I have found that using a 
     large float returns a value close enough to Pickover's to 
     be useful. */
    
  for(int i=0; i<10000000; i++) {
    xx = sin(y*b) + c*sin(x*b); yy = sin(x*a) + d*sin(y*a);
    xsave = xx; ysave = yy; x = xe; y = ye; n++;
    xx = sin(y*b) + c*sin(x*b); yy = sin(x*a) + d*sin(y*a);
    dLx = xx - xsave; dLy = yy - ysave; dL2 = dLx*dLx + dLy*dLy;
    df = bigNumber*dL2; rs = 1/sqrt(df);
    xe = xsave + rs*(xx - xsave); ye = ysave + rs*(yy - ysave);
    xx = xsave; yy = ysave; Lsum = Lsum + log(df); L = 0.721347*Lsum/n;
    x = xx; y = yy;
  }
  return L;
}

Mind-Controlled Robots @ Seattle Mini Maker Faire

For those who attended mine and my daughter Chloe’s presentation on mind-controlled robots, today, here are some details about the tech we used.

Magabot Arduino+laptop-based robot: http://magabot.cc/

Emotiv EPOC neuroheadset: http://emotiv.com/

Mind Your OSCs: http://sourceforge.net/projects/mindyouroscs/

oscP5 library for Processing: http://www.sojamo.de/libraries/oscP5/

Here is the Processing code I used to read the OSC data from the EPOC and send serial signals to the Arduino:

/**
 * NeuroMagabot
 * by Joshua Madara, hyperritual.com
 *
 * Transforms data from the Emotiv EPOC neuroheadset
 * to control data for the Magabot, via OSC.
 */

import processing.serial.*;
import oscP5.*;
import netP5.*;

Serial port;
OscP5 oscP5;

float thresh = 0.25; // one threshold for all Cognitiv values

void setup() {
  size(200, 200);
  println(Serial.list());
  port = new Serial(this, Serial.list()[0], 9600);
  
  //start oscP5, listening for incoming messages at port 7400
  //make sure this matches the port in Mind Your OSCs
  oscP5 = new OscP5(this, 7400);
  
  // plug the messages for the Cognitiv values
  oscP5.plug(this,"sendW","/COG/PUSH");
  oscP5.plug(this,"sendS","/COG/PULL");
  oscP5.plug(this,"sendA","/COG/LEFT");
  oscP5.plug(this,"sendD","/COG/RIGHT");
}

void draw() {
  // here you could graph the EPOC data, draw an animated face on the laptop screen, etc.
}

public void sendW(float val) {
  if (val >= thresh) {
    port.write('w'); // send move-forward command to Arduino
  } else {
    port.write('p'); // send stop command
  }
}

public void sendS(float val) {
  if (val >= thresh) {
    port.write('s'); // send move-reverse command to Arduino
  } else {
    port.write('p');
  }
}

public void sendA(float val) {
  if (val >= thresh) {
    port.write('a'); // send turn-left command to Arduino
  } else {
    port.write('p');
  }
}

public void sendD(float val) {
  if (val >= thresh) {
    port.write('d'); // send turn-right command to Arduino
  } else {
    port.write('p');
  }
}

The Arduino code is the Magabot_SerialControl sketch for Magabot (protocol = ‘H’).

TAD2011.02 Emotiv EPOC Video Recorder

Second TAD project (with video). [2013.12.09: the TAD website has been offline for a while now]

Emotiv EPOC Video Recorder

Here is the source code:

/**
 * EPOC Recorder 1
 * by Joshua Madara, hyperRitual.com
 * This sketch records graphical data from the Emotiv 
 * EPOC via OSC messages, as a .mov video file.
 */

import oscP5.*;
import processing.video.*;

// declare objects
OscP5 oscP5;
MovieMaker mm;
Format formatter;
PFont miniMono;
Date time;

int barColor = (color(255,0,0));

void setup() {
  size(320, 240);
  background(0);
  rectMode(CORNERS);
  
  // listen for OSC messages on port 7400
  oscP5 = new OscP5(this, 7400);
  
  // plug the messages from COG/PUSH to function makeFrame()
  oscP5.plug(this,"makeFrame","/COG/PUSH");
  
  // load MiniMono font
  miniMono = loadFont("MiniMono.vlw");
  textFont(miniMono);
  
  // format timestamp
  formatter = new SimpleDateFormat("HH:mm:ss:SSS");
  
  // Create MovieMaker object with size, filename,
  // framerate, compression codec and quality
  mm = new MovieMaker(this, width, height, "epoc_record.mov",
                       30, MovieMaker.H263, MovieMaker.LOSSLESS);
}

void draw() {
  // not used
}

// for each new event, add a frame to the video
void makeFrame(float pushValue) {
  background(0); // clear screen
  
  // update and draw timestamp and COG/PUSH value
  /* note that the time here is the local time when the 
  event is recorded, not a count-up timer that initiates 
  when recording begins, although that could be implemented */
  time = new Date();
  String timestamp = formatter.format(time);
  fill(255);
  text("Time: "+timestamp+"  Value: "+pushValue, 5, 10);
  
  // draw horizontal lines
  for (int i = 0; i <= 10; i++) {
    stroke(map(i, 0, 10, 255, 0));
    float yPos = map(i, 0, 10, 20, 220);
    line(100, yPos, 220, yPos);
  }
  
  // draw bar
  float barHeight = map(pushValue, 0, 1, 220, 20);
  noStroke();
  fill(barColor, 100);
  rect(width/2 - 20, 220, width/2 + 20, barHeight);
  
  // write pixels to video frame
  mm.addFrame();
}

// press space bar to finish the video
void keyPressed() {
  if (key == ' ') {
    mm.finish();
  }
}