// 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 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 => filter; 0.05 => 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); // fun void modulate() { // while (true) { // // remap [-1, 1] --> [100, 2600] // Math.map(freqLFO.last(), -1.0, 1.0, 100, 12000) => float filterFreq; // // remap [-1, 1] --> [1, 10] // Math.map(qLFO.last(), -1.0, 1.0, .1, 8) => filter.Q; // // apply filter EG // filterEnv.last() * filterFreq + 100 => filter.freq; // 1::ms => now; // } // } spork ~ modulate(); // 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; } } public class GButton extends GGen { // background // GPlane back --> this; // front GPlane pad --> this; FlatMaterial mat; pad.mat(mat); int digit; // 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[]; // [ // Color.WHITE * 0.8, // NONE // Color.WHITE, // HOVERED // @(242, 235, 191)/255, // ACTIVE // Color.GREEN // PLAYING // ] @=> vec3 colorMap[]; // [ // // @(0, 0, 0), // // @(92, 75, 81)/255, // NONE // @(140, 190, 178)/255, // HOVERED // @(242, 235, 191)/255, // ACTIVE // @(243, 181, 98)/255, // PLAYING // @(240, 96, 96)/255, // ] @=> vec3 colorMap[]; // constructor fun void init(Mouse @ m, Printing @ p, int pitch, vec3 size, vec3 cmap[], int d) { bass.setPitch(pitch); if (mouse != null) return; m @=> this.mouse; p @=> this.printing; d => digit; // input @=> this.currInput; 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()) { onHoverEvent.broadcast(); 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()) { onClickEvent.broadcast(); 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); } 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(); } } // animation when playing // set juice = true to animate // fun void play(int juice) { // // handleInput(NOTE_ON); // if (juice) { // pad.sca(1.4); // pad.rotZ(Math.random2f(-.5, .2)); // } // } // stop play animation (called by sequencer on note off) // fun void stop() { // handleInput(NOTE_OFF); // } // 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; // uncomment to randomize color when playing // if (state == PLAYING) Color.random() => colorMap[PLAYING]; } // basic state machine for handling input fun void handleInput(int input) { // if (input == NOTE_ON) { // enter(PLAYING); // return; // } // if (input == NOTE_OFF) { // enter(lastState); // return; // } // <<>>; 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); spork ~ bass.stop(); } } // else if (state == PLAYING) { // if (input == MOUSE_CLICK) enter(NONE); // if (input == NOTE_OFF) enter(ACTIVE); // } } // override ggen update fun void update(float dt) { // <<<"here">>>; // check if hovered checkHover(); animate(); // update state // <<<"state", state>>>; this.setColor(colorMap[state]); // interpolate back towards uniform scale (handles animation) // this is cursed // pad.scaX() - .03 * Math.pow(Math.fabs((1.0 - pad.scaX())), .3) => pad.sca; // much less cursed // pad.scaX() + .05 * (1.0 - pad.scaX()) => pad.sca; // pad.rot().z + .06 * (0.0 - pad.rot().z) => pad.rotZ; } }