Created
November 4, 2012 21:03
-
-
Save rioleo/4013771 to your computer and use it in GitHub Desktop.
Wrist-based input
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
import processing.serial.*; | |
import ddf.minim.*; | |
Minim minim; | |
AudioOutput out; | |
PluckedString string; | |
// The fundamental frequency of the string (in Hz) | |
// Setting this very low (e.g. 2.0) results in | |
// visible traveling waves. | |
float f0 = 440.0; | |
int thresholds = 25000; | |
import cc.arduino.*; | |
int wait = 3; | |
int sum; | |
Arduino arduino; | |
int time; | |
int testExecuted = 0; | |
int traintime = 400; | |
int downdelay = 500; | |
int numsensors = 4; | |
int numtrials = 5; | |
int forceRun = -1; | |
int rows = 200; | |
int thickness = 25; | |
int [][][] signal = new int [5][traintime][4]; | |
int [][] tempsignal = new int [traintime][numsensors]; | |
int [][] tempsignalshift = new int [traintime][numsensors]; | |
int tempsignallength = 0; | |
color off = color(4, 79, 111); | |
color on = color(84, 145, 158); | |
void setup() { | |
minim = new Minim(this); | |
// get a line out from Minim, default bufferSize is 1024, default sample rate is 44100, bit depth is 16 | |
out = minim.getLineOut(Minim.STEREO); | |
// initialize the string (but it won't ring until you pluck it) | |
string = new PluckedString(f0, 1.0); | |
int[] seqA = { | |
0, 0, 0, 3, 6, 13, 25, 22, 7, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 | |
}; | |
int[] seqB = { | |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 12, 24, 23, 8, 3, 1, 0, 0, 0, 0, 0 | |
}; | |
println(dynamicTimeWarp(seqA, seqB)); | |
size(700, 500); | |
arduino = new Arduino(this, Arduino.list()[0], 57600); | |
for (int i = 0; i <= 13; i++) { | |
arduino.pinMode(i, Arduino.INPUT); | |
} | |
} | |
void sampleTest2() { | |
int [] train0 = new int[traintime]; | |
int [] train1 = new int[traintime]; | |
int [] train2 = new int[traintime]; | |
int [] train3 = new int[traintime]; | |
int [] test0 = new int[traintime]; | |
int [] test1 = new int[traintime]; | |
int [] test2 = new int[traintime]; | |
int [] test3 = new int[traintime]; | |
for (int x = 0;x <traintime;x++) { | |
// Compare sensor 1 | |
if (forceRun == -1) { | |
train0[x] = signal[0][x][0]; | |
train1[x] = signal[0][x][1]; | |
train2[x] = signal[0][x][2]; | |
train3[x] = signal[0][x][3]; | |
test0[x] = tempsignal[x][0]; | |
test1[x] = tempsignal[x][1]; | |
test2[x] = tempsignal[x][2]; | |
test3[x] = tempsignal[x][3]; | |
} | |
else { | |
train0[x] = signal[0][x][forceRun]; | |
test0[x] = tempsignal[x][forceRun]; | |
} | |
} | |
if (forceRun == -1) { | |
sum = (dynamicTimeWarp(train0, test0) + dynamicTimeWarp(train1, test1) + dynamicTimeWarp(train2, test2) + dynamicTimeWarp(train3, test3)); | |
} | |
else { | |
sum = dynamicTimeWarp(train0, test0); | |
} | |
if (sum < thresholds) { | |
play(); | |
println("Signal: perceived match with score: " + sum); | |
} | |
else { | |
println("Signal: perceived nonmatch with score: " + sum); | |
} | |
} | |
void play() { | |
float pluck_point = map(0, 0, 100, 0, 1); | |
float sharpness = map(0, 0, 100, 0, 1); | |
string.pluck(pluck_point, sharpness); | |
string.release(); | |
} | |
void testAgainstFirst(int id) { | |
int [] train0 = new int[traintime]; | |
int [] train1 = new int[traintime]; | |
int [] train2 = new int[traintime]; | |
int [] train3 = new int[traintime]; | |
int [] test0 = new int[traintime]; | |
int [] test1 = new int[traintime]; | |
int [] test2 = new int[traintime]; | |
int [] test3 = new int[traintime]; | |
for (int x = 0;x <traintime;x++) { | |
// Compare sensor 1 | |
if (forceRun == -1) { | |
train0[x] = signal[0][x][0]; | |
train1[x] = signal[0][x][1]; | |
train2[x] = signal[0][x][2]; | |
train3[x] = signal[0][x][3]; | |
test0[x] = signal[id][x][0]; | |
test1[x] = signal[id][x][1]; | |
test2[x] = signal[id][x][2]; | |
test3[x] = signal[id][x][3]; | |
} | |
else { | |
train0[x] = signal[0][x][forceRun]; | |
test0[x] = signal[id][x][forceRun]; | |
} | |
} | |
if (forceRun == -1) { | |
sum = (dynamicTimeWarp(train0, test0) + dynamicTimeWarp(train1, test1) + dynamicTimeWarp(train2, test2) + dynamicTimeWarp(train3, test3)); | |
} | |
else { | |
sum = dynamicTimeWarp(train0, test0); | |
} | |
if (sum < thresholds) { | |
println("Signal: perceived match with score: " + sum); | |
play(); | |
if (id == 4) { | |
testExecuted = 1; | |
} | |
} | |
else { | |
testExecuted = 0; | |
println("Perceived nonmatch with score: " + sum); | |
} | |
} | |
void runTrial(int id) { | |
testExecuted = 0; | |
while (wait > 0) { | |
println("Starting training session " + id + " in " + wait); | |
delay(downdelay); | |
wait = wait - 1; | |
} | |
println("Go!"); | |
for (int x = 0;x <traintime;x++) { | |
for (int y = 0;y < numsensors;y++) { | |
int z = arduino.analogRead(y); | |
signal[id-1][x][y] = z; | |
//subsignal = append(subsignal, z); | |
delay(1); | |
} | |
//signal[id-1][y] = subsignal; | |
} | |
println("Training done."); | |
drawOnce(); | |
wait = 3; | |
testAgainstFirst(id-1); | |
} | |
int abso(int x, int y) { | |
return abs(x-y); | |
} | |
int dynamicTimeWarp(int [] seqA, int [] seqB) { | |
int numRows = seqA.length; | |
int numCols = seqB.length; | |
int[][] cost = new int[numCols][numRows]; | |
// Initialize 2D array values | |
for (int i = 0; i < numCols; i++ ) { | |
for (int j = 0; j < numRows; j++ ) { | |
cost[i][j] = 0; | |
} | |
} | |
cost[0][0] = abso(seqA[0], seqB[0]); | |
for (int i = 1; i < numRows; i++) { | |
cost[i][0] = cost[i-1][0] + abso(seqA[i], seqB[0]); | |
} | |
for (int j = 1; j < numCols; j++) { | |
cost[0][j] = cost[0][j-1] + abso(seqA[0], seqB[j]); | |
} | |
for (int i = 1; i < numRows; i++) { | |
for (int j = 1; j < numCols; j++) { | |
int choices1 = cost[i-1][j]; | |
int choices2 = cost[i][j-1]; | |
int choices3 = cost[i-1][j-1]; | |
cost[i][j] = min(choices1, choices2, choices3) + abso(seqA[i], seqB[j]); | |
} | |
} | |
return cost[numCols-1][numRows-1]; | |
} | |
void keyPressed() { | |
switch (key) { | |
case 'r': | |
println("Resetting"); | |
wait = 3; | |
testExecuted = 0; | |
forceRun = -1; | |
// do something if the key pressed was 'r' | |
break; | |
case 'a': | |
forceRun = 0; | |
break; | |
case 'b': | |
forceRun = 1; | |
break; | |
case 'c': | |
forceRun = 2; | |
break; | |
case 'd': | |
forceRun = 3; | |
break; | |
case 'f': | |
thresholds = sum; | |
println("Set threshold to: " + thresholds); | |
break; | |
case '1': | |
runTrial(1); | |
break; | |
case '2': | |
runTrial(2); | |
break; | |
case '3': | |
runTrial(3); | |
break; | |
case '4': | |
runTrial(4); | |
break; | |
case '5': | |
runTrial(5); | |
break; | |
default: | |
break; | |
} | |
} | |
void sampleTest() { | |
int [] train = new int[traintime]; | |
int [] test = new int[traintime]; | |
for (int x = 1;x <traintime;x++) { | |
// Training for x is signal at trial 0, sensor 0 | |
train[x] = signal[0][x][0]; | |
test[x] = tempsignal[x][0]; | |
} | |
println(dynamicTimeWarp(train, test)); | |
} | |
void drawOnce() { | |
colorMode(RGB, 1023); | |
// Trial 0 | |
for (int l = 0; l < numtrials; l++) { | |
for (int j = 0; j < rows; j++) { | |
for (int i = 0; i < numsensors; i++) { | |
stroke(signal[l][j][i]); | |
for (int k = 0; k < thickness; k++) { | |
point(i+(thickness*i) + k+(l*numsensors*thickness), j); | |
} | |
} | |
} | |
} | |
} | |
void draw() { | |
if (testExecuted == 1) { | |
if (tempsignallength < traintime) { | |
//println("Entering"); | |
for (int x = 0;x <traintime;x++) { | |
//tempsignallength++; | |
for (int y = 0;y < numsensors;y++) { | |
int z = arduino.analogRead(y); | |
tempsignal[x][y] = z; | |
delay(1); | |
} | |
} | |
} | |
// else { | |
// //println("Shifting"); | |
// for (int x = 1;x <traintime;x++) { | |
// for (int y = 0;y < numsensors;y++) { | |
// tempsignalshift[x-1][y] = tempsignal[x][y]; | |
// } | |
// } | |
// | |
// for (int y = 0;y < numsensors;y++) { | |
// int z = arduino.analogRead(y); | |
// tempsignalshift[traintime-1][y] = z; | |
// delay(1); | |
// } | |
// tempsignal = tempsignalshift; | |
// } | |
sampleTest2(); | |
} | |
else { | |
for (int i = 0; i < 4; i++) { | |
ellipse(280 + i * 30, 240, arduino.analogRead(i) / 16, arduino.analogRead(i) / 16); | |
} | |
} | |
} | |
class PluckedString implements AudioSignal | |
{ | |
int wg_length; // the length of the string in samples | |
float[] y_plus; // buffer to hold positive-moving wave | |
float[] y_minus; // buffer to hold negative-moving wave | |
float[] y; // temporary buffer to calculate resultant shape of wave, used during display | |
float[] reflec_filt = { | |
.25, .5, .25 | |
}; // the dispersion filter at the "bridge" end reflection | |
int ref_hlen; | |
boolean active; // flag to stop the wave from propagating while string is being plucked | |
// variables to control interactive damping (experimental) | |
float damp_point; | |
float damp_r; | |
PluckedString(float freq, float ampl) | |
{ | |
// Waveguide length is a delay equivalent to half the desired period | |
wg_length = Math.round(out.sampleRate()/freq/2); | |
// how much extra to allow for reflection filter | |
ref_hlen = (reflec_filt.length-1)/2; | |
y_plus = new float[wg_length+ref_hlen]; // extra for symmetric ref_filt | |
y_minus = new float[wg_length+ref_hlen]; // just convenience | |
// combined wave used only for display | |
y = new float[wg_length]; | |
// initialize damping point to 0.0, no effect | |
damp_point = 0.0; | |
damp_r = 1.0; | |
// connect it to output for sound output | |
out.addSignal(this); | |
} | |
// PluckedString::pluck is called to setup an initial displacement of the string | |
// It stops the string from ringing, call PluckedString::release to start it again. | |
void pluck(float pluck_point, float sharpness) | |
{ | |
// stop wave propagation for a moment | |
active = false; | |
// remove damping | |
damp_point = 0.0; | |
// initialize traveling wave values in response to pluck | |
// equal and triangular, for string displaced at pluck_point of its length | |
// raise triangular waveshape to a positive exponent to allow more sharper plucks | |
float exponent = (float)Math.pow(2.0, 8*sharpness); | |
float pluck_amp = 1.0; | |
for (int i = 0; i < wg_length; ++i) { | |
if (i < Math.round(pluck_point * (float)wg_length)) { | |
y_minus[i] = y_plus[i] = (float)Math.pow(pluck_amp*(float)i/(pluck_point*(float)wg_length), exponent); | |
} | |
else { | |
y_minus[i] = y_plus[i] = (float)Math.pow(pluck_amp*(float)(wg_length-i)/((1-pluck_point)*(float)wg_length), exponent); | |
} | |
} | |
} | |
// called after setting up waveshape with pluck() to start oscillation | |
void release() { | |
active = true; | |
} | |
// external control for the experimental damping function | |
void setDamp(float position, float ratio) | |
{ | |
damp_point = position; | |
// ratio comes in as -1..1 | |
//damp_r = ratio; | |
damp_r = (float)Math.pow(2.0, 5*(-1.0-ratio)); // 0.001 .. 1.0 | |
println("damp_r="+damp_r); | |
} | |
// helper function draws a waveshape in a specific box and color | |
void drawWave(float [] vals, int len, int x, int y, int w, int h, int r, int g, int b) | |
{ | |
stroke(r, g, b); | |
int mid = y + h/2; | |
int vscale = h/2; | |
for (int i = 0; i < len-1; i++) | |
{ | |
float x1 = x+map(i, 0, len, 0, w); | |
float x2 = x+map(i+1, 0, len, 0, w); | |
line(x1, mid - vscale*vals[i], | |
x2, mid - vscale*vals[i+1]); | |
} | |
} | |
// display the current state of the string | |
void draw() | |
{ | |
background(0); | |
stroke(255); | |
// draw the two traveling waves and their sum = string displacement | |
// forward wave in red, top half of pane | |
drawWave(y_plus, wg_length, 0, height/2, width, height/2, 255, 0, 0); | |
// backward wave in blue, bottom half of pane | |
drawWave(y_minus, wg_length, 0, 0, width, height/2, 0, 0, 255); | |
// combined wave - white, middle of pane | |
// sum up the two traveling waves | |
for (int i = 0; i < wg_length; ++i) | |
{ | |
y[i] = y_plus[i] + y_minus[i]; | |
} | |
drawWave(y, wg_length, 0, height/4, width, height/2, 255, 255, 255); | |
if (damp_point > 0) { | |
stroke(255); | |
int damp_x = Math.round(map(damp_point, 0, 1, 0, width)); | |
line(damp_x, 0, damp_x, height); | |
} | |
} | |
// called by sound output to calculate samples, drives the basic | |
// computation loop. | |
void generate(float[] samps) | |
{ | |
// each sample of the output corresponds to one time step | |
// of the basic physics simulaiton | |
for (int i = 0; i < samps.length; ++i) | |
{ | |
float y_reflec = 0; | |
// only do anything if the string has been released | |
if (active == true) | |
{ | |
// move the traveling waves one point forward | |
for (int j = y_plus.length-1; j > 0; --j) { | |
// copy forward starting at end | |
y_plus[j] = y_plus[j-1]; | |
} | |
for (int j = 0; j < y_plus.length-1; ++j) | |
{ | |
// copy backwards starting at beginning | |
y_minus[j] = y_minus[j+1]; | |
} | |
// simple reflection at one end | |
y_plus[0] = -y_minus[0]; | |
// filtered reflection at far end | |
for (int k=0; k < reflec_filt.length; ++k) | |
{ | |
// y_plus has a few points "off the end" to allow | |
// a symmetric reflec_filter | |
y_reflec += reflec_filt[k] * y_plus[wg_length - 1 - ref_hlen + k]; | |
} | |
// reflected wave is the new point in the backward-traveling wave | |
y_minus[wg_length - 1] = -y_reflec; | |
// apply experimental damping/scattering | |
int damp_index = Math.round(damp_point * wg_length); | |
if (damp_index > 0) | |
{ | |
// additional offsets to traveling wave both ways | |
float y0 = y_plus[damp_index] + y_minus[damp_index]; | |
if (y0 > damp_r) { | |
float delta = y0 - damp_r; | |
// add to ongoing waves | |
y_plus[damp_index] -= delta; | |
y_minus[damp_index] -= delta; | |
} | |
} | |
} | |
// output is given by amount of reflection (~ energy being passed through bridge) | |
samps[i] = y_reflec; | |
} | |
} | |
// AudioSignal requires both mono and stereo generate functions | |
void generate(float [] sampL, float [] sampR) | |
{ | |
generate(sampL); | |
// copy left to right | |
System.arraycopy(sampL, 0, sampR, 0, sampR.length); | |
} | |
} | |
//void draw() { | |
// | |
// for (int i = 0; i < 4; i++) { | |
// ellipse(280 + i * 30, 240, arduino.analogRead(i) / 16, arduino.analogRead(i) / 16); | |
// } | |
//} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment