// Borrowed from Andrew's GPad // Issues // 1. Camera has to be set to orthographic to make it work // 2. No text display available // 3. Understand event listener global int currInput; public class GButton extends GGen { // background // GPlane back --> this; // front GPlane pad --> this; FlatMaterial mat; pad.mat(mat); int myDigit; // int inputRegister; // reference to a mouse Mouse @ mouse; Printing @ printing; // reference to a bass instrument Bass bass; vec3 colorMap[]; // events Event onHoverEvent, onClickEvent, onExitEvent, onReleaseEvent; // onExit, onRelease // states 0 => static int NONE; // not hovered or active 1 => static int HOVERED; // hovered 2 => static int ACTIVE; // clicked // 3 => static int PLAYING; // makine sound! 0 => int state; // current state // input types 0 => static int MOUSE_HOVER; 1 => static int MOUSE_EXIT; 2 => static int MOUSE_CLICK; 3 => static int MOUSE_RELEASE; // 3 => static int NOTE_ON; // 4 => static int NOTE_OFF; // color map // [ // Color.BLACK, // NONE // Color.RED, // HOVERED // Color.YELLOW, // ACTIVE // Color.GREEN // PLAYING // ] @=> vec3 colorMap[]; // constructor fun void init(Mouse @ m, Printing @ p, int pitch, vec3 size, vec3 cmap[], int d, Event @ e) { bass.setPitch(pitch); if (mouse != null) return; m @=> this.mouse; p @=> this.printing; d => myDigit; e @=> this.onReleaseEvent; spork ~ this.clickListener(); spork ~ this.releaseListener(); pad.sca(size); cmap @=> colorMap; } // check if state is active (i.e. should play sound) fun int isActive() { return state == ACTIVE; } // set color fun void setColor(vec3 c) { pad.mat().color(c); } // returns true if mouse is hovering over pad fun int isHovered() { pad.scaWorld() => vec3 worldScale; // get dimensions worldScale.x / 2.0 => float halfWidth; worldScale.y / 2.0 => float halfHeight; pad.posWorld() => vec3 worldPos; // get position // <<>>; // <<>>; if (mouse.worldPos.x > worldPos.x - halfWidth && mouse.worldPos.x < worldPos.x + halfWidth && mouse.worldPos.y > worldPos.y - halfHeight && mouse.worldPos.y < worldPos.y + halfHeight) { return true; } return false; } // poll for hover events fun void checkHover() { // <<>>; if (isHovered()) { handleInput(MOUSE_HOVER); } else { if (state == HOVERED) handleInput(MOUSE_EXIT); } } // handle mouse clicks fun void clickListener() { now => time lastClick; while (true) { mouse.mouseDownEvents[Mouse.LEFT] => now; if (isHovered()) { handleInput(MOUSE_CLICK); } 100::ms => now; // cooldown } } fun void releaseListener() { now => time lastRelease; while (true) { mouse.mouseUpEvents[Mouse.LEFT] => now; if (isActive()) { // onReleaseEvent.broadcast(); handleInput(MOUSE_RELEASE); myDigit => currInput; } 100::ms => now; // cooldown } } // animetion when clicked fun void animate(){ if (state == ACTIVE) { this.sca(0.8); // spork ~ bass.play(); } else { this.sca(1); // bass.stop(); } } // activate pad, meaning it should be played when the sequencer hits it fun void activate() { enter(ACTIVE); } 0 => int lastState; // enter state, remember last state fun void enter(int s) { state => lastState; s => state; } // basic state machine for handling input fun void handleInput(int input) { // <<>>; if (state == NONE) { if (input == MOUSE_HOVER) enter(HOVERED); else if (input == MOUSE_CLICK) { enter(ACTIVE); spork ~ bass.play(); } } else if (state == HOVERED) { if (input == MOUSE_EXIT) enter(NONE); else if (input == MOUSE_CLICK) { enter(ACTIVE); spork ~ bass.play(); } } else if (state == ACTIVE) { // if (input == MOUSE_CLICK) enter(NONE); if( input == MOUSE_RELEASE ){ enter(NONE); onReleaseEvent.broadcast(); spork ~ bass.stop(); myDigit => currInput; } } } // override ggen update fun void update(float dt) { // <<<"here">>>; // check if hovered checkHover(); animate(); // update state // <<<"state", state>>>; this.setColor(colorMap[state]); } } class Bass{ float freq; SawOsc saw1, saw2; SqrOsc sqr1; ADSR env; // amplitude EG Step step => Envelope filterEnv => blackhole; // filter cutoff EG LPF filter; TriOsc freqLFO => blackhole; // LFO to modulate filter frequency TriOsc qLFO => blackhole; // LFO to modulate filter resonance saw1 => env => filter => Gain g => dac; // saw2 => env => filter; sqr1 => env; 0.1 => g.gain; // initialize amp EG env.set(40::ms, 10::ms, .6, 150::ms); // initialize filter EG step.next(1.0); filterEnv.duration(50::ms); // initialize filter LFOs freqLFO.period(8::second); qLFO.period(10::second); // initialize filter filter.freq(2000); filter.Q(2); // spork to play! fun void setPitch(int pitch) { Std.mtof(pitch) / 2 => freq; } fun void play() { // set frequencies saw1.freq(freq); sqr1.freq(2 * freq * 1.02); // slight detune for more harmonic content // activate EGs env.keyOn(); filterEnv.keyOn(); // wait for note to hit sustain portion of ADSR env.attackTime() + env.decayTime() => now; // // deactivate EGs // env.keyOff(); filterEnv.keyOff(); // // wait for note to finish // env.releaseTime() => now; } fun void stop() { // deactivate EGs env.keyOff(); filterEnv.keyOff(); // wait for note to finish env.releaseTime() => now; } }