Drw wave with serial data from Ardino, analyse with Max/msp

chrislaw's icon

I aim to measure heart rates through the arduino using an infra red led and a IR phototransistor to detect the pulse in my finger. This creates a data list in the serial window. From there i need to transform the data into a visually identifiable wave form using processing, send the wave to Max/ Msp which can analyse the waves for peaks and troughs, and then control the speed of a captured sound file of waves crashing against the beach using the frequency of the heart beat. There is 10 days to do this. I am sort of hoping that its not quite as difficult as it sounds. I have discovered pachube. Any help or advice would be gold dust, thanks everyone.

brendan mccloskey's icon

There's some example code and a patch here, relating to graphing analog input to Processing and Max:
http://arduino.cc/en/Tutorial/Graph

If you're happy with this, then look at [peak] and [trough] and related objects

Brendan

chrislaw's icon
chrislaw's icon

thanks very much Brendan, Ill check that out now.

Heres a few posts I have found to help me with the processing/ arduino side of things.

i've also been looking at jitter tutorial 28 which looks at tracking msp sound waves amplitudes. Hopefully i'm going down the right track with this

llumen's icon

nice project, it is more or less exactly what I did, although I used synthesis to create the waves instead of a .wav and using vbap to spatialize the sound.
I'm not sure why you want to include processing and not take the whole thing into max though.
just ask if you need more help

chrislaw's icon

Hi llumen, most definitely could do with any help I can get.
This is the project sketch I am starting with for the arduino which records the heartbeat as serial data in the serial monitor;

int ledPin = 13;
int sensorPin = 0;

double alpha = 0.75;
int period = 20;
double change = 0.0;

void setup()
{
pinMode(ledPin, OUTPUT);
}

void loop()
{
static double oldValue = 0;
static double oldChange = 0;
int rawValue =
analogRead(sensorPin);
// smooths wave form using alpha value for level of smoothing
double value = alpha * oldValue
+ (1 - alpha) * rawValue;

digitalWrite(ledPin, (change < 0.0 && oldChange > 0.0));

oldValue = value;
oldChange = change;
delay(period);
}

1502.resized.jpg
jpg
chrislaw's icon

ill check out the details you have posted now and hopefully come up with something, thanks

llumen's icon

what I suggest is that you take a look at the icubeX website (http://infusionsystems.com/catalog/), they have heartbeat sensors that fit their system (but they fit arduino's too). In any case are they normal adc sensors with a 0-1024 scale.

They have maxpatches http://infusionsystems.com/catalog/product_info.php/products_id/197 (look at software) that readout the full signal and turn it into BPM etc (very handy)

also, why don't you use firmata on the arduino http://www.arduino.cc/playground/Interfacing/Firmata and maxuino http://www.maxuino.org/

all of this will make sure you can work on the fun stuff instead of the tedious stuff

chrislaw's icon

Hi. the heart rate monitor is a little out of my budget. Its good to struggle though as its an educational project. I have previously installed the maxuino library but im really sweating it trying to see the bigger picture of how to bring things together. My heart rate monitor does scale between 0- 1024 though. My previous patch above was the wrong one, here is the one im using to collect serial data.

int ledPin = 13;
int sensorPin = 0;

double alpha = 0.75;
int period = 20;
double change = 0.0;

void setup()
{
pinMode(ledPin, OUTPUT);
Serial.begin(115200);
}

void loop()
{
static double oldValue = 0;
static double oldChange = 0;
int rawValue =
analogRead(sensorPin);
double value = alpha * oldValue
+ (1 - alpha) * rawValue;

Serial.print(rawValue);
Serial.print(",");
Serial.println(value);

oldValue = value;
delay(period);
}

Brendans link above has a viable option although I have not got it working with my sketch yet.

max

{
"boxes" : [ {
"box" : {
"maxclass" : "comment",
"text" : "GraphnnThis patch takes a string, containing ASCII formatted number from 0 to 1023, with a carriage return and linefeed at the end. It converts the string to an integer and graphs it.nncreated 2006nby David A. Mellisnmodified 14 Apr 2009nby Scott Fitzgerald and Tom Igoe",
"linecount" : 10,
"patching_rect" : [ 479.0, 6.0, 344.0, 144.0 ],
"numoutlets" : 0,
"fontsize" : 12.0,
"id" : "obj-32",
"fontname" : "Arial",
"numinlets" : 1
}

}
, {
"box" : {
"maxclass" : "newobj",
"text" : "select 0 1",
"patching_rect" : [ 327.0, 80.0, 62.0, 20.0 ],
"numoutlets" : 3,
"fontsize" : 12.0,
"outlettype" : [ "bang", "bang", "" ],
"id" : "obj-30",
"fontname" : "Arial",
"numinlets" : 1
}

}
, {
"box" : {
"maxclass" : "comment",
"text" : "click here to close the serial port",
"patching_rect" : [ 412.0, 231.0, 206.0, 20.0 ],
"numoutlets" : 0,
"fontsize" : 12.0,
"id" : "obj-26",
"fontname" : "Arial",
"numinlets" : 1
}

}
, {
"box" : {
"maxclass" : "comment",
"text" : "click here to open the serial port",
"patching_rect" : [ 412.0, 205.0, 206.0, 20.0 ],
"numoutlets" : 0,
"fontsize" : 12.0,
"id" : "obj-27",
"fontname" : "Arial",
"numinlets" : 1
}

}
, {
"box" : {
"maxclass" : "message",
"text" : "close",
"patching_rect" : [ 327.0, 231.0, 39.0, 18.0 ],
"numoutlets" : 1,
"fontsize" : 12.0,
"outlettype" : [ "" ],
"id" : "obj-21",
"fontname" : "Arial",
"numinlets" : 2
}

}
, {
"box" : {
"maxclass" : "message",
"text" : "port a",
"patching_rect" : [ 349.0, 205.0, 41.0, 18.0 ],
"numoutlets" : 1,
"fontsize" : 12.0,
"outlettype" : [ "" ],
"id" : "obj-19",
"fontname" : "Arial",
"numinlets" : 2
}

}
, {
"box" : {
"maxclass" : "multislider",
"candicane7" : [ 0.878431, 0.243137, 0.145098, 1.0 ],
"patching_rect" : [ 302.0, 450.0, 246.0, 167.0 ],
"contdata" : 1,
"numoutlets" : 2,
"peakcolor" : [ 0.498039, 0.498039, 0.498039, 1.0 ],
"slidercolor" : [ 0.066667, 0.058824, 0.776471, 1.0 ],
"candicane8" : [ 0.027451, 0.447059, 0.501961, 1.0 ],
"outlettype" : [ "", "" ],
"setminmax" : [ 0.0, 1023.0 ],
"settype" : 0,
"candicane6" : [ 0.733333, 0.035294, 0.788235, 1.0 ],
"setstyle" : 3,
"bgcolor" : [ 0.231373, 0.713726, 1.0, 1.0 ],
"id" : "obj-1",
"candicane4" : [ 0.439216, 0.619608, 0.070588, 1.0 ],
"candicane5" : [ 0.584314, 0.827451, 0.431373, 1.0 ],
"candicane2" : [ 0.145098, 0.203922, 0.356863, 1.0 ],
"candicane3" : [ 0.290196, 0.411765, 0.713726, 1.0 ],
"numinlets" : 1
}

}
, {
"box" : {
"maxclass" : "comment",
"text" : "Click here to get a list of serial ports",
"patching_rect" : [ 412.0, 179.0, 207.0, 20.0 ],
"numoutlets" : 0,
"fontsize" : 12.0,
"id" : "obj-2",
"fontname" : "Arial",
"numinlets" : 1
}

}
, {
"box" : {
"maxclass" : "comment",
"text" : "Here's the number from Arduino's analog input",
"linecount" : 2,
"patching_rect" : [ 153.0, 409.0, 138.0, 34.0 ],
"numoutlets" : 0,
"fontsize" : 12.0,
"id" : "obj-3",
"fontname" : "Arial",
"numinlets" : 1
}

}
, {
"box" : {
"maxclass" : "comment",
"text" : "Convert ASCII to symbol",
"patching_rect" : [ 379.0, 378.0, 147.0, 20.0 ],
"numoutlets" : 0,
"fontsize" : 12.0,
"id" : "obj-4",
"fontname" : "Arial",
"numinlets" : 1
}

}
, {
"box" : {
"maxclass" : "comment",
"text" : "Convert integer to ASCII",
"patching_rect" : [ 379.0, 355.0, 147.0, 20.0 ],
"numoutlets" : 0,
"fontsize" : 12.0,
"id" : "obj-5",
"fontname" : "Arial",
"numinlets" : 1
}

}
, {
"box" : {
"maxclass" : "number",
"patching_rect" : [ 302.0, 414.0, 37.0, 20.0 ],
"numoutlets" : 2,
"fontsize" : 12.0,
"outlettype" : [ "int", "bang" ],
"bgcolor" : [ 0.866667, 0.866667, 0.866667, 1.0 ],
"id" : "obj-6",
"triscale" : 0.9,
"fontname" : "Arial",
"htextcolor" : [ 0.870588, 0.870588, 0.870588, 1.0 ],
"numinlets" : 1
}

}
, {
"box" : {
"maxclass" : "newobj",
"text" : "fromsymbol",
"patching_rect" : [ 302.0, 378.0, 74.0, 20.0 ],
"numoutlets" : 1,
"fontsize" : 12.0,
"outlettype" : [ "" ],
"id" : "obj-7",
"fontname" : "Arial",
"color" : [ 1.0, 0.890196, 0.090196, 1.0 ],
"numinlets" : 1
}

}
, {
"box" : {
"maxclass" : "newobj",
"text" : "itoa",
"patching_rect" : [ 302.0, 355.0, 46.0, 20.0 ],
"numoutlets" : 1,
"fontsize" : 12.0,
"outlettype" : [ "int" ],
"id" : "obj-8",
"fontname" : "Arial",
"color" : [ 1.0, 0.890196, 0.090196, 1.0 ],
"numinlets" : 3
}

}
, {
"box" : {
"maxclass" : "newobj",
"text" : "zl group 4",
"patching_rect" : [ 302.0, 332.0, 64.0, 20.0 ],
"numoutlets" : 2,
"fontsize" : 12.0,
"outlettype" : [ "", "" ],
"id" : "obj-9",
"fontname" : "Arial",
"numinlets" : 2
}

}
, {
"box" : {
"maxclass" : "newobj",
"text" : "select 10 13",
"patching_rect" : [ 244.0, 281.0, 77.0, 20.0 ],
"numoutlets" : 3,
"fontsize" : 12.0,
"outlettype" : [ "bang", "bang", "" ],
"id" : "obj-10",
"fontname" : "Arial",
"numinlets" : 1
}

}
, {
"box" : {
"maxclass" : "toggle",
"patching_rect" : [ 244.0, 43.0, 15.0, 15.0 ],
"numoutlets" : 1,
"outlettype" : [ "int" ],
"id" : "obj-11",
"numinlets" : 1
}

}
, {
"box" : {
"maxclass" : "newobj",
"text" : "qmetro 10",
"patching_rect" : [ 244.0, 80.0, 65.0, 20.0 ],
"numoutlets" : 1,
"fontsize" : 12.0,
"outlettype" : [ "bang" ],
"id" : "obj-12",
"fontname" : "Arial",
"numinlets" : 2
}

}
, {
"box" : {
"maxclass" : "message",
"text" : "print",
"patching_rect" : [ 369.0, 179.0, 36.0, 18.0 ],
"numoutlets" : 1,
"fontsize" : 12.0,
"outlettype" : [ "" ],
"id" : "obj-13",
"fontname" : "Arial",
"numinlets" : 2
}

}
, {
"box" : {
"maxclass" : "newobj",
"text" : "serial a 9600",
"patching_rect" : [ 244.0, 255.0, 84.0, 20.0 ],
"numoutlets" : 2,
"fontsize" : 12.0,
"outlettype" : [ "int", "" ],
"id" : "obj-14",
"fontname" : "Arial",
"numinlets" : 1
}

}
, {
"box" : {
"maxclass" : "comment",
"text" : "Read serial input buffer every 10 milliseconds",
"linecount" : 2,
"patching_rect" : [ 53.0, 72.0, 185.0, 34.0 ],
"numoutlets" : 0,
"fontsize" : 12.0,
"id" : "obj-15",
"fontname" : "Arial",
"numinlets" : 1
}

}
, {
"box" : {
"maxclass" : "comment",
"text" : "If you get newline (ASCII 10), send the list. If you get return (ASCII 13) do nothing. Any other value, add to the list",
"linecount" : 3,
"patching_rect" : [ 332.0, 269.0, 320.0, 48.0 ],
"numoutlets" : 0,
"fontsize" : 12.0,
"id" : "obj-16",
"fontname" : "Arial",
"numinlets" : 1
}

}
, {
"box" : {
"maxclass" : "comment",
"text" : "Click to open/close serial port and start/stop patch",
"linecount" : 2,
"patching_rect" : [ 271.0, 32.0, 199.0, 34.0 ],
"numoutlets" : 0,
"fontsize" : 12.0,
"id" : "obj-17",
"fontname" : "Arial",
"numinlets" : 1
}

}
],
"lines" : [ {
"patchline" : {
"source" : [ "obj-6", 0 ],
"destination" : [ "obj-1", 0 ],
"hidden" : 0,
"midpoints" : [ ]
}

}
, {
"patchline" : {
"source" : [ "obj-7", 0 ],
"destination" : [ "obj-6", 0 ],
"hidden" : 0,
"midpoints" : [ ]
}

}
, {
"patchline" : {
"source" : [ "obj-8", 0 ],
"destination" : [ "obj-7", 0 ],
"hidden" : 0,
"midpoints" : [ ]
}

}
, {
"patchline" : {
"source" : [ "obj-9", 0 ],
"destination" : [ "obj-8", 0 ],
"hidden" : 0,
"midpoints" : [ ]
}

}
, {
"patchline" : {
"source" : [ "obj-10", 0 ],
"destination" : [ "obj-9", 0 ],
"hidden" : 0,
"midpoints" : [ 253.5, 308.0, 311.5, 308.0 ]
}

}
, {
"patchline" : {
"source" : [ "obj-10", 2 ],
"destination" : [ "obj-9", 0 ],
"hidden" : 0,
"midpoints" : [ 311.5, 320.0, 311.5, 320.0 ]
}

}
, {
"patchline" : {
"source" : [ "obj-14", 0 ],
"destination" : [ "obj-10", 0 ],
"hidden" : 0,
"midpoints" : [ ]
}

}
, {
"patchline" : {
"source" : [ "obj-12", 0 ],
"destination" : [ "obj-14", 0 ],
"hidden" : 0,
"midpoints" : [ ]
}

}
, {
"patchline" : {
"source" : [ "obj-11", 0 ],
"destination" : [ "obj-12", 0 ],
"hidden" : 0,
"midpoints" : [ ]
}

}
, {
"patchline" : {
"source" : [ "obj-13", 0 ],
"destination" : [ "obj-14", 0 ],
"hidden" : 0,
"midpoints" : [ 378.5, 200.5, 253.5, 200.5 ]
}

}
, {
"patchline" : {
"source" : [ "obj-19", 0 ],
"destination" : [ "obj-14", 0 ],
"hidden" : 0,
"midpoints" : [ 358.5, 228.5, 253.5, 228.5 ]
}

}
, {
"patchline" : {
"source" : [ "obj-21", 0 ],
"destination" : [ "obj-14", 0 ],
"hidden" : 0,
"midpoints" : [ 336.5, 251.5, 253.5, 251.5 ]
}

}
, {
"patchline" : {
"source" : [ "obj-30", 0 ],
"destination" : [ "obj-21", 0 ],
"hidden" : 0,
"midpoints" : [ ]
}

}
, {
"patchline" : {
"source" : [ "obj-30", 1 ],
"destination" : [ "obj-19", 0 ],
"hidden" : 0,
"midpoints" : [ ]
}

}
, {
"patchline" : {
"source" : [ "obj-11", 0 ],
"destination" : [ "obj-30", 0 ],
"hidden" : 0,
"midpoints" : [ 253.0, 71.0, 336.5, 71.0 ]
}

}
]
}

arduino

void setup() {
// initialize the serial communication:
Serial.begin(9600);
}

void loop() {
// send the value of analog input 0:
Serial.println(analogRead(A0));
// wait a bit for the analog-to-digital converter
// to stabilize after the last reading:
delay(10);
}

chrislaw's icon

This paart of the project is now up and running. Thanks brendan for that link. The serial data from the arduino can now be indeed be translated into a graph using the the patch found at http://arduino.cc/en/Tutorial/Graph. I am now looking to analyse the graph for peaks and use this to drive the speed of a sound file (containing the sound of waves hitting the beach) within the Max/ Msp patch. My project will be completed and handed into university by the 17th and I will publish my entire project on the forum. Its only a creative beginners project but hopefully someone may find something of interest. In the mean time, anyone who can help us with the task in hand please add a post. Thanks to everyone on the forum, your all stars

brendan mccloskey's icon

"I am now looking to analyse the graph for peaks and use this to drive the speed of a sound file (containing the sound of waves hitting the beach) within the Max/ Msp patch"

Is this the remaining task you need help with? How do you wish to apply the data peaks, as a discrete trigger or continuous control value?

Here is a quick mock-up of how to evaluate peaks, and extract control data for playback speed; there are better (more aesthetic) ways to do this, this is a demo of the kind of thing you can easily do in Max:

Max Patch
Copy patch and select New From Clipboard in Max.

Brendan

brendan mccloskey's icon

ps

there is a valuable object called [past] which, imo, may work more intuitively than [peak] or [maximum] in this context

Max Patch
Copy patch and select New From Clipboard in Max.

Brendan

chrislaw's icon

Hey Brendan, in response to your post,"discrete trigger or continuous control value"; i'm not entirely sure. I'm thinking it would be a discrete trigger as I am using a prerecorded audio file. Ive taking some screen shots to show you what i have been up to. My first problem has been the graph patches interpretation of the wave. The serial data enters the patch as two values; the raw value and the value (check arduino sketch, 5th post). This is separated by a delimiter(,). Pic 1 shows how a used a 2 axis graph to display the heart beat using a spread sheet.
This led me to look at max basics tutorials, Data 2. I was trying to incorporate the part of the patch showing in pic2 so that the patch could read the .wav file and display it to the full potential in the graph by using the Peak and Trough objects to Analyse the beats range of values, adapting the graph to only show the minimum and maximum values ignoring those out of range. This would let me see what was going on better.
Pic 3 shows how i have added your patch which is working with the audio file, I'm so happy. I guess I just need to adjust the button argument values which are triggered to control the rate. Is that what the patch is doing? I really need to fix my graph readings. This is a massive help. I think ones I fix my incoming serial data for the graph and make a few tweaks to the values I will be up and running with this. Having thought this through discrete values would be fine.
thanks Brendan for all your help

1532.2.png
png
chrislaw's icon

this is the 3rd pic as i could only load 2 pictures at a time.
Also the serial data prints as in the txt file,

1534.heartb.txt
txt
chrislaw's icon

The first patch is for pic 2 left hand side and the 2nd is for pic 3.

1536.graph2.maxpat
Max Patch
chrislaw's icon

sorry about this, patch one seems to be crashing my computer as i pasted some patches together without thinking and Im getting some sort of conflict disabling my keyboard and stalling Max. Don't open it, thanks

chrislaw's icon

this is the max window

1538.list.png
png
chrislaw's icon

Im looking at 3 things at the moment. 1.Is the multislider capable of displaying this data
2. are the two values in each line in the serial data x, Y coordinates
3. How can I change the patch to recognise two values in the same line separated by the delimiter (,), and how can I graph them

chrislaw's icon

I modified the arduino sketch by deleting two of the lines;

int sensorPin = 0;

double alpha = 0.9;
int period = 20;
double change = 0.0;

void setup()
{
Serial.begin(115200);
}

void loop()
{
static double oldValue = 0;
static double oldChange = 0;
int rawValue =
analogRead(sensorPin);
// smoothing operation
double value = alpha * oldValue
+ (1 - alpha) * rawValue;
// the following two lines were removed
//Serial.print(rawValue);
//Serial.print(",");
//The last line was kept but now prints out
//the rawValue,
Serial.println(rawValue);

oldValue = value;
delay(period);
}

This makes the input compatible with the MAx/Msp patch which expects one value per line. Ive figured there was no need for the other printed values. I keep the raw value and it seems to be working to some degree of success although further calibration of the alpha value is needed. Hope I dont get into trouble for posting to much here, lol, its turning into a blogg

chrislaw's icon

Hi, the problem im facing is that the out put from the multislider is measuring the peak amplitude of the waves while I need to measure the frequency. Does anyone know how I could approach this dilemma? Im sure there has to be something in MSP, some sort of counter

George Khut's icon

Hi all

Interesting thread. I know the initial call was a few months ago now, but this is a recurring area on this forum, so I thought I'd share my own recent work, using Pulse Oximenters with Arduino FIO, Xbee etc.

You can download the collective and a sample data file via my blog
http://georgekhut.com/2011/01/heart-and-breath-sensing-w-max-msp/

It uses Pulse and Breath data measured by GP-4u PPG and GP-Resp (purchased from Dr. Philip Brotman, Allied Products/Biofeedback Instrument Corp, and manufactured by J&J Engineering).

The sensors are connected to two Arduino FIO’s, and this data is transmitted to my Mac using XBee based Zigbee wireless connection. The Max patch includes the Arduino code for both the Breath sensor and Pulse sensor (they are different!).

I use the “dot.schmitt” schmitt trigger object by Joseph Malloch, Stephen Sinclair, and Marlon Schumacher, to determine beats and inter beat intervals, then extract various parameters from there…

I'm still looking for help to implement Frequency Domain (LOMB) analysis of the heart RATE variations… any suggestions?

Hope its useful to other exploring this area. Always happy to discuss issues, opportunities etc.