// audio setup =================================================== // number of ticks (steps) on clock 12 => int NUM_TICKS; // beat (smallest division; dur between ticks) 1000::ms => dur BEAT; // wind acceleration value that goes with beat duration .1 => float WIND; // starting value for playback rate of each step 1 => float f; // float for reverse tracks -1 => float REVERSE_RATE; // update rate for pos 10::ms => dur POS_RATE; // increment per step POS_RATE/BEAT => float posInc; // global variables for animation int currentTick; // which tick we are at // will take on fractional values for smooth animation[0-NUM_TICKS] float playheadPos; // a sequence (one of many ways to "sequence") // this ones treats ticks as a step sequencer; it will store the current rate, gain, and reverb in the 12 step sequence float seqRate[NUM_TICKS]; float seqGain[NUM_TICKS]; float seqRevb[NUM_TICKS]; // sound buffers, multiple SndBuf bufs[NUM_TICKS]; NRev reverb => dac; // initialize for (int i; i < NUM_TICKS; i++) { 1 => seqGain[i]; 1 => seqRate[i]; .7 => seqRevb[i]; } // connect them for( int i; i < NUM_TICKS; i++) { // connect to dac bufs[i] => reverb; // load sound if( i == 0 || i == 3 || i == 6) "B.wav" => bufs[i].read; else if( i == 1 || i == 4 || i == 7) "A.wav" => bufs[i].read; else if( i == 2 || i == 5) "F.wav" => bufs[i].read; else if (i == 8) "D.wav" => bufs[i].read; else "E.wav" => bufs[i].read; // silence 0 => bufs[i].gain; } // function for playing sound fun void play( int which, float gain, float rate, float rev) { // restart 0 => bufs[which].pos; // set gain gain => bufs[which].gain; // set rate rate => bufs[which].rate; } fun void playSequence() { while (true) { // play the current thing play( currentTick, seqGain[currentTick], seqRate[currentTick], seqRevb[currentTick] ); // sync with discrete grid position currentTick => playheadPos; // advance time by duration of one beat BEAT => now; // increment to next tick currentTick++; // wrap back to beginning if( currentTick >= NUM_TICKS ) 0 => currentTick; } } spork ~ playSequence(); // function for playing wave files in reverse for background fun void playSndBuf(string filename, float r) { SndBuf mbox => dac; filename => mbox.read; // play in reverse on loop while( true ) { r => mbox.rate; 2 => mbox.gain; if (r < 1) { mbox.samples() -1 => mbox.pos; } else { mbox.samples() +1 => mbox.pos; } 5::second => now; } } // playing the audio using spork and loop spork ~ playSndBuf("SongofHealing.wav", REVERSE_RATE); spork ~ playSndBuf("F.wav", REVERSE_RATE); spork ~ playSndBuf("A.wav", REVERSE_RATE); spork ~ playSndBuf("B.wav", REVERSE_RATE); // player interactive audio portion ================================================== fun void changeTempo() { while( true ) { if ( KB.isKeyDown(KB.KEY_RIGHT) ) { BEAT * .9 => BEAT; // accelerate tempo REVERSE_RATE * .9 => REVERSE_RATE; // accelerate reverse playback WIND * 1.1 => WIND; // accelerate wind } else if ( KB.isKeyDown(KB.KEY_LEFT) ) { BEAT * 1.1 => BEAT; // decelerate tempo REVERSE_RATE * 1.1 => REVERSE_RATE; // decelerate reverse playback WIND * .9 => WIND; // decelerate wind } BEAT => now; } } spork ~ changeTempo(); // scene setup ================================================================= // uncomment to fullscreen GG.fullscreen(); GScene scene; GCamera camera; scene.backgroundColor(@(0, 0, 0)); // camera.clip(1.5, 1000); camera.posZ(5); 1.5 => float r; // set radius of clock face here // setting ================================================================== // would be nice to have a prop in front that cycles in time with clock? GCircle gear1, gear2, gear3, gear4, gear5, gear6, gear7, gear8; GGen gears --> scene; gears.posZ(-.5); FileTexture prop, prop2; prop.path("WGear.png"); gear1.mat().diffuseMap(prop); gear2.mat().diffuseMap(prop); gear3.mat().diffuseMap(prop); gear4.mat().diffuseMap(prop); gear5.mat().diffuseMap(prop); gear6.mat().diffuseMap(prop); gear7.mat().diffuseMap(prop); gear8.mat().diffuseMap(prop); // toggling each gear's transparency 1 => gear1.mat().transparent; .99 => gear1.mat().alpha; 1 => gear2.mat().transparent; .99 => gear2.mat().alpha; 1 => gear3.mat().transparent; .99 => gear3.mat().alpha; 1 => gear4.mat().transparent; .99 => gear4.mat().alpha; 1 => gear5.mat().transparent; .99 => gear5.mat().alpha; 1 => gear6.mat().transparent; .99 => gear6.mat().alpha; 1 => gear7.mat().transparent; .99 => gear7.mat().alpha; 1 => gear8.mat().transparent; .99 => gear8.mat().alpha; 1.7 => float g; // float for storing gear scale gear1 --> gears; gear1.sca( @(g,g,g)); gear1.posX(-3); gear1.posY(-3); gear2 --> gears; gear2.sca( @(g,g,g)); gear2.posX(3); gear2.posY(-3); gear3 --> gears; gear3.sca( @(g,g,g)); gear3.posX(3); gear3.posY(3); gear4 --> gears; gear4.sca( @(g,g,g)); gear4.posX(-3); gear4.posY(3); gear5 --> gears; gear5.sca( @(g,g,g)); gear5.posX(Math.sqrt(18)); gear6 --> gears; gear6.sca( @(g,g,g)); gear6.posX(-Math.sqrt(18)); gear7 --> gears; gear7.sca( @(g,g,g)); gear7.posY(Math.sqrt(18)); gear8 --> gears; gear8.sca( @(g,g,g)); gear8.posY(-Math.sqrt(18)); // Gears for selecting which note in sequence to modify ========================== GCircle s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12; GGen steps --> scene; steps.posZ(1); FileTexture selector; selector.path("Flake.png"); s1.mat().diffuseMap(selector); s2.mat().diffuseMap(selector); s3.mat().diffuseMap(selector); s4.mat().diffuseMap(selector); s5.mat().diffuseMap(selector); s6.mat().diffuseMap(selector); s7.mat().diffuseMap(selector); s8.mat().diffuseMap(selector); s9.mat().diffuseMap(selector); s10.mat().diffuseMap(selector); s11.mat().diffuseMap(selector); s12.mat().diffuseMap(selector); 1 => s1.mat().transparent; .99 => s1.mat().alpha; 1 => s2.mat().transparent; .99 => s2.mat().alpha; 1 => s3.mat().transparent; .99 => s3.mat().alpha; 1 => s4.mat().transparent; .99 => s4.mat().alpha; 1 => s5.mat().transparent; .99 => s5.mat().alpha; 1 => s6.mat().transparent; .99 => s6.mat().alpha; 1 => s7.mat().transparent; .99 => s7.mat().alpha; 1 => s8.mat().transparent; .99 => s8.mat().alpha; 1 => s9.mat().transparent; .99 => s9.mat().alpha; 1 => s10.mat().transparent; .99 => s10.mat().alpha; 1 => s11.mat().transparent; .99 => s11.mat().alpha; 1 => s12.mat().transparent; .99 => s12.mat().alpha; 0.5 => float g2; s1 --> steps; s1.sca( @(g2,g2,g2)); s1.posX(1.5*r*Math.cos(2*pi/12*2)); s1.posY(1.5*r*Math.sin(2*pi/12*2)); s2 --> steps; s2.sca( @(g2,g2,g2)); s2.posX(1.5*r*Math.cos(2*pi/12)); s2.posY(1.5*r*Math.sin(2*pi/12)); s3 --> steps; s3.sca( @(g2,g2,g2)); s3.posX(1.5*r*Math.cos(0)); s3.posY(1.5*r*Math.sin(0)); s4 --> steps; s4.sca( @(g2,g2,g2)); s4.posX(1.5*r*Math.cos(2*pi/12*-1)); s4.posY(1.5*r*Math.sin(2*pi/12*-1)); s5 --> steps; s5.sca( @(g2,g2,g2)); s5.posX(1.5*r*Math.cos(2*pi/12*-2)); s5.posY(1.5*r*Math.sin(2*pi/12*-2)); s6 --> steps; s6.sca( @(g2,g2,g2)); s6.posX(1.5*r*Math.cos(2*pi/12*-3)); s6.posY(1.5*r*Math.sin(2*pi/12*-3)); s7 --> steps; s7.sca( @(g2,g2,g2)); s7.posX(1.5*r*Math.cos(2*pi/12*-4)); s7.posY(1.5*r*Math.sin(2*pi/12*-4)); s8 --> steps; s8.sca( @(g2,g2,g2)); s8.posX(1.5*r*Math.cos(2*pi/12*-5)); s8.posY(1.5*r*Math.sin(2*pi/12*-5)); s9 --> steps; s9.sca( @(g2,g2,g2)); s9.posX(1.5*r*Math.cos(2*pi/12*-6)); s9.posY(1.5*r*Math.sin(2*pi/12*-6)); s10 --> steps; s10.sca( @(g2,g2,g2)); s10.posX(1.5*r*Math.cos(2*pi/12*-7)); s10.posY(1.5*r*Math.sin(2*pi/12*-7)); s11 --> steps; s11.sca( @(g2,g2,g2)); s11.posX(1.5*r*Math.cos(2*pi/12*-8)); s11.posY(1.5*r*Math.sin(2*pi/12*-8)); s12 --> steps; s12.sca( @(g2,g2,g2)); s12.posX(1.5*r*Math.cos(2*pi/12*-9)); s12.posY(1.5*r*Math.sin(2*pi/12*-9)); // Clock setup =================================================================== GCircle face; // face of clock GPlane hourHand; // hour hand GPlane minuteHand; // minute hand GPlane secondHand; // second hand GCircle pin; // gear to cover up center GCircle frame; // gear surrounding outside of clock to give more activity GCircle frame2; // more frames GCircle frame3; GCircle frame4; GCircle frame5; // creating inputs for multiple textures FileTexture tex1, tex2, tex3, tex4, tex5, tex6, tex7; // all IO happens here, at program start tex1.path("ClockFaceEdit.png"); tex2.path("HourHand.png"); tex3.path("MinuteHand.png"); tex4.path("SecondHand.png"); tex5.path("BGear.png"); tex6.path("WGear.png"); tex7.path("FancyGear.png"); face.sca( @(r, r, r)); // Setting texture of clock face then putting into scene face.mat().diffuseMap(tex1); hourHand.mat().diffuseMap(tex2); minuteHand.mat().diffuseMap(tex3); secondHand.mat().diffuseMap(tex4); pin.mat().diffuseMap(tex7); frame.mat().diffuseMap(tex6); frame2.mat().diffuseMap(tex5); frame3.mat().diffuseMap(tex6); frame4.mat().diffuseMap(tex5); frame5.mat().diffuseMap(tex6); // Scaling hour hand and toggling transparency hourHand.sca( @(r, r*.25, 0)); hourHand.rotateZ(-pi/2); hourHand.posZ(0.13); 1 => hourHand.mat().transparent; 1 => hourHand.mat().alpha; // Scaling minute hand and toggling transparency minuteHand.sca( @(r, r*.1, 0)); minuteHand.rotateZ(-pi/2); minuteHand.posZ(0.12); 1 => minuteHand.mat().transparent; .99 => minuteHand.mat().alpha; // Scaling second hand and toggling transparency secondHand.sca( @(r*.7, r*.1, 0)); secondHand.rotateZ(-pi/2); secondHand.posZ(0.1); 1 => secondHand.mat().transparent; .99 => secondHand.mat().alpha; // putting pin in place pin.sca( @(r*.1, r*.1, r*.1)); pin.posZ(0.14); 1 => pin.mat().transparent; .99 => pin.mat().alpha; // putting frame in place frame.sca( @(r*1.1, r*1.1, r*1.1)); frame.posZ(-1); 1 => frame.mat().transparent; .99 => frame.mat().alpha; // putting frame2 in place frame2.sca( @(r*1.4, r*1.4, r*1.4)); frame2.posZ(-1.1); 1 => frame2.mat().transparent; .99 => frame2.mat().alpha; // putting frame3 in place frame3.sca( @(r*1.5, r*1.5, r*1.5)); frame3.posZ(-1.2); 1 => frame3.mat().transparent; .99 => frame3.mat().alpha; // putting frame4 in place frame4.sca( @(r*1.7, r*1.7, r*1.7)); frame4.posZ(-1.3); 1 => frame4.mat().transparent; .99 => frame4.mat().alpha; // putting frame5 in place frame5.sca( @(r*1.9, r*1.9, r*1.9)); frame5.posZ(-1.4); 1 => frame5.mat().transparent; .99 => frame5.mat().alpha; face --> scene; hourHand --> face; minuteHand --> face; secondHand --> face; pin --> face; frame --> face; frame2 --> face; frame3 --> face; frame4 --> face; frame5 --> face; // have second hand sync with music box sound fun void rotateWithTick() { while (true) { // rotate second hand 1/12th of 2pi as playhead secondHand.rotateZ(2*pi/NUM_TICKS); // rotate pin and frame in reverse pin.rotateZ(-2*pi/(NUM_TICKS*4)); // rotate frame, outer ones rotate slower frame.rotateZ(-2*pi/NUM_TICKS); // Gears, as well as individual gears gears.rotateZ(GG.dt()); gear1.rotateZ(2*pi/NUM_TICKS); gear2.rotateZ(2*pi/NUM_TICKS); gear3.rotateZ(2*pi/NUM_TICKS); gear4.rotateZ(2*pi/NUM_TICKS); gear5.rotateZ(2*pi/NUM_TICKS); gear6.rotateZ(2*pi/NUM_TICKS); gear7.rotateZ(2*pi/NUM_TICKS); gear8.rotateZ(2*pi/NUM_TICKS); // move beat forward BEAT => now; } } spork ~ rotateWithTick(); fun void handMovement() { while( true ) { if ( KB.isKeyDown(KB.KEY_RIGHT) ) { hourHand.rotateZ(GG.dt()/6); // move hands clockwise minuteHand.rotateZ(GG.dt()/2); } else if ( KB.isKeyDown(KB.KEY_LEFT) ) { hourHand.rotateZ(-GG.dt()/6); // move hands counter-clockwise minuteHand.rotateZ(-GG.dt()/2); } if ((hourHand.rotZ() < 0.6 && hourHand.rotZ() > 0.38) || (hourHand.rotZ() < -0.38 && hourHand.rotZ() > -0.6)) { // Yay!!! Finally found a way to adjust pitch!!! if (KB.isKeyDown(KB.KEY_DOWN)) { 0.9 * f => seqRate[0]; 0.9 * f => seqRate[4]; s1.sca( @(g2*0.7,g2*0.7,g2*0.7)); s1.rotateX(GG.dt()*1.5); s5.sca( @(g2*0.7,g2*0.7,g2*0.7)); s5.rotateX(GG.dt()*1.5); } else if (KB.isKeyDown(KB.KEY_UP)) { 1.1 * f => seqRate[0]; 1.1 * f => seqRate[4]; s1.sca( @(g2*1.3,g2*1.3,g2*1.3)); s1.rotateY(GG.dt()*1.5); s5.sca( @(g2*1.3,g2*1.3,g2*1.3)); s5.rotateY(GG.dt()*1.5); } } else if ((hourHand.rotZ() < 0.38 && hourHand.rotZ() > 0.17) || (hourHand.rotZ() < -0.17 && hourHand.rotZ() > -0.38)) { if (KB.isKeyDown(KB.KEY_DOWN)) { 0.9 * f => seqRate[1]; 0.9 * f => seqRate[3]; s2.rotateX(GG.dt()*1.5); s2.sca( @(g2*0.7,g2*0.7,g2*0.7)); s4.rotateX(GG.dt()*1.5); s4.sca( @(g2*0.7,g2*0.7,g2*0.7)); } else if (KB.isKeyDown(KB.KEY_UP)) { 1.1 * f => seqRate[1]; 1.1 * f => seqRate[3]; s2.rotateY(GG.dt()*1.5); s2.sca( @(g2*1.3,g2*1.3,g2*1.3)); s4.rotateY(GG.dt()*1.5); s4.sca( @(g2*1.3,g2*1.3,g2*1.3)); } } else if (hourHand.rotZ() < 0.17 && hourHand.rotZ() > -0.17) { if (KB.isKeyDown(KB.KEY_DOWN)) { 0.9 * f => seqRate[2]; s3.rotateX(GG.dt()*1.5); s3.sca( @(g2*0.7,g2*0.7,g2*0.7)); } else if (KB.isKeyDown(KB.KEY_UP)) { 1.1 * f => seqRate[2]; s3.rotateY(GG.dt()*1.5); s3.sca( @(g2*1.3,g2*1.3,g2*1.3)); } } else if ((hourHand.rotZ() < 0.8 && hourHand.rotZ() > 0.6) || (hourHand.rotZ() < -0.6 && hourHand.rotZ() > -0.8)) { if (KB.isKeyDown(KB.KEY_DOWN)) { 0.9 * f => seqRate[5]; 0.9 * f => seqRate[11]; s6.rotateX(GG.dt()*1.5); s6.sca( @(g2*0.7,g2*0.7,g2*0.7)); s12.rotateX(GG.dt()*1.5); s12.sca( @(g2*0.7,g2*0.7,g2*0.7)); } else if (KB.isKeyDown(KB.KEY_UP)) { 1.1 * f => seqRate[5]; 1.1 * f => seqRate[11]; s6.rotateY(GG.dt()*1.5); s6.sca( @(g2*1.3,g2*1.3,g2*1.3)); s12.rotateY(GG.dt()*1.5); s12.sca( @(g2*1.3,g2*1.3,g2*1.3)); } } else if ((hourHand.rotZ() < 0.92 && hourHand.rotZ() > 0.8) || (hourHand.rotZ() > -0.92 && hourHand.rotZ() < -0.8)) { if (KB.isKeyDown(KB.KEY_DOWN)) { 0.9 * f => seqRate[6]; 0.9 * f => seqRate[10]; s7.rotateX(GG.dt()*1.5); s7.sca( @(g2*0.7,g2*0.7,g2*0.7)); s11.rotateX(GG.dt()*1.5); s11.sca( @(g2*0.7,g2*0.7,g2*0.7)); } else if (KB.isKeyDown(KB.KEY_UP)) { 1.1 * f => seqRate[6]; 1.1 * f => seqRate[10]; s7.rotateY(GG.dt()*1.5); s7.sca( @(g2*1.3,g2*1.3,g2*1.3)); s11.rotateY(GG.dt()*1.5); s11.sca( @(g2*1.3,g2*1.3,g2*1.3)); } } else if ((hourHand.rotZ() < 0.98 && hourHand.rotZ() > 0.92) || (hourHand.rotZ() < -0.92 && hourHand.rotZ() > -0.98)) { if (KB.isKeyDown(KB.KEY_DOWN)) { 0.9 * f => seqRate[7]; 0.9 * f => seqRate[9]; s8.rotateX(GG.dt()*1.5); s8.sca( @(g2*0.7,g2*0.7,g2*0.7)); s10.rotateX(GG.dt()*1.5); s10.sca( @(g2*0.7,g2*0.7,g2*0.7)); } else if (KB.isKeyDown(KB.KEY_UP)) { 1.1 * f => seqRate[7]; 1.1 * f => seqRate[9]; s8.rotateY(GG.dt()*1.5); s8.sca( @(g2*1.3,g2*1.3,g2*1.3)); s10.rotateY(GG.dt()*1.5); s10.sca( @(g2*1.3,g2*1.3,g2*1.3)); } } else { if (KB.isKeyDown(KB.KEY_DOWN)) { 0.9 * f => seqRate[8]; s9.rotateX(GG.dt()*1.5); s9.sca( @(g2*0.7,g2*0.7,g2*0.7)); } else if (KB.isKeyDown(KB.KEY_UP)) { 1.1 * f => seqRate[8]; s9.rotateY(GG.dt()*1.5); s9.sca( @(g2*1.3,g2*1.3,g2*1.3)); } } 1::ms => now; } } spork ~ handMovement(); // snowstorm setup ============================================ // setup textures ============================================= FileTexture sprites[6]; for (int i; i < 6; i++) { sprites[i].path(me.dir() + "data/snowflake" + (i+1) + ".png"); } // materials ============================================ PointMaterial snowflakeMats[6]; // default material values 85.0 => float DEFAULT_POINT_SIZE; 0.75 => float DEFAULT_ALPHA; @(.741, .894, 1.0) => vec3 DEFAULT_COLOR; // initialize materials for (int i; i < snowflakeMats.size(); i++) { snowflakeMats[i] @=> PointMaterial @ mat; mat.pointSize(DEFAULT_POINT_SIZE); mat.pointSprite(sprites[i]); mat.attenuatePoints(true); mat.transparent(true); mat.alpha(DEFAULT_ALPHA); mat.color(DEFAULT_COLOR); } // geometry ============================================= 10000 => int NUM_SNOWFLAKES; 14 => int SNOWFLAKE_SPREAD; vec3 snowflakePos[NUM_SNOWFLAKES]; // initialize snowflake positions for (0 => int i; i < snowflakePos.size(); i++) { @( Math.random2f(-SNOWFLAKE_SPREAD, SNOWFLAKE_SPREAD), // x Math.random2f(-SNOWFLAKE_SPREAD, SNOWFLAKE_SPREAD), // y Math.random2f(-SNOWFLAKE_SPREAD, SNOWFLAKE_SPREAD) // z ) => snowflakePos[i]; } CustomGeometry snowflakeGeo; snowflakeGeo.positions(snowflakePos); // meshes ============================================== GGen snowstorm --> scene; GMesh snowflakes[6]; // initialize snowflake meshes for (int i; i < 6; i++) { snowflakes[i] @=> GMesh @ snowflake; snowflakes[i].set(snowflakeGeo, snowflakeMats[i]); snowflakes[i] --> snowstorm; // randomize rotation .15 => float s; Math.random2f(0, s*Math.TWO_PI) => snowflake.rotX; Math.random2f(0, s*Math.TWO_PI) => snowflake.rotY; Math.random2f(0, s*Math.TWO_PI) => snowflake.rotZ; } // camera update fun void updateCamera(float dt) { // calculate position of mouse relative to center of window // (GG.windowWidth() / 2.0 - GG.mouseX()) / 100.0 => float mouseX; // (GG.windowHeight() / 2.0 - GG.mouseY()) / 100.0 => float mouseY; // update camera position 5 * Math.cos(-.18 * (now/second)) + 10 => float radius; radius * 1.2 => GG.camera().posZ; // look at origin GG.camera().lookAt( @(0,0,0) ); } // graphics render loop with simple sequence =============================================== 0.1 => float camSpeed; 1.5 => float camMovement; while( true ) { // rotate frame, outer ones rotate slower frame2.rotateZ(GG.dt()*.9); frame3.rotateZ(-GG.dt()*.7); frame4.rotateZ(GG.dt()*.5); frame5.rotateZ(-GG.dt()*.3); // rotate snowflakes at constant rate along Y and Z s1.rotateX(GG.dt()); s1.rotateZ(GG.dt()); s1.rotateY(GG.dt()); s2.rotateX(GG.dt()); s2.rotateZ(GG.dt()); s2.rotateY(GG.dt()); s3.rotateX(GG.dt()); s3.rotateZ(GG.dt()); s3.rotateY(GG.dt()); s4.rotateX(GG.dt()); s4.rotateZ(GG.dt()); s4.rotateY(GG.dt()); s5.rotateX(GG.dt()); s5.rotateZ(GG.dt()); s5.rotateY(GG.dt()); s6.rotateX(GG.dt()); s6.rotateZ(GG.dt()); s6.rotateY(GG.dt()); s7.rotateX(GG.dt()); s7.rotateZ(GG.dt()); s7.rotateY(GG.dt()); s8.rotateX(GG.dt()); s8.rotateZ(GG.dt()); s8.rotateY(GG.dt()); s9.rotateX(GG.dt()); s9.rotateZ(GG.dt()); s9.rotateY(GG.dt()); s10.rotateX(GG.dt()); s10.rotateZ(GG.dt()); s10.rotateY(GG.dt()); s11.rotateX(GG.dt()); s11.rotateZ(GG.dt()); s11.rotateY(GG.dt()); s12.rotateX(GG.dt()); s12.rotateZ(GG.dt()); s12.rotateY(GG.dt()); // snowflake update for (int i; i < snowflakes.size(); i++) { Math.map(i, 0, snowflakes.size(), 1, 2.5) * WIND => float snowflakeRotRate; snowflakeRotRate * GG.dt() => snowflakes[i].rotateY; } // camera controls (inverted so it looks like you're moving the snowflakes!) GG.mouseX() / GG.windowWidth() => float mouseX; GG.mouseY() / GG.windowHeight() => float mouseY; // normalize mouse coordinates to [-1, 1], scale by camMovement -camMovement * (mouseX * 3.0 - 1.0) => mouseX; camMovement * (mouseY * 3.0 - 1.0) => mouseY; camSpeed * (mouseX - camera.posX()) + camera.posX() => camera.posX; camSpeed * (mouseY - camera.posY()) + camera.posY() => camera.posY; camera.lookAt( scene.pos() ); updateCamera(GG.dt()); // next graphics frame GG.nextFrame() => now; } // have ring of 12 gears surrounding clock // how fast it spins corresponds to frequency // create an array that stores all 12 different sound bufs