Sending data From Processing to Max using MaxLink

judd127a's icon

Hi guys,

I am at a bit of a loss as to how to send a float from processing to max, i have read the various but limited instructions that i have found online but have not offered much help.

I have built an EEG from Instructables, and it came with a processing sketch which separates the data into the different brainwave categories (alpha, beta etc...). All i want to do is be able to send the data of these six bands into six different flonums in Max so i can process them. Does anyone have any ideas?

I assume it will be to enter "link.declareInlet("xPosition");" in the part of the code where it says "//Draw un-averaged frequency bands of signal."

But i am new to processing and fairly new to Max and cant work out where i would put it and how i would get Max to recognise it using Maxlink.

I know im probably asking a lot, but i will be grateful for any type of feedback.

Thanks

judd127a's icon

Here is the processing file.

/* Reading and Visualizing EEG Data
* by Christian Henry.

* Reads in EEG data through the microphone input of the
* computer, then displays the signal in time, its frequency
* components, and then averages of frequencies that estimate
* the concentration of different brain waves.
*
* For reference, the frequency bars are ordered/classified as
* follows:
*
* 1 - blue -------- delta
* 2 - blue/purple - theta
* 3 - purple ------ alpha
* 4 - purple/red -- low beta
* 5 - dark red ---- mid beta
* 6 - red --------- high beta
*
* This sketch will measure all brain waves, from 0 - 30 Hz. It does
* best, however, measuring alpha waves across the occipital lobe.
* To view this function, play the program, click the window to
* make sure its in "focus", and hit the "a" key to bandpass the alpha
* waves only. The alpha wave bar is the 3rd one (purple), and should
* increase in height by 2-3x when you close your eyes and relax
* (you'll see it for a second or two after you open your eyes, before it
* averages back down).
* /

/* One issue: when taking the FFT of the data, it seems as if
the frequency bands have a bandwidth of 1.33 instead of 1, as
60Hz noise peaks out at band 45. This is worked around by using
the scaleFreq parameter, which is used frequently. */

import ddf.minim.*;
import ddf.minim.signals.*;
import ddf.minim.analysis.*;
import ddf.minim.effects.*;

//Important constants that may need to be changed.
float timeScale = 50; //scales the amplitude of time-domain data, can be changed
static float normalScale = 50;
static float alphaScale = 100;
static int freqAvgScale = 50; //does same for averages of frequency data
static int alphaCenter = 12;
static int alphaBandwidth = 2; //really bandwidth divided by 2
static int betaCenter = 24;
static int betaBandwidth = 2;

//Variables used to store data functions/effects.
Minim minim;
AudioInput in;
FFT fft;
NotchFilter notch;
LowPassSP lpSP;
LowPassFS lpFS;
BandPass betaFilter;
BandPass alphaFilter;

//Constants mainly used for scaling the data to readable sizes.
int windowLength = 840;
int windowHeight = 500;
int FFTheight;
float scaling[] = {.00202,.002449/2,.0075502/2,.00589,.008864,.01777};
float offset[] = {0,0,0,0,0,0};
float amplify[] = {1,1,1,1,1,1};
float maximum[] = {0,0,0,0,0,0};
float scaledMaximum[] = {0,0,0,0,0,0};
int FFTrectWidth = 6;
float scaleFreq = 1.33f;
float timeDomainAverage = 0;

//Variables used to handle bada
int cutoffHeight = 200; //frequency height to throw out "bad data" for averaging after
float absoluteCutoff = 1.5;
boolean absoluteBadDataFlag; //data that is bad because it's way too far out of our desired range --
// ex: shaking your head for a second
boolean averageBadDataFlag; //data that's bad because it spikes too far outside of the average for
//that second --
// ex: blinking your eyes for a split second

//Constants used to create a running average of the data.
float[][] averages;
int averageLength = 200; //averages about the last 5 seconds worth of data
int averageBins = 6; //we have 6 types of brain waves
int counter = 0;

void setup()
{
//initialize array of averages for running average calculation
averages = new float[averageBins][averageLength];
for (int i = 0; i < averageBins; i++){
for (int j = 0; j < averageLength; j++){
averages[i][j] = 0;
}
}

//set some drawing parameters
windowLength = 840;
windowHeight = 500;
FFTheight = windowHeight - 200;

size(windowLength, windowHeight, P2D);

//initialize minim, as well as some filters
minim = new Minim(this);
minim.debugOn();
notch = new NotchFilter(60, 10, 32768);
lpSP = new LowPassSP(40, 32768);
lpFS = new LowPassFS(60, 32768);
betaFilter = new BandPass(betaCenter/scaleFreq,betaBandwidth/scaleFreq,32768);
alphaFilter = new BandPass(alphaCenter/scaleFreq,alphaBandwidth/scaleFreq,32768);

// get a line in from Minim, default bit depth is 16
in = minim.getLineIn(Minim.MONO, 8192*4);
in.addEffect(notch);

//initialize FFT
fft = new FFT(in.bufferSize(), in.bufferSize());
fft.window(FFT.HAMMING);
rectMode(CORNERS);
println(fft.getBandWidth());
}

void draw()
{
/*badDataFlag handles any "artifacts" we may pick up while recording the data.
Artifacts are essentially imperfections in the data recording -- they can come
from muscle movements, blinking, anything that disturbs the electrodes. If the
program encounters a set of data that spikes out of a reasonable window
(controlled by the variable cutoffHeight), it won't consider that data
when computing the running average.
*/
absoluteBadDataFlag = false;
averageBadDataFlag = false;

fft.forward(in.mix); //compute FFT
background(0); //make sure the background color is black
stroke(255); //and that time data is drawn in white

line(0,100,windowLength,100); //line separating time and frequency data

drawSignalData();

//check for spikes relative to other data
for (int i = 0; i < windowLength - 1; i++){
if (abs(in.left.get((i+1)*round(in.bufferSize()/windowLength))) > timeDomainAverage*4)
averageBadDataFlag = true;
}

displayText();

displayFreqAverages();

counter++;
}

//Calls function to zoom into average bars when you right click on the average bars.
//Scales depending on position of click.
void mousePressed(){
if (mouseButton == RIGHT && mouseY > FFTheight){
scaleAverage(floor(mouseX/(windowLength/averageBins)), windowHeight - mouseY);
}
}

//Zooms into average bars to show small fluctuations better. New range (from original) is
//from position clicked on to a maximum height such that the previously logged maximum fills 75% of the
//current bar.
void scaleAverage(int bin, float pos){
offset[bin] += pos/amplify[bin];
println(offset[bin]);
amplify[bin] *= 200/(1.33f*(scaledMaximum[bin] - pos));
println(amplify[bin]);
maximum[bin] = 0;
scaledMaximum[bin] = 0;
}

//Hitting number keys will reset that bin of averages, hitting "`" will wipe them all.
void keyPressed(){
if (key == '1' || key == '2' || key == '3' || key == '4' || key == '5' || key == '6'){
char data[] = {key};
String str1 = new String(data);
int keyNum = Integer.parseInt(str1);
keyNum -= 1; //shift down 1 since array is zero-indexed
offset[keyNum] = 0;
amplify[keyNum] *= 1;
maximum[keyNum] = 0;
scaledMaximum[keyNum] = 0;
}

if (key == '`'){
for (int i = 0; i < offset.length; i++){
offset[i] = 0;
amplify[i] = 1;
maximum[i] = 0;
scaledMaximum[i] = 0;
}
}
if (key == 'w'){
fft.window(FFT.HAMMING);
}
if (key == 'e'){
fft.window(FFT.NONE);
}
if (key == 'a'){
toggleEffect(alphaFilter);
}
if (key == 'b'){
toggleEffect(betaFilter);
}
}

//Toggle effects such as notch or lowpass filter, not being used at
//the moment, though.
void toggleEffect(AudioEffect effect){
if(in.hasEffect(effect)){
in.removeEffect(effect);
timeScale = normalScale;
}
else{
in.addEffect(effect);
timeScale = alphaScale;
}
}

//Draw the signal in time and frequency.
void drawSignalData(){
timeDomainAverage = 0;
for(int i = 0; i < windowLength - 1; i++)
{
stroke(255,255,255);
//data that fills our window is normalized to +-1, so we want to throw out
//sets that have data that exceed this by the factor absoluteCutoff
if (abs(in.left.get(i*round(in.bufferSize()/windowLength)))*timeScale/normalScale > .95){
absoluteBadDataFlag = true;
fill(250,250,250);
stroke(150,150,150);
}
//Draw the time domain signal.
line(i, 50 + in.left.get(i*round(in.bufferSize()/windowLength))*timeScale,
i+1, 50 + in.left.get((i+1)*round(in.bufferSize()/windowLength))*timeScale);

timeDomainAverage += abs(in.left.get(i*round(in.bufferSize()/windowLength)));

//Draw un-averaged frequency bands of signal.
if (i < (windowLength - 1)/2){
//set colors for each type of brain wave
if (i
fill(0,0,250); //delta
stroke(25,0,225);
}
if (i >= round(4/scaleFreq) && i
fill(50,0,200); //theta
stroke(75,0,175);
}
if (i >= round((alphaCenter - alphaBandwidth)/scaleFreq) &&
i
fill(100,0,150); //alpha
stroke(125,0,125);
}
if (i >= round((alphaCenter + alphaBandwidth)/scaleFreq)+1 &&
i
fill(150,0,100); //low beta
stroke(175,0,75);
}
if (i >= round((betaCenter - betaBandwidth)/scaleFreq) &&
i
fill(200,0,50); //midrange beta
stroke(225,0,25);
}
if (i >= round((betaCenter + betaBandwidth)/scaleFreq)+1 && i
fill(250,0,0); //high beta
stroke(255,0,10);
}
if (i >= round(32/scaleFreq)){
fill(240,240,240); //rest of stuff, mainly noise
stroke(200,200,200);
}
if (i == round(60/scaleFreq)){
fill(200,200,200); //color 60 Hz a different tone of grey,
stroke(150,150,150); //to see how much noise is in data
}
//draw the actual frequency bars
rect(FFTrectWidth*i, FFTheight, FFTrectWidth*(i+1), FFTheight - fft.getBand(i)/10);
}
}
//divide the average by how many time points we have
timeDomainAverage = timeDomainAverage / (windowLength - 1);
}

//Give user textual information on data being thrown out and filter's we have active.
void displayText(){
//show user when data is being thrown out
text("absoluteBadDataFlag = " + absoluteBadDataFlag, windowLength - 200, 120);
if (absoluteBadDataFlag == true)
{
println("absoluteBadDataFlag = " + absoluteBadDataFlag);
println(counter);
}
text("averageBadDataFlag = " + averageBadDataFlag, windowLength - 200, 140);
if (averageBadDataFlag == true)
{
println("averageBadDataFlag = " + averageBadDataFlag);
println(counter);
}

//and when a filter is being applied to the data
text("alpha filter is " + in.hasEffect(alphaFilter),
windowLength - 200, 160);
text("beta filter is " + in.hasEffect(betaFilter),
windowLength - 200, 180);
}

//Compute and display averages for each brain wave for the past ~5 seconds.
void displayFreqAverages(){
//show averages of alpha, beta, etc. waves
for (int i = 0; i < 6; i++){
float avg = 0; //raw data for amplitude of section of frequency
int lowFreq = 0;
int hiFreq = 0;

//Set custom frequency ranges to be averaged.
if(i == 0){
lowFreq = 0;
hiFreq = 3;
fill(0,0,250);
stroke(25,0,225);
}
if(i == 1){
lowFreq = 3;
hiFreq = 7;
fill(50,0,200);
stroke(75,0,175);
}
if(i == 2){
lowFreq = alphaCenter - alphaBandwidth;
hiFreq = alphaCenter + alphaBandwidth;
fill(100,0,150);
stroke(125,0,125);
}
if(i == 3){
lowFreq = 12;
hiFreq = 15;
fill(150,0,100);
stroke(175,0,75);
}
if(i == 4){
lowFreq = betaCenter - betaBandwidth;
hiFreq = betaCenter + betaBandwidth;
fill(200,0,50);
stroke(225,0,25);
}
if(i == 5){
lowFreq = 20;
hiFreq = 30;
fill(250,0,0);
stroke(255,0,10);
}

//Convert frequencies we want to the actual FFT bands. Because of our
//FFT parameters, these happen to be equal (each band has a 1 Hz width).
int lowBound = fft.freqToIndex(lowFreq);
int hiBound = fft.freqToIndex(hiFreq);

//Scale the band number, because of the issue outlined at very beginning of
//program.
lowBound = round(lowBound/scaleFreq);
hiBound = round(hiBound/scaleFreq);

//get average for frequencies in range
for (int j = lowBound; j
avg += fft.getBand(j);
}
avg /= (hiBound - lowBound + 1);

// Scale the bars so that it fits our window a little better.
for (int k = 0; k < 6; k++)
{
if (i == k)
{
avg *= scaling[i]*freqAvgScale;
}
}

//update our array for the moving average (only if our data is "good")
if (absoluteBadDataFlag == false && averageBadDataFlag == false){
averages[i][counter%averageLength] = avg;
}

//calculate the running average for each frequency range
float sum = 0;
for (int k = 0; k < averageLength; k++){
sum += averages[i][k];
}
sum = sum / averageLength;

//draw averaged/smoothed frequency ranges
rect(i*width/6, height, (i+1)*width/6, height - (sum-offset[i])*amplify[i]);
}
}

// always close Minim audio classes when you are done with them
void stop()
{
in.close();
minim.stop();
super.stop();
}

mzed's icon

I realize this might be the wrong kind of answer, but I've done all of my communication between Processing and Max using oscP5. This library:

I was trying to find some documentation for Maxlink, but all of the web stuff looked very old. Can you make Maxlink work with the examples that come with the library? Is it just floats that are your problem, or the any data at all?

judd127a's icon

At the moment im struggling getting to grips with the code in processing. However, i'm going to spend some time on the OSCP5 and see if that gives me better results, its certainly got a lot more useful documentation than MaxLink... Thank you!

michaelpromeo's icon

I'd also say the best way is to send OSC messages from program to program. I've done it with openFrameworks to Max, which I've included a Max file below. There is a library for OSC for processing here.
Also, another thing I just learned from a friend, if you are going to send data between two computers just link them together with a cat5 cable. If you run it over a wireless network you'll get lag.

Hope that helps!

osc.maxpat
Max Patch
judd127a's icon

I am still having trouble unfortunately, the example patch works fine, but the issue i am having is that im not too sure where i would find the output of the frequency and amplitude of the signal to send as a number into max. I have tried adding the code in the link above, but processing wants to remove a lot of the void objects and draw objects before it runs. does anyone know how to find this?