-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathProcessing_Polyrhythmic_Teaching_Tool_.pde
310 lines (271 loc) · 10.1 KB
/
Processing_Polyrhythmic_Teaching_Tool_.pde
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
// The purpose of this code is to graph the data from Arduino's serial monitor
// into a bar graph. The graph has two horizontal ideal lines, where each ideal line
// represents one of the two components of the polyrhythm. If the data point
// falls sufficiently close to the ideal, its vertical line turns a “positive” color:
// BLUE or GREEN (depending on the ideal line in question). If the data point
// doesn't fall close to the ideal, its vertical line turns a "negative" color:
// RED.
// As a qualitative judgment of polyrhythmic accuracy, you can think:
// BLUE & GREEN = you're doing well!
// RED = you ain't doing so well... but spiral out and keep going!
// GOTTA MAKE SIMPLE CONTROLS FOR DIFFICULTY SETTINGS
// also, statistical analysis could be graphing error.
// this would be noisy, but it could be averaged into a curve.
// the curve should converge as initial learning occurs.
// then maybe wait half an hour and show that there's some remembered learning too.
// could combine these curves from many individuals.
// one group could be the control: after the half hour gap, it's the same tempos!
// another group could be the expt: after the half hour, it's slightly diff tempos!
// thinking out of the box... teaching someone to be *asynchronous* is difficult...
// then again, being asynchronous from 1/2 or 2/3, could simply mean being
// synchronous with something complex, like 37/61...
import processing.serial.*;
// is it important to have everything initialized up *here*? it's good form... do it!
// arrange the initialized variables in the order in which they show up in the code!
Serial myPort; // the serial port
int xPos = 1; // horizontal position of the graph
ArrayList<String> fastdatalist = new ArrayList<String>(); // create array of strings
ArrayList<String> slowdatalist = new ArrayList<String>();
ArrayList<String> errorFASTlist = new ArrayList<String>();
ArrayList<String> errorSLOWlist = new ArrayList<String>();
float bslow;
float bfast;
float cslow;
float cfast;
float dfast;
float dslow;
float avgcfast = 800;
float avgcslow = 350;
PFont f;
int yslow = 250; // 1/2->200,2/3->250,3/4->280,4/5->210
int yfast = 400; // 1/2->450,2/3->400,3/4->385,4/5->308
float circleXideal = 800;
float circleYideal = 350;
float circleRadiusideal = 20;
float circleXactual, circleYactual;
float circleRadiusactual = 10;
int timerStart = 0;
int offset;
int mill;
int seconds;
boolean stopped = false;
boolean continued = false;
void setup () {
size(1100, 700); // set window size
frameRate(10); // prevent slowing of image
f = createFont("Arial",16,true); // create font for directions
myPort = new Serial(this, Serial.list()[0], 9600); //only [5] works on my Mac.
myPort.bufferUntil('\n'); // don't generate serialEvent without newline character.
background(0); // set inital background as black
ellipseMode(RADIUS);
smooth();
}
void draw () { // empty, as everything is done in serialEvent
//background(0);
}
void serialEvent (Serial myPort) {
String inString = myPort.readStringUntil('\n'); // get the ASCII string
if (inString != null) {
inString = trim(inString); // trim off any whitespace
float inByte = float(inString); // convert to float
inByte = map(inByte, 0, 1023, 0, height); // map to screen height
int rinByte = round(inByte);
String realheight = Float.toString(height - rinByte);
stroke(255);
line(0, yfast, 500, yfast); // white reference line for ideal fast rhythm
stroke(255);
line(0, yslow, 500, yslow); // white reference line for ideal slow rhythm
// draw green line if value is within 20 pixels of ideal line for fast rhythm:
if (((height - rinByte) > (yfast - 30)) && (height - rinByte) < (yfast + 30)) {
stroke(0, 255, 0);
line(xPos, height, xPos, height - rinByte);
// categorize this data by adding a digit before the line height, in its string:
// why is this all necessary if the starting digit doesn't matter below?????
if (yfast == 450) {
String fast = "2";
fast += "," + realheight;
fastdatalist.add(fast);
}
if (yfast == 400) {
String fast = "3";
fast += "," + realheight;
fastdatalist.add(fast);
}
if (yfast == 385){
String fast = "4";
fast += "," + realheight;
fastdatalist.add(fast);
}
if (yfast == 308){
String fast = "5";
fast += "," + realheight;
fastdatalist.add(fast);
}
String fastdata[] = fastdatalist.toArray(new String[0]);
for (int i=0; i < fastdata.length; i++) {
// takes the second-to-last data point and turns it into a working String
String tempfast = fastdata[i=fastdata.length - 1];
String actualfast = tempfast.substring(2, 5);
float afast = Float.valueOf(actualfast).floatValue();
bfast = norm(afast, yfast, yfast+20); // decreasing yfast+X ups difficulty
dfast = norm(afast, yfast,yfast+20); // this is just for data collection
}
String errorFAST[] = errorFASTlist.toArray(new String[0]);
String errorfast = Float.toString(dfast);
errorFASTlist.add(errorfast);
saveStrings("fast.csv", errorFAST);
}
// draw blue line if value is within 20 pixels from ideal 100 line for slow rhythm:
if (((height - rinByte) > (yslow - 30)) && (height - rinByte) < (yslow + 30)) {
stroke(0, 100, 255);
line(xPos, height, xPos, height - rinByte);
// categorize this data by adding a digit before the line height, in its string:
// why is this all necessary if the starting digit doesn't matter below?????
if (yslow == 200) {
String slow = "1";
slow += "," + realheight;
slowdatalist.add(slow);
}
if (yslow == 250) {
String slow = "2";
slow += "," + realheight;
slowdatalist.add(slow);
}
if (yslow == 280) {
String slow = "3";
slow += "," + realheight;
slowdatalist.add(slow);
}
if (yslow == 210) {
String slow = "4";
slow += "," + realheight;
slowdatalist.add(slow);
}
String slowdata[] = slowdatalist.toArray(new String[0]);
for (int i=0; i < slowdata.length; i++) {
// takes the second-to-last data point and turns it into a working String
String tempslow = slowdata[i=slowdata.length - 1];
String actualslow = tempslow.substring(2, 5);
float aslow = Float.valueOf(actualslow).floatValue();
bslow = norm(aslow, yslow, yslow+20); // decreasing yslow+X ups difficulty
dslow = norm(aslow, yslow,yslow+20); // this is just for data collection
}
String errorSLOW[] = errorSLOWlist.toArray(new String[0]);
String errorslow = Float.toString(dslow);
errorSLOWlist.add(errorslow);
saveStrings("slow.csv", errorSLOW);
}
// draw a red line if value is farther than 20 pixels from either ideal line:
if (((height - rinByte) > (yslow + 30)) && (height - rinByte) < (yfast - 30) ||
((height - rinByte) < (yslow - 30)) || ((height - rinByte) > (yfast + 30))) {
stroke(200, 0, 0);
line(xPos, height, xPos, height - inByte);
}
// halfway across the screen, go back to the beginning:
if (xPos >= 500) {
xPos = 0;
background(0);
} else {
// increment the horizontal position:
xPos++;
xPos++;
}
}
// the rest of the code focuses on drawing ellipses,
// with distance from center based on distance from ideal.
float cfast = map(bfast, -2.0, 2.0, 600, 1000);
float cslow = map(bslow, -2.0, 2.0, 550, 150);
// average values using infinite impulse response:
avgcfast=.9*avgcfast+.1*(float)(cfast);
avgcslow=.9*avgcslow+.1*(float)(cslow);
circleXactual = avgcfast;
circleYactual = avgcslow;
if (!stopped) {
mill=(millis()-timerStart);
if (continued) mill += offset;
seconds = mill / 1000;
}
//draw opaque rectangle to remove older score:
stroke(255,0,0);
fill(255,0,0);
rect(850, 20, 100, 60);
textFont(f,50);
fill(255);
text(seconds, 860, 70);
text("score =", 680, 70);
if (circleCircleIntersect(circleXideal, circleYideal, circleRadiusideal,
circleXactual, circleYactual, circleRadiusactual) == true) {
stopped = false;
continued = true;
timerStart = millis();
offset = mill;
} else {
stopped = true; // pause
}
//draw translucent red ellipse to show severe offset
stroke(255,0,0);
fill(255,0,0, 20);
ellipse(800, 350, 200, 200);
//draw translucent yellow ellipse to show mild offset
stroke(150,150,0);
fill(255,255,0, 20);
ellipse(800, 350, 100, 100);
//draw translucent green ellipse to show negligible offset... !IDEAL!
stroke(0,150,0);
fill(0,255,0, 20);
ellipse(circleXideal, circleYideal, circleRadiusideal, circleRadiusideal);
//draw translucent rectangle to fade older ellipses:
stroke(0);
fill(0,50);
rect(590, 140, 420, 420);
// display directions for offset of each type of polyrhythm:
if ((yslow == 200) && (yfast == 450)) {
textFont(f,25);
fill(0, 100, 255);
text("1 slow",770,575);
text("1 fast",770,140);
fill(0, 255, 0);
text("2 fast",1020,360);
text("2 slow",520,360);
}
else if ((yslow == 250) && (yfast == 400)) {
textFont(f,25);
fill(0, 100, 255);
text("2 slow",770,575);
text("2 fast",770,140);
fill(0, 255, 0);
text("3 fast",1020,360);
text("3 slow",520,360);
}
else if ((yslow == 280) && (yfast == 385)) {
textFont(f,25);
fill(0, 100, 255);
text("3 slow",770,575);
text("3 fast",770,140);
fill(0, 255, 0);
text("4 fast",1020,360);
text("4 slow",520,360);
}
else if ((yslow == 210) && (yfast == 308)) {
textFont(f,25);
fill(0, 100, 255);
text("4 slow",770,575);
text("4 fast",770,140);
fill(0, 255, 0);
text("5 fast",1020,360);
text("5 slow",520,360);
}
// draw ellipses from data:
stroke(0);
fill(255);
ellipse(circleXactual, circleYactual, circleRadiusactual, circleRadiusactual);
}
// this is a function that returns a boolean based on whether the circles overlap:
boolean circleCircleIntersect(float cxideal, float cyideal, float crideal,
float cxactual, float cyactual, float cractual) {
if (dist(cxideal, cyideal, cxactual, cyactual) < crideal + cractual) {
return true;
} else {
return false;
}
}