thabir – Clock
While the perception of time for Western society is largely linear, the measurement of time is intricately dependent on recurring observable cycles. Day and night, lunar cycles, sleep cycles, seasons, migratory patterns, life cycles, cosmic events, all of these are units with which one can measure intervals of time. The more I thought about the notion of measurement using cycles, the more I realized the arbitrary (yet convenient) nature of the cycles we have chosen to represent the world around us, as well as fact that no natural cycle truly aligns itself perfectly with our canonical system of measurement: the Gregorian Calendar. Its this notion of phasing, of constant cosmic counterpoint, that my clock tries to explore.
My clock is a rectangle packing algorithm, which allows the user to define a number of cosmic cycles of their choice, along with their length in days. It then generates a packing in a manner such that the ratios of the areas of the rectangles are equal to the ratios of the lengths of the respective cycles. Therefore area becomes a dimension for representing the period of a cosmic cycle. Moreover, the packing algorithm is designed to create a radial structure in the clock.
The overall hue palette of the clock is defined by the time of day, cycling through the entire range over the course of 24 hours. Each individual rectangle is initialized to a random epsilon variation in brightness and saturation, with the range of epsilon increasing the further outward you move from the center. However, the central concept of the clock is about the subtle phasing of cosmic cycles, independent of the units we try to bind them in. To try and represent that, I took the cycle lengths of each event and, by artificially syncing them all at the turn of the millennium, I calculated their position in their cycle. In other words, I calculated the phase. All the cycles are represented in terms of days and are unfolding in real time. Since all of them were artificially synced at the year 2000(an arbitrary choice that the user can change), the data I get is not true astronomical data but rather a representation of cosmic phasing. I then take these cyclic motions and map them on to the rectangles by making the hues of the rectangles subtly oscillate to their cosmic frequency.
The clock also has some interactive features. When you open the Processing application, you get a screen like the one below, telling you the various features. On pressing enter, you begin experiencing the clock. If you press the letter ‘l’, you can find out the name of the representative cosmic cycle by hovering over a rectangle. Since the actual phasing of the clock is occurring in real time and is therefore imperceptibly slow, you can speed it up and slow it down by factors of 2 using ‘f’ and ‘s’. My intention for this design was to create a subtly undulating field of hues that vary in an almost subliminal manner. Therefore, the right choice of speed is extremely important. By pressing ‘o’, you get what I believe to be the optimal speed for the given packing. The gif above illustrates the clock at this speed.
A fundamental premise of this design was the separation of units from events, which is reflected in the design of the clock since the change in the speed of the cosmic cycles having no effect on the hue palette. This is solely linked to the time of day. However, by pressing ‘h’ and ‘g’ you can move forwards and backwards respectively through the hue palette of the clock in hourly increments. By pressing ‘z’, the current frame gets exported as a png. The image at the top is an arrangement of such frames taken of the clock at hourly increments, starting at midnight and going till 11pm. The first gif below is a visualization of cycle packings of increasing number. The second gif is a longer demonstration of the entire flow of interaction.
The subliminal nature of this clock makes it a media object you need to live with for longer periods to begin to understand and discern. My main visual inspiration was Mondriaan, (such as his Composition No 11 from 1913), even though my work is extremely different in final form. Another major influence is Steve Reich’s use of phasing in his Clapping Music for Two Musicians. From media art, the project that really struck me was Robert Yang’s ‘Rinse and Repeat’, a first person shower simulator which, among many other things, blurs the lines between “real” time and “in-media” time.
There are many directions in which I feel this clock can and should be pushed. One of the most basic, yet important, is the development of a UI that allows for complete customizability of the clock, i.e. it allows for the user to enter the relevant cycles and the choice of the sync year, as well as providing fine-grained control of the speed parameters. I would also love for the clock to be able to scale and resize the rectangles to fit a constant, specified canvas size. This is the easiest adjustment to implement, the only difficulty being some rounding errors, causing gaps between the rectangles. A much harder improvement would be the efficiency of the packing algorithm. As it stands, the algorithm is pretty inefficient and approaches the problem using brute force. It is too slow to represent clocks with greater than 12 cycles. A major part of a redesign will be to optimize this so that I can visualize more data. Ideally I would be able to represent up to 50 cycles, all phasing with each other. However, by far the most important extension to this project would be the addition of an accompanying data sonification. This piece is intended to be a temporal, meditative, subliminal experience of cosmic evolution and is inherently multimodal in its conception.
Since my clock is something that communicates not through a single visual act of transmission, but through the slow process of experiencing it, (an attempt at the visual analog to Brian Eno’s ambient music), it requires a space that allows for extended uninterrupted meditation. In a gallery, I could see it as a projection in a dark room, where viewers are encouraged to enter and deprive themselves of all other sensory stimulation for a while, losing all of our daily markers for the passage of time. In a non-gallery context I could possibly see it projected on a wall, or used as a desktop background or screensaver, but its impact would be greatly reduced in my opinion. This is another reason why sound is so integral to the clock, since it is the most effective medium at isolating and suspending people, both physically and mentally, inside of an experience, acting as a buffer between them and the rest of the world.
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 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 | class Cycle { int x; int y; int Width; int Height; float period; String label; color c; float epsilon; Cycle(int a, int b, int c, int d, float e, String f) { x = a; y = b; Width = c; Height = d; period = e; label = f; } void rotate90(int canvasHeight) { int t = this.y; this.y = this.x; this.x = canvasHeight - (t+this.Height); t = this.Width; this.Width = this.Height; this.Height = t; } float calculateCyclePos(int syncYear, int year, int month, int day, int hour, int minute) { float numDays = (year - syncYear) * 365; if(month==1) numDays = numDays + day; else if(month==2) numDays = numDays + day + 31; else if(month==3) numDays = numDays + day + 59; else if(month==4) numDays = numDays + day + 90; else if(month==5) numDays = numDays + day + 120; else if(month==6) numDays = numDays + day + 151; else if(month==7) numDays = numDays + day + 181; else if(month==8) numDays = numDays + day + 212; else if(month==9) numDays = numDays + day + 243; else if(month==10) numDays = numDays + day + 273; else if(month==11) numDays = numDays + day + 304; else numDays = numDays + day + 334; int leapYear = (year - syncYear)/4; if(year%4==0 && month>=3) leapYear++; numDays = numDays + leapYear; numDays = numDays + ((hour*60)+minute)*1.0/1440.0; float cyclePos = (numDays/this.period) - floor(numDays/this.period); cyclePos = cyclePos*2; if(cyclePos>1) cyclePos = map(cyclePos, 1, 2, 1, 0); cyclePos = map(cyclePos, 0, 1, -0.5, 0.5); return cyclePos; } void updateColor(int hour, float cyclePos) { colorMode(HSB, 1440, 50, 50); this.c = color((hour*60)+minute()+(150*cyclePos), 22+this.epsilon, 36+this.epsilon); } boolean isMouseOver(int a, int b) { return(this.x<=a && a<(this.x+this.Width) && this.y<=b && b<(this.y+this.Height)); } void showLabel(int canvasWidth, int canvasHeight) { noStroke(); fill(215, 50); rect(0, 0, canvasWidth, canvasHeight); textAlign(LEFT); float Width = textWidth(this.label); int xOffset = round((canvasWidth - Width)/2.0); fill(255, 255); text(this.label, xOffset, round(canvasHeight/2.5)); } void drawCycle() { noStroke(); fill(this.c); rect(this.x, this.y, this.Width, this.Height); } } int globalWidth = 0; int globalHeight = 0; float[] cyclePeriods = new float[12]; String[] Labels = new String[12]; Cycle[] Cycles = (Cycle[])new Cycle[12]; PFont GillSans; //calculate all subsets of the ratios float[][] allSubsets(float[] ratios) { if(ratios.length==0){ float[][] subsets = new float[1][0]; return subsets; } else { float last = ratios[ratios.length-1]; ratios = shorten(ratios); float[][] subsets = allSubsets(ratios); float[][] finalSubsets = new float[2*subsets.length][]; for(int i=0; i<subsets.length; i++) { finalSubsets[2*i] = subsets[i]; finalSubsets[(2*i)+1] = append(subsets[i], last); } return finalSubsets; } } //filters all subsets of cardinality lower than 2 ArrayList filterCardinality(float[][] subsets) { ArrayList filteredSubsets = new ArrayList(); for(int i=0; i<subsets.length; i++) { if(subsets[i].length>=2) { FloatList subset = new FloatList(subsets[i]); filteredSubsets.add(subset); } } return filteredSubsets; } //calculate the width addition of a subset int subsetWidth(float ratio, FloatList ratios) { float ratioSum = 1; for(int i=1; i<ratios.size(); i++) { ratioSum = ratioSum + (ratios.get(i)/ratios.get(0)); } int a = round((globalHeight*1.0)/ratioSum); int b = round((ratios.get(0)*globalWidth*globalHeight)/(ratio*a)); return b; } //filter ArrayList of subsets based on choice of subset ArrayList filterSubsetSequence(FloatList subset, ArrayList subsets) { for(int i=0; i<subset.size(); i++) { IntList toRemove = new IntList(); for(int j=0; j<subsets.size(); j++) { if(((FloatList)subsets.get(j)).hasValue(subset.get(i))) { int index = j - toRemove.size(); toRemove.append(index); } } for(int j=0; j<toRemove.size(); j++) { subsets.remove(toRemove.get(j)); } } return subsets; } //depth first search of subset sequence tree, calculating width addition at each step ArrayList allSubsetSequences(ArrayList subsets) { if(subsets.size()==0) { return subsets; } else if(subsets.size()==1) { ArrayList subsetSeq = new ArrayList(); subsetSeq.add(subsets); return subsetSeq; } else { ArrayList sequenceList = new ArrayList(); for(int i=0; i<subsets.size(); i++) { FloatList subset = (FloatList)subsets.get(i); ArrayList temp = new ArrayList(); for(int j=0; j<subsets.size(); j++) { temp.add((FloatList)subsets.get(j)); } ArrayList newSubsets = filterSubsetSequence(subset, temp); ArrayList localSequenceList = allSubsetSequences(newSubsets); for(int j=0; j<localSequenceList.size(); j++) { ArrayList temp1 = new ArrayList(); temp1.add(subset); temp1.addAll((ArrayList)localSequenceList.get(j)); localSequenceList.set(j, temp1); } sequenceList.addAll(localSequenceList); } return sequenceList; } } //ranks subset sequences by some criteria, returns highest ranking subset sequence int finalSequenceNumber(ArrayList widthLists) { int bestMetric = -1; int bestSequence = -1; for(int i=0; i<widthLists.size(); i++) { boolean ordered = true; for(int j=0; j<((IntList)widthLists.get(i)).size()-1; j++) { if(((IntList)widthLists.get(i)).get(j)<((IntList)widthLists.get(i)).get(j+1)) { ordered = false; break; } } if(ordered==false) continue; int metric = 0; for(int j=0; j<((IntList)widthLists.get(i)).size()-1; j++) { metric += pow((((IntList)widthLists.get(i)).get(j)-((IntList)widthLists.get(i)).get(j+1)), 2); } metric = round((metric * 1.0) / (((IntList)widthLists.get(i)).size()-1)); metric = round(sqrt(metric)); if(bestMetric==-1 && bestSequence==-1) { bestMetric = metric; bestSequence = i; } if(metric<bestMetric) { bestMetric = metric; bestSequence = i; } } return bestSequence; } //checks if a column of rectangles produce a suitable width to height ratio and then initializes them void addColumn(float ratio, float[] ratios, int placeHolder) { float ratioSum = 1; for(int i=1; i<ratios.length; i++) { ratioSum = ratioSum + (ratios[i]/ratios[0]); } int a = round((globalHeight*1.0)/ratioSum); int b = round((ratios[0]*globalWidth*globalHeight)/(ratio*a)); int index = 0; for(int i=0; i<cyclePeriods.length; i++) { if(ratios[0]==cyclePeriods[i]) index = i; } Cycles[placeHolder] = new Cycle(globalWidth, 0, b, a, ratios[0], Labels[index]); int hOffset = a; placeHolder++; for(int i=1; i<ratios.length-1; i++) { a = round((ratios[i]/ratios[0])*a); for(int j=0; j<cyclePeriods.length; j++) { if(ratios[i]==cyclePeriods[j]) index = j; } Cycles[placeHolder] = new Cycle(globalWidth, hOffset, b, a, ratios[i], Labels[index]); hOffset += a; placeHolder++; } for(int j=0; j<cyclePeriods.length; j++) { if(ratios[ratios.length-1]==cyclePeriods[j]) index = j; } Cycles[placeHolder] = new Cycle(globalWidth, hOffset, b, globalHeight-hOffset, ratios[ratios.length-1], Labels[index]); globalWidth += b; } void makeCanvas() { globalWidth = 500; globalHeight = 500; Cycles[0] = new Cycle(0, 0, globalWidth, globalHeight, cyclePeriods[0], Labels[0]); float[] temp = new float[cyclePeriods.length-1]; for(int i=0; i<temp.length; i++) { temp[i] = cyclePeriods[i+1]; } ArrayList subsets = filterCardinality(allSubsets(temp)); ArrayList allSequences = allSubsetSequences(subsets); ArrayList widthLists = new ArrayList(); for(int i=0; i<allSequences.size(); i++) { float ratio = cyclePeriods[0]; IntList widths = new IntList(); for(int j=0; j<((ArrayList)allSequences.get(i)).size(); j++) { widths.append(subsetWidth(ratio, (FloatList)((ArrayList)allSequences.get(i)).get(j))); for(int k=0; k<((FloatList)((ArrayList)allSequences.get(i)).get(j)).size(); k++) { ratio += ((FloatList)((ArrayList)allSequences.get(i)).get(j)).get(k); } } widthLists.add(widths); } int sequenceNum = finalSequenceNumber(widthLists); subsets = (ArrayList)allSequences.get(sequenceNum); float ratio = cyclePeriods[0]; int placeHolder = 1; for(int i=0; i<subsets.size(); i++) { float[] ratios = ((FloatList)subsets.get(i)).array(); addColumn(ratio, ratios, placeHolder); for(int j=0; j<ratios.length; j++) { ratio = ratio + ratios[j]; } placeHolder += ratios.length; for(int j=0; j<placeHolder; j++) { Cycles[j].rotate90(globalHeight); } int t = globalWidth; globalWidth = globalHeight; globalHeight = t; } if(globalHeight>globalWidth) { for(int j=0; j<Cycles.length; j++) { Cycles[j].rotate90(globalHeight); } int t = globalWidth; globalWidth = globalHeight; globalHeight = t; } /*if(globalWidth>900 || globalHeight>900) { int bigSide; if(globalWidth>=globalHeight) bigSide = globalWidth; else bigSide = globalHeight; float multiplier = 900.0/bigSide; globalWidth = floor(globalWidth*multiplier); globalHeight = floor(globalHeight*multiplier); for(int i=0; i<Cycles.length; i++) { Cycles[i].x = floor(Cycles[i].x*multiplier); Cycles[i].y = floor(Cycles[i].y*multiplier); Cycles[i].Width = floor(Cycles[i].Width*multiplier); Cycles[i].Height = floor(Cycles[i].Height*multiplier); } }*/ } int hour = hour(); int minute = minute(); int day = day(); int month = month(); int year = year(); int lastMinute = millis(); int syncYear = 2000; float speed = 1.0; int hueHour = 0; int numKeyPressed = 0; int drawHourFrame = -1; int drawSpeedFrame = -1; boolean drawInstructionFrame = true; void setup() { cyclePeriods[0] = 10752.9; Labels[0] = "Saturn Revolution"; //cyclePeriods[1] = 5595.45; Labels[1] = "Lemmon Comet Period"; cyclePeriods[1] = 4328.9; Labels[1] = "Jupiter Revolution"; cyclePeriods[2] = 3836.15; Labels[2] = "Boattini Comet Period"; cyclePeriods[3] = 3489.4; Labels[3] = "Kowal Comet Period"; cyclePeriods[4] = 3120.75; Labels[4] = "Catalina Comet Period"; //cyclePeriods[6] = 3036.8; Labels[6] = "Tenagra Comet Period"; //cyclePeriods[7] = 2730.2; Labels[7] = "Christensen Comet Period"; cyclePeriods[5] = 2686.4; Labels[5] = "Lagerkvist Comet Period"; cyclePeriods[6] = 1981.95; Labels[6] = "Kowalski Comet Period"; cyclePeriods[7] = 1401.6; Labels[7] = "Stattmayer Comet Period"; cyclePeriods[8] = 1361.45; Labels[8] = "Meyer Comet Period"; cyclePeriods[9] = 686.2; Labels[9] = "Mars Revolution"; cyclePeriods[10] = 521.95; Labels[10] = "IRAS Comet Period"; cyclePeriods[11] = 365.26; Labels[11] = "Earth Revolution"; makeCanvas(); Cycles[0].epsilon = 0; for(int i=1; i<Cycles.length; i++) { float epsilon = random((i-1)*0.5, i*0.5); int sign; if(i%2==1) sign = 1; else sign = -1; Cycles[i].epsilon = sign*epsilon; } surface.setSize(globalWidth, globalHeight); GillSans = createFont("GillSans", 24); textFont(GillSans); } void draw() { calculateTime(); for(int i=0; i<Cycles.length; i++) { float cyclePos = Cycles[i].calculateCyclePos(syncYear, year, month, day, hour, minute); Cycles[i].updateColor((hour()+hueHour)%24, cyclePos); Cycles[i].drawCycle(); } if(drawHourFrame>=0) { noStroke(); fill(215, 50); rect(0, 0, globalWidth, globalHeight); textAlign(LEFT); String text; if(minute()<10 && (hour()+hueHour)%24<10) text = "0"+((hour()+hueHour)%24)+":0"+minute(); else if(minute()<10 && (hour()+hueHour)%24>=10) text = ((hour()+hueHour)%24)+":0"+minute(); else if(minute()>=10 && (hour()+hueHour)%24<10) text = "0"+((hour()+hueHour)%24)+":"+minute(); else text = ((hour()+hueHour)%24)+":"+minute(); float Width = textWidth(text); int xOffset = round((globalWidth - Width)/2.0); fill(255, 255); text(text, xOffset, round(globalHeight/2.5)); drawHourFrame++; if(drawHourFrame>60) drawHourFrame = -1; } if(drawSpeedFrame>=0) { noStroke(); fill(215, 50); rect(0, 0, globalWidth, globalHeight); textAlign(LEFT); String text = "x "+speed; float Width = textWidth(text); int xOffset = round((globalWidth - Width)/2.0); fill(255, 255); text(text, xOffset, round(globalHeight/2.5)); drawSpeedFrame++; if(drawSpeedFrame>60) drawSpeedFrame = -1; } if(numKeyPressed%2==1 && drawHourFrame==-1 && drawSpeedFrame==-1) { for(int i=0; i<Cycles.length; i++) { if(Cycles[i].isMouseOver(mouseX, mouseY)) { Cycles[i].showLabel(globalWidth, globalHeight); } } } if(drawInstructionFrame==true) { openingInstructions(); } } void keyPressed() { if(key=='l') numKeyPressed++; if(key=='f') { speed = speed*2; drawSpeedFrame = 0; } if(key=='s') { speed = speed/2; drawSpeedFrame = 0; } if(key=='h') { hueHour++; drawHourFrame = 0; } if(key=='g') { hueHour--; drawHourFrame = 0; } if(key=='o') { calculateOptimum(); drawSpeedFrame = 0; } if(key=='p') { speed = 1.0; drawSpeedFrame = 0; } if(key==ENTER || key==RETURN) { drawInstructionFrame = false; GillSans = createFont("GillSans", 32); textFont(GillSans); } if(key=='z') { String filename = Cycles.length + "_Cycles_" + nf(((hour()+hueHour)%24), 2) + "00.png"; saveFrame("frames/" + filename); println("Saved: " + filename); } } void openingInstructions() { noStroke(); fill(215, 50); rect(0, 0, globalWidth, globalHeight); String text1 = "This clock phases with the cosmos. Through the subtle shifts in hue, watch as the world unfolds in every widening asynchronicity."; String text2 = "To toggle the labels of the cosmic cycles:"; String text3 = "Press 'l' and hover over the rectangles"; String text4 = "To control the speed of this clock's journey through time:"; String text5 = "Press 'f' or 's'"; String text6 = "To reach the optimum speed for the human eye to appreciate the chromatic counterpoint:"; String text7 = "Press 'o' (and 'p' to undo)"; String text8 = "To move forwards and backwards in increments of an hour:"; String text9 = "Press 'h' and 'g' respectively"; String text10 = "To capture a moment that, in all likelihood, will never return quite the same:"; String text11 = "Press'z'"; String text12 = "Press 'Enter' to proceed"; fill(255, 255); textAlign(LEFT); text(text1, 50, 50, globalWidth-100, globalHeight-100); text(text2, 100, 200, globalWidth/2-100, globalHeight-200); text(text4, 100, 300, globalWidth/2-100, globalHeight-300); text(text6, 100, 400, globalWidth/2-100, globalHeight-400); text(text8, 100, 525, globalWidth/2-100, globalHeight-525); text(text10, 100, 625, globalWidth/2-100, globalHeight-625); textAlign(RIGHT); text(text3, globalWidth/2, 200, (globalWidth/2)-100, globalHeight-200); text(text5, globalWidth/2, 300, (globalWidth/2)-100, globalHeight-300); text(text7, globalWidth/2, 400, (globalWidth/2)-100, globalHeight-400); text(text9, globalWidth/2, 525, (globalWidth/2)-100, globalHeight-525); text(text11, globalWidth/2, 625, (globalWidth/2)-100, globalHeight-625); text(text12, globalWidth-50, globalHeight-50); } void findYearMonthAndDay(int dayIncrement) { int[] monthDays = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; dayIncrement = dayIncrement - (monthDays[month-1] - day); if(dayIncrement<=0) day = monthDays[month-1] + dayIncrement; else { while(dayIncrement>0) { month = (month%12)+1; if(month==1) year++; if(year%4==0 && month==2) dayIncrement -= 29; else dayIncrement -= monthDays[month-1]; } if(year%4==0 && month==2) day = 29 + dayIncrement; else day = monthDays[month-1] + dayIncrement; } } void calculateTime() { int offset = floor((millis()-lastMinute)*speed/60000); if(offset>=1) lastMinute = millis(); minute += offset; if (minute>=60){ hour = hour+(minute/60); minute %= 60;} int dayIncrement = 0; if(hour>=24) { dayIncrement = hour/24; hour %= 24; } findYearMonthAndDay(dayIncrement); } void calculateOptimum() { int median = floor(cyclePeriods.length/2); float optimalSeconds = 10.0; speed = 1440*(60/optimalSeconds)*cyclePeriods[median]; //speed = 31536000.0; } |