8 => int N_STEPS; Math.round(89 * 2.5)$int => int TEMPO; 2 => int MOUSE_DEVICE; .6 => float SQUARE_WIDTH; // Common Vars 1::minute / TEMPO => dur normalT; normalT * 2 / 3 => dur alternateT; normalT => dur T; 0 => int timeIndex; Event timeAdvance; Event timeStart; Event timeStop; // Graphics -2.85 => float BASES_SELECT_INTERFACE_X; -.5 => float BASES_INTERFACE_Y; 2.5 => float TIME_SEQUENCE_DISPLAY_Y; GGen interfaceDolly --> GG.scene(); GG.scene().light().specular(@(0,0,0)); //GG.fullscreen(); GG.scene().backgroundColor(@(1,1,1)); GG.camera().orthographic(); -10.0 => float interfaceZ; // mouse depth in world space class GSquare extends GMesh { GLines square --> this; square.mat().color(@(0,0,0)); // TODO: necessary? float width; init(SQUARE_WIDTH); // initialize a circle fun void init(float widthIn) { widthIn => width; // positions of our circle vec3 pos[5]; [-.5, .5, .5, -.5, -.5] @=> float xCoords[]; [.5, .5, -.5, -.5, .5] @=> float yCoords[]; for (int i; i < pos.size(); i++) { xCoords[i] * width => pos[i].x; yCoords[i] * width => pos[i].y; interfaceZ => pos[i].z; } // set positions square.geo().positions(pos); } fun int inside(float x, float y) { this.posWorld() => vec3 globalPos; x < globalPos.x + width / 2 => int rightBound; x > globalPos.x - width / 2 => int leftBound; y < globalPos.y + width / 2 => int topBound; y > globalPos.y - width / 2 => int bottomBound; return rightBound && leftBound && topBound && bottomBound; } } class GArray extends GGen { int length; GSquare squares[0]; float width; init(N_STEPS, SQUARE_WIDTH); fun void init(int lengthIn, float widthIn) { lengthIn => length; GSquare sqrArr[length] @=> squares; widthIn => width; for (0 => int i; i < length; i++) { squares[i].init(width); (i + .5 - length / 2.) * width => squares[i].posX; squares[i] --> this; } } } class GButton extends GGen { 64 => int BUTTON_CIRCLE_NUM_SEGMENTS; .7 => float COVER_WIDTH_RATIO; .75 => float BG_CIRCLE_WIDTH_RATIO; 0 => float CIRCLE_HOVER_Z; float width; GSquare outline --> this; 0 => int toggledOn; 0 => int fill; GCircle bgCircle; bgCircle.mat().color(@(0,0,0)); GCircle cover; init(SQUARE_WIDTH); fun void init(float widthIn) { widthIn => width; outline.init(width); CircleGeometry bgCircGeo; bgCircGeo.set(width * BG_CIRCLE_WIDTH_RATIO / 2, BUTTON_CIRCLE_NUM_SEGMENTS, 0, 2 * Math.PI); bgCircle.set(bgCircGeo, bgCircle.mat()); bgCircle.posZ(CIRCLE_HOVER_Z); CircleGeometry coverGeo; coverGeo.set(width * COVER_WIDTH_RATIO / 2, BUTTON_CIRCLE_NUM_SEGMENTS, 0, 2 * Math.PI); cover.set(coverGeo, cover.mat()); cover.posZ(CIRCLE_HOVER_Z); } fun int processClick(float x, float y) { if (outline.inside(x, y)) { toggle(); return true; } else { return false; } } fun void toggle() { !toggledOn => toggledOn; if (toggledOn) { bgCircle --> this; if (!fill) { cover --> this; } } else { bgCircle --< this; if (!fill) { cover --< this; } } } fun void toggleFill() { !fill => fill; if (toggledOn) { if (fill) { cover --< this; } else { cover --> this; } } } } class GButtonArray extends GGen { int length; GButton buttons[0]; float width; init(N_STEPS, SQUARE_WIDTH); fun void init(int lengthIn, float widthIn) { lengthIn => length; GButton btnArr[length] @=> buttons; widthIn => width; for (0 => int i; i < length; i++) { buttons[i].init(width); (i + .5 - length / 2.) * width => buttons[i].posX; buttons[i] --> this; } } } GButtonArray selectInterface --> interfaceDolly; selectInterface.rotZ(Math.pi / 2); selectInterface.posX(BASES_SELECT_INTERFACE_X); selectInterface.posY(BASES_INTERFACE_Y); for (0 => int i; i < N_STEPS; i++) { selectInterface.buttons[i].toggleFill(); } class GGrid extends GGen { int length; GButtonArray buttonArrs[0]; float width; init(N_STEPS, SQUARE_WIDTH); fun void init(int lengthIn, float widthIn) { lengthIn => length; GButtonArray btnGrid[length] @=> buttonArrs; widthIn => width; for (0 => int i; i < length; i++) { buttonArrs[i].init(length, width); (i + .5 - length / 2.) * width => buttonArrs[i].posY; buttonArrs[i] --> this; } } } GGrid basisInterface --> interfaceDolly; basisInterface.posY(BASES_INTERFACE_Y); GButtonArray timeSequenceDisplay --> interfaceDolly; timeSequenceDisplay.posY(TIME_SEQUENCE_DISPLAY_Y); for (0 => int i; i < N_STEPS; i++) { timeSequenceDisplay.buttons[i].toggleFill(); } GLines pointer; pointer.geo().positions([@(-1,1,interfaceZ),@(0,0,interfaceZ),@(1,1,interfaceZ)]); pointer.mat().color(@(0,0,0)); pointer.sca(.15); pointer.posY(TIME_SEQUENCE_DISPLAY_Y + SQUARE_WIDTH * .75); fun void pointerVisiblizer() { while (true) { timeStart => now; pointer --> interfaceDolly; timeStop => now; pointer --< interfaceDolly; } } spork ~ pointerVisiblizer(); fun void movePointer() { while (true) { timeAdvance => now; (timeIndex + .5 - N_STEPS / 2.) * SQUARE_WIDTH => pointer.posX; } } spork ~ movePointer(); GButton onButton; onButton.pos(@(BASES_SELECT_INTERFACE_X,TIME_SEQUENCE_DISPLAY_Y,interfaceZ)); onButton --> interfaceDolly; onButton.toggleFill(); GLines connectors[N_STEPS][N_STEPS]; // Audio UGen noteBus => dac; UGen kickBus => dac; noteBus => Gain noteRevGain => NRev rev => dac; kickBus => Gain kickRevGain => rev => dac; 0 => float normalRevInputGain; 1 => float alternateRevInputGain; rev.gain(.1); rev.mix(1); noteRevGain.gain(normalRevInputGain); kickRevGain.gain(normalRevInputGain); .6::second => dur normalMelNoteDur; .4::second => dur alternateMelNoteDur; normalMelNoteDur => dur melNoteDur; .2 => float normalMelA; .2 => float normalMelD; .4 => float normalMelS; .1 => float normalMelR; .15 => float altMelA; .15 => float altMelD; .3 => float altMelS; .1 => float altMelR; normalMelA => float melA; normalMelD => float melD; normalMelS => float melS; normalMelR => float melR; .23 => float normalOscGain; .3 => float maxOscGain; normalOscGain => float oscGain; 60 => float BASE_PITCH; [0., 2, 4, 5, 7, 9, 11, 12] @=> float NORMAL_MELODY_NOTES[]; //[0., 1, 3, 5, 7, 8, 10, 12] @=> float ALTERNATE_MELODY_NOTES[]; [0., 2, 3, 5, 7, 8, 10, 12] @=> float ALTERNATE_MELODY_NOTES[]; NORMAL_MELODY_NOTES @=> float MELODY_NOTES[]; int timeSequence[N_STEPS]; int selectInputs[N_STEPS]; int basisInputs[N_STEPS][N_STEPS]; for (0 => int i; i < N_STEPS; i++) { true => basisInputs[i][i]; basisInterface.buttonArrs[i].buttons[i].toggle(); } fun void playSequence() { while (true) { timeAdvance => now; if (timeSequence[timeIndex]) { spork ~ playKick(); } } } spork ~ playSequence(); fun void playKick() { me.dir() + "kick.wav" => string filename; SndBuf buf => kickBus; filename => buf.read; .25 => buf.gain; buf.length() => now; buf =< kickBus; } fun void playBasisMelody() { while (true) { timeAdvance => now; for (0 => int j; j < N_STEPS; j++) { if (basisInputs[j][timeIndex]) { spork ~ playNote(BASE_PITCH + MELODY_NOTES[j]); } } } } spork ~ playBasisMelody(); fun void playNote(float note) { TriOsc osc => ADSR env => noteBus; oscGain => osc.gain; note => Std.mtof => osc.freq; env.set(melA, melD, melS, melR); env.keyOn(); melNoteDur => now; env.keyOff(); env.releaseTime() => now; env =< noteBus; } fun void updateTimeSequence() { for (0 => int i; i < N_STEPS; i++) { false => int play; for (0 => int b; b < N_STEPS; b++) { // XOR play with basisInputs[b][i] if the bth basis is selected (selectInputs[b] && basisInputs[b][i]) == play => play; } if (play != timeSequence[i]) { timeSequenceDisplay.buttons[i].toggle(); play => timeSequence[i]; } } } Shred @ runningShred; fun void runTimeAdvance() { while (true) { for (0 => timeIndex; timeIndex < N_STEPS; timeIndex++) { timeAdvance.broadcast(); T => now; } } } // Color config true => int configIsNormal; fun void updateConnectors() { for (0 => int j; j < N_STEPS; j++) { for (0 => int i; i < N_STEPS; i++) { if (selectInputs[i] && basisInputs[i][j]) { i + 1 => int endI; while (endI < N_STEPS && !(selectInputs[endI] && basisInputs[endI][j])) { endI++; } basisInterface.buttonArrs[i].buttons[j].posWorld() @=> vec3 startPos; vec3 endPos; if (endI == N_STEPS) { timeSequenceDisplay.buttons[j].posWorld() => endPos; } else { connectors[endI][j] --< interfaceDolly; basisInterface.buttonArrs[endI].buttons[j].posWorld() => endPos; } connectors[i][j] --> interfaceDolly; connectors[i][j].geo().positions([startPos - interfaceDolly.posWorld(), endPos - interfaceDolly.posWorld()]); connectors[i][j].mat().color(bw(configIsNormal)); connectors[i][j].posZ(-1); endI => i; } else { connectors[i][j] --< interfaceDolly; } } } } fun int normalConfig() { for (0 => int i; i < N_STEPS; i++) { for (0 => int j; j < N_STEPS; j++) { if ((i == j) != basisInputs[i][j]) { return false; } } } return true; } fun int alternateConfig() { for (0 => int i; i < N_STEPS; i++) { for (0 => int j; j < N_STEPS; j++) { if ((i == N_STEPS - 1 - j) != basisInputs[i][j]) { return false; } } } return true; } fun int maxConfig() { for (0 => int i; i < N_STEPS; i++) { if (!selectInputs[i]) { return false; } } return true; } fun vec3 bw(int black) { return black ? @(0,0,0) : @(1,1,1); } fun void setBW(GMesh g, int black) { g.mat().color(bw(black)); } fun void setButtonColors(GButton button) { setBW(button.outline.square, configIsNormal); setBW(button.bgCircle, configIsNormal); setBW(button.cover, !configIsNormal); } fun void setAllColors() { GG.scene().backgroundColor(bw(!configIsNormal)); setButtonColors(onButton); setBW(pointer, configIsNormal); for (0 => int i; i < N_STEPS; i++) { setButtonColors(selectInterface.buttons[i]); setButtonColors(timeSequenceDisplay.buttons[i]); for (0 => int j; j < N_STEPS; j++) { setButtonColors(basisInterface.buttonArrs[i].buttons[j]); setBW(connectors[i][j], configIsNormal); } } } 0 => int configIsMax; fun void handleAlternation() { if (alternateConfig() && configIsNormal) { false => configIsNormal; ALTERNATE_MELODY_NOTES @=> MELODY_NOTES; setAllColors(); alternateT => T; false => configIsMax; normalOscGain => oscGain; alternateMelNoteDur => melNoteDur; altMelA => melA; altMelD => melD; altMelS => melS; altMelR => melR; noteRevGain.gain(alternateRevInputGain); kickRevGain.gain(alternateRevInputGain); } else if (maxConfig() && !configIsMax) { kickRevGain.gain(normalRevInputGain); true => configIsMax; true => configIsNormal; NORMAL_MELODY_NOTES @=> MELODY_NOTES; setAllColors(); maxOscGain => oscGain; alternateT => T; alternateMelNoteDur => melNoteDur; altMelA => melA; altMelD => melD; altMelS => melS; altMelR => melR; } else if (normalConfig() && configIsNormal && !maxConfig()) { false => configIsMax; true => configIsNormal; NORMAL_MELODY_NOTES @=> MELODY_NOTES; setAllColors(); normalT => T; normalMelNoteDur => melNoteDur; normalMelA => melA; normalMelD => melD; normalMelS => melS; normalMelR => melR; normalOscGain => oscGain; noteRevGain.gain(normalRevInputGain); kickRevGain.gain(normalRevInputGain); } } fun void processClick(float x, float y) { for (0 => int i; i < selectInterface.length; i++) { if (selectInterface.buttons[i].processClick(x, y)) { !selectInputs[i] => selectInputs[i]; for (0 => int j; j < basisInterface.buttonArrs[i].length; j++) { basisInterface.buttonArrs[i].buttons[j].toggleFill(); } } } for (0 => int i; i < basisInterface.length; i++) { for (0 => int j; j < basisInterface.buttonArrs[i].length; j++) { if (basisInterface.buttonArrs[i].buttons[j].processClick(x, y)) { !basisInputs[i][j] => basisInputs[i][j]; } } } updateTimeSequence(); if (onButton.processClick(x, y)) { if (onButton.toggledOn) { timeStart.broadcast(); spork ~ runTimeAdvance() @=> runningShred; } else { timeStop.broadcast(); Machine.remove(runningShred.id()); } } updateConnectors(); handleAlternation(); } fun void trackMouse() { Hid hi; HidMsg msg; // get from command line // if (me.args()) me.arg(0) => Std.atoi => MOUSE_DEVICE; // open mouse 0, exit on fail if (!hi.openMouse(MOUSE_DEVICE)) me.exit(); <<< "mouse '" + hi.name() + "' ready", "" >>>; // From Andrew Zhu Aday // need to pass 1 frame to propagate correct frame dimensions from render thread //GG.nextFrame() => now; // Causes hang 1.0 => float SPP; // framebuffer to screen pixel ratio // workaround for mac retina displays if (GG.frameWidth() != GG.windowWidth()) { 2.0 => SPP; <<< "retina display detected, SPP = " + SPP >>>; } // infinite event loop while (true) { hi => now; // wait on HidIn as event GG.frameWidth() / SPP => float screenWidth; GG.frameHeight() / SPP => float screenHeight; while (hi.recv(msg)) { if (msg.isButtonDown()) { // From Andrew Zhu Aday vec3 worldPos; // calculate mouse world X and Y coords if (GG.camera().mode() == GCamera.ORTHO) { // calculate screen aspect screenWidth / screenHeight => float aspect; // calculate camera frustrum size in world space GG.camera().viewSize() => float frustrumHeight; // height of frustrum in world space frustrumHeight * aspect => float frustrumWidth; // width of frustrum in world space // convert from normalized mouse coords to view space coords // (we negate viewY so that 0,0 is bottom left instead of top left) frustrumWidth * (GG.mouseX() / screenWidth - 0.5) => float viewX; -frustrumHeight * (GG.mouseY() / screenHeight - 0.5) => float viewY; // convert from view space coords to world space coords GG.camera().posLocalToWorld(@(viewX, viewY, interfaceZ)) => worldPos; } else { // perspective // generate ray going from camera through click location GG.camera().screenCoordToWorldRay(GG.mouseX(), GG.mouseY()) => vec3 ray; // calculate spawn position by moving along ray -interfaceZ * ray + GG.camera().posWorld() => worldPos; } processClick(worldPos.x, worldPos.y); }// else if ( msg.isMouseMotion() ) { // if (msg.deltaX) { // <<< "mouse motion:", msg.deltaX, "on x-axis" >>>; // } // if (msg.deltaY) { // <<< "mouse motion:", msg.deltaY, "on y-axis" >>>; // } //} // mouse button up //else if (msg.isButtonUp() ) //{ // <<< "mouse button", msg.which, "up" >>>; //} } } } spork ~ trackMouse(); while (true) { GG.nextFrame() => now; }