t()))}}class a{constructor(){this.experience=new o,this.keyMap=new Map,window.addEventListener("keydown",(e=>this._onKeyPress(e)))}addKeyPress(e,t){this.keyMap.set(e.toUpperCase().charCodeAt(0),t),this.keyMap.set(this._getNumPadKey(e),t)}removeKeyPress(e){this.keyMap.delete(e)}_onKeyPress(e){let t=e.keyCode,i=this.keyMap.get(t);i&&i()}_getNumPadKey(e){switch(e){case"9":return 33;case"8":return 38;case"7":return 36;case"6":return 39;case"5":return 12;case"4":return 32;case"3":return 34;case"2":return 40;case"1":return 35}return e}}let r=null;class o{constructor(e){if(r)return r;r=this,this.pane=new Tweakpane.Pane,this.tweekPaneInit(),this.kick=null,this.snare=null,this.buttonPressHandler=new s,this.keyPressHandler=new a,this.buttonPressHandler.addButtonPressEvent("start",(()=>this.onInfoPress()))}onInfoPress(){this.audioContext||this.setup(),document.getElementById("information").style.visibility="hidden"}tweekPaneInit(){this.params={kickTimeMultiplier:1,btn9_gain:1,btn8_gain:1,btn7_gain:1,btn6_gain:1,btn5_gain:1,btn4_gain:1,btn3_gain:1,btn2_gain:1,btn1_gain:1},this.pane.addInput(this.params,"kickTimeMultiplier",{min:.1,max:5,step:.1}),this.pane.addInput(this.params,"btn9_gain",{min:.1,max:5,step:.1}),this.pane.addInput(this.params,"btn8_gain",{min:.1,max:5,step:.1}),this.pane.addInput(this.params,"btn7_gain",{min:.1,max:5,step:.1}),this.pane.addInput(this.params,"btn6_gain",{min:.1,max:5,step:.1}),this.pane.addInput(this.params,"btn5_gain",{min:.1,max:5,step:.1}),this.pane.addInput(this.params,"btn4_gain",{min:.1,max:5,step:.1}),this.pane.addInput(this.params,"btn3_gain",{min:.1,max:5,step:.1}),this.pane.addInput(this.params,"btn2_gain",{min:.1,max:5,step:.1}),this.pane.addInput(this.params,"btn1_gain",{min:.1,max:5,step:.1})}setup(){this.audioContext=new(window.AudioContext||window.webkitAudioContext),this.kick=new i("9"),this.snare=new n("8"),this.hihat=new t("hi_hat.wav","7"),this.sizzle=new t("sizzel_snap.wav","6"),this.tunnel=new t("tunnel_shaker.wav","5"),this.low_and_slow=new t("low_and_slow_kick.wav","4"),this.throat=new t("throat_kick.wav","3"),this.fly=new t("fly_hi_hat.wav","2"),this.short=new t("short_and_sweet_hi_hat.wav","1")}}let h=new o("experience");window.experience=h})();
+//# sourceMappingURL=bundle.535bbd2ffe74b8fe.js.map
\ No newline at end of file
diff --git a/public/bundle.535bbd2ffe74b8fe.js.map b/public/bundle.535bbd2ffe74b8fe.js.map
new file mode 100644
index 00000000..c5c1b96b
--- /dev/null
+++ b/public/bundle.535bbd2ffe74b8fe.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"bundle.535bbd2ffe74b8fe.js","mappings":"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","sources":["webpack:///./src/experience/sounds/api/Sound.js","webpack:///./src/experience/sounds/api/LoadedSound.js","webpack:///./src/experience/sounds/Kick.js","webpack:///./src/experience/sounds/Snare.js","webpack:///./src/experience/util/ButtonPressHandler.js","webpack:///./src/experience/util/KeyPressHandler.js","webpack:///./src/experience/Experience.js","webpack:///./src/script.js"],"sourcesContent":["import Experience from \"../../Experience\"\r\n\r\nexport default class Sound\r\n{\r\n constructor(triggerID)\r\n {\r\n this.triggerID = triggerID;\r\n this.experience = new Experience()\r\n this.experience.buttonPressHandler.addButtonPressEvent(`btn${triggerID}`, () => (this.trigger()))\r\n this.experience.keyPressHandler.addKeyPress(`${triggerID}`, () => (this.trigger()))\r\n }\r\n\r\n _setup()\r\n {\r\n throw new Error(\"Method '_setup()' must be implemented.\")\r\n\r\n }\r\n \r\n _playSound()\r\n {\r\n throw new Error(\"Method '_playSound()' must be implemented.\")\r\n }\r\n\r\n trigger()\r\n {\r\n this._setup()\r\n this._playSound()\r\n }\r\n}","import Sound from \"./Sound\";\r\n\r\nexport default class LoadedSounded extends Sound\r\n{\r\n constructor(fileName, triggerID)\r\n {\r\n super(triggerID)\r\n this.fileName = fileName\r\n this.loaded = false\r\n this._loadSound()\r\n }\r\n\r\n _setup()\r\n {\r\n this.source = this.experience.audioContext.createBufferSource()\r\n this.source.buffer = this.buffer\r\n this.gain = this.experience.audioContext.createGain()\r\n this.source.connect(this.gain)\r\n\r\n this.gain.connect(this.experience.audioContext.destination)\r\n\r\n }\r\n\r\n _playSound()\r\n {\r\n let gain = this.experience.params[`btn${this.triggerID}_gain`]\r\n console.log(gain)\r\n let time = this.experience.audioContext.currentTime\r\n this.gain.gain.setValueAtTime(gain, time)\r\n this.source.start(this.experience.audioContext.currentTime)\r\n }\r\n\r\n trigger()\r\n {\r\n if(!this.loaded)\r\n throw new Error(\"Sound not loaded!\")\r\n\r\n this._setup()\r\n this._playSound()\r\n }\r\n\r\n _loadSound()\r\n {\r\n fetch( `${this.fileName}`, {\r\n method:'GET'\r\n })\r\n .then(response => {\r\n if(!response.ok)\r\n throw new Error(`HTTP error, status = ${response.status}`)\r\n \r\n return response.arrayBuffer()\r\n })\r\n .then(buffer => this.experience.audioContext.decodeAudioData(buffer))\r\n .then(decodedData => {\r\n this.buffer = decodedData;\r\n this.loaded = true;\r\n })\r\n }\r\n}","import Sound from \"./api/Sound\";\r\n\r\nexport default class Kick extends Sound\r\n{\r\n constructor(triggerID)\r\n {\r\n super(triggerID)\r\n }\r\n\r\n _setup()\r\n {\r\n this.oscillator = this.experience.audioContext.createOscillator();\r\n this.gain = this.experience.audioContext.createGain();\r\n\r\n //connect oscillator node to gain node\r\n this.oscillator.connect(this.gain)\r\n //connect gain node to speakers\r\n this.gain.connect(this.experience.audioContext.destination)\r\n }\r\n\r\n _playSound()\r\n { \r\n let time = this.experience.audioContext.currentTime\r\n this.gain.gain.setValueAtTime(1, time)\r\n this.gain.gain.exponentialRampToValueAtTime(0.001, time + 0.5 * this.experience.params.kickTimeMultiplier)\r\n this.oscillator.frequency.setValueAtTime(150 * this.experience.params.kickTimeMultiplier, time)\r\n this.oscillator.frequency.exponentialRampToValueAtTime(0.001, time + 0.5 * this.experience.params.kickTimeMultiplier)\r\n\r\n this.oscillator.start(time)\r\n this.oscillator.stop(time + 2 * this.experience.params.kickTimeMultiplier)\r\n }\r\n}","import Sound from \"./api/Sound\";\r\n\r\nexport default class Snare extends Sound\r\n{\r\n constructor(triggerID)\r\n {\r\n super(triggerID)\r\n }\r\n\r\n noiseBuffer()\r\n {\r\n let bufferSize = this.experience.audioContext.sampleRate;\r\n let buffer = this.experience.audioContext.createBuffer(1, bufferSize, this.experience.audioContext.sampleRate)\r\n let output = buffer.getChannelData(0)\r\n\r\n for(let i = 0; i < bufferSize; i++)\r\n {\r\n output[i] = Math.random() * 2 - 1\r\n }\r\n\r\n return buffer;\r\n }\r\n\r\n _setup()\r\n {\r\n this.noise = this.experience.audioContext.createBufferSource();\r\n this.noise.buffer = this.noiseBuffer()\r\n\r\n let noiseFilter = this.experience.audioContext.createBiquadFilter()\r\n noiseFilter.type = \"highpass\"\r\n noiseFilter.frequency.value = 1000;\r\n this.noise.connect(noiseFilter)\r\n\r\n this.noiseEnvelope = this.experience.audioContext.createGain()\r\n noiseFilter.connect(this.noiseEnvelope)\r\n this.noiseEnvelope.connect(this.experience.audioContext.destination)\r\n \r\n //Create Snap effect of snare\r\n this.oscillator = this.experience.audioContext.createOscillator()\r\n this.oscillator.type = \"triangle\"\r\n\r\n this.oscillatorEnvelope = this.experience.audioContext.createGain()\r\n this.oscillator.connect(this.oscillatorEnvelope)\r\n this.oscillatorEnvelope.connect(this.experience.audioContext.destination)\r\n }\r\n\r\n _playSound()\r\n {\r\n let time = this.experience.audioContext.currentTime\r\n\r\n this.noiseEnvelope.gain.setValueAtTime(1, time)\r\n\r\n this.noiseEnvelope.gain.exponentialRampToValueAtTime(0.01, time + 0.2)\r\n this.noise.start(time)\r\n\r\n this.oscillator.frequency.setValueAtTime(100, time)\r\n this.oscillatorEnvelope.gain.setValueAtTime(0.7, time)\r\n this.oscillatorEnvelope.gain.exponentialRampToValueAtTime(0.01, time + 0,1)\r\n this.oscillator.start(time)\r\n\r\n this.oscillator.stop(time + 0.2)\r\n this.noise.stop(time + 0.2)\r\n }\r\n\r\n}","import Experience from \"../Experience\";\r\n\r\nexport default class ButtonPressHandler\r\n{\r\n\r\n constructor()\r\n {\r\n this.experience = new Experience();\r\n }\r\n\r\n addButtonPressEvent(elementID, triggerFunction)\r\n {\r\n document.getElementById(elementID).addEventListener('click', () => triggerFunction())\r\n }\r\n}","import Experience from \"../Experience\";\r\n\r\nexport default class KeyPressHandler\r\n{\r\n constructor()\r\n {\r\n this.experience = new Experience()\r\n //stores keycode, function to call\r\n this.keyMap = new Map();\r\n window.addEventListener(\"keydown\", (event) => (this._onKeyPress(event)))\r\n }\r\n\r\n addKeyPress(key, triggerFunction)\r\n {\r\n this.keyMap.set(key.toUpperCase().charCodeAt(0), triggerFunction)\r\n this.keyMap.set(this._getNumPadKey(key), triggerFunction)\r\n }\r\n\r\n removeKeyPress(key)\r\n {\r\n this.keyMap.delete(key)\r\n }\r\n\r\n _onKeyPress(event)\r\n {\r\n let key = event.keyCode\r\n let triggerFunction = this.keyMap.get(key)\r\n\r\n if(triggerFunction)\r\n triggerFunction()\r\n }\r\n \r\n _getNumPadKey(key)\r\n {\r\n switch(key)\r\n {\r\n case \"9\":\r\n return 33\r\n case \"8\":\r\n return 38\r\n case \"7\":\r\n return 36\r\n case \"6\":\r\n return 39\r\n case \"5\":\r\n return 12\r\n case \"4\":\r\n return 32\r\n case \"3\":\r\n return 34\r\n case \"2\":\r\n return 40\r\n case \"1\":\r\n return 35\r\n }\r\n\r\n return key;\r\n }\r\n}","import LoadedSounded from './sounds/api/LoadedSound'\r\nimport HiHat from './sounds/HiHat'\r\nimport Kick from './sounds/Kick'\r\nimport Snare from './sounds/Snare'\r\nimport ButtonPressHandler from './util/ButtonPressHandler'\r\nimport KeyPressHandler from './util/KeyPressHandler'\r\n\r\nlet instance = null\r\n//https://analogcases.com/blogs/news/analog-supplies-vol-3\r\nexport default class Experience\r\n{\r\n constructor(_svgID)\r\n {\r\n if(instance)\r\n {\r\n return instance\r\n } \r\n instance = this\r\n this.pane = new Tweakpane.Pane()\r\n this.tweekPaneInit()\r\n\r\n this.kick = null\r\n this.snare = null\r\n this.buttonPressHandler = new ButtonPressHandler()\r\n this.keyPressHandler = new KeyPressHandler()\r\n\r\n this.buttonPressHandler.addButtonPressEvent(\"start\", () => (this.onInfoPress()))\r\n }\r\n\r\n onInfoPress()\r\n {\r\n if(!this.audioContext)\r\n this.setup()\r\n\r\n document.getElementById(\"information\").style.visibility = \"hidden\"\r\n }\r\n\r\n tweekPaneInit()\r\n {\r\n this.params = {\r\n kickTimeMultiplier: 1,\r\n btn9_gain: 1,\r\n btn8_gain: 1,\r\n btn7_gain: 1,\r\n btn6_gain: 1,\r\n btn5_gain: 1,\r\n btn4_gain: 1,\r\n btn3_gain: 1,\r\n btn2_gain: 1,\r\n btn1_gain: 1,\r\n }\r\n\r\n this.pane.addInput(\r\n this.params, \"kickTimeMultiplier\",\r\n {min: .1, max: 5, step: .1},\r\n )\r\n\r\n this.pane.addInput(\r\n this.params, \"btn9_gain\",\r\n {min: .1, max: 5, step: .1},\r\n )\r\n\r\n this.pane.addInput(\r\n this.params, \"btn8_gain\",\r\n {min: .1, max: 5, step: .1},\r\n )\r\n\r\n this.pane.addInput(\r\n this.params, \"btn7_gain\",\r\n {min: .1, max: 5, step: .1},\r\n )\r\n\r\n this.pane.addInput(\r\n this.params, \"btn6_gain\",\r\n {min: .1, max: 5, step: .1},\r\n )\r\n\r\n this.pane.addInput(\r\n this.params, \"btn5_gain\",\r\n {min: .1, max: 5, step: .1},\r\n )\r\n\r\n this.pane.addInput(\r\n this.params, \"btn4_gain\",\r\n {min: .1, max: 5, step: .1},\r\n )\r\n\r\n this.pane.addInput(\r\n this.params, \"btn3_gain\",\r\n {min: .1, max: 5, step: .1},\r\n )\r\n\r\n this.pane.addInput(\r\n this.params, \"btn2_gain\",\r\n {min: .1, max: 5, step: .1},\r\n )\r\n\r\n this.pane.addInput(\r\n this.params, \"btn1_gain\",\r\n {min: .1, max: 5, step: .1},\r\n )\r\n }\r\n\r\n setup()\r\n {\r\n this.audioContext = new (window.AudioContext || window.webkitAudioContext)\r\n this.kick = new Kick(\"9\")\r\n this.snare = new Snare(\"8\")\r\n this.hihat = new LoadedSounded(\"hi_hat.wav\", \"7\")\r\n this.sizzle = new LoadedSounded(\"sizzel_snap.wav\", \"6\")\r\n this.tunnel = new LoadedSounded(\"tunnel_shaker.wav\", \"5\")\r\n this.low_and_slow = new LoadedSounded(\"low_and_slow_kick.wav\", \"4\")\r\n this.throat = new LoadedSounded(\"throat_kick.wav\", \"3\")\r\n this.fly = new LoadedSounded(\"fly_hi_hat.wav\", \"2\")\r\n this.short = new LoadedSounded(\"short_and_sweet_hi_hat.wav\", \"1\")\r\n }\r\n}","import './style.css'\r\nimport Experience from \"./experience/Experience\";\r\n\r\nlet experience = new Experience(\"experience\")\r\nwindow.experience = experience"],"names":["Sound","constructor","triggerID","this","experience","Experience","buttonPressHandler","addButtonPressEvent","trigger","keyPressHandler","addKeyPress","_setup","Error","_playSound","LoadedSounded","fileName","super","loaded","_loadSound","source","audioContext","createBufferSource","buffer","gain","createGain","connect","destination","params","console","log","time","currentTime","setValueAtTime","start","fetch","method","then","response","ok","status","arrayBuffer","decodeAudioData","decodedData","Kick","oscillator","createOscillator","exponentialRampToValueAtTime","kickTimeMultiplier","frequency","stop","Snare","noiseBuffer","bufferSize","sampleRate","createBuffer","output","getChannelData","i","Math","random","noise","noiseFilter","createBiquadFilter","type","value","noiseEnvelope","oscillatorEnvelope","ButtonPressHandler","elementID","triggerFunction","document","getElementById","addEventListener","KeyPressHandler","keyMap","Map","window","event","_onKeyPress","key","set","toUpperCase","charCodeAt","_getNumPadKey","removeKeyPress","delete","keyCode","get","instance","_svgID","pane","Tweakpane","Pane","tweekPaneInit","kick","snare","onInfoPress","setup","style","visibility","btn9_gain","btn8_gain","btn7_gain","btn6_gain","btn5_gain","btn4_gain","btn3_gain","btn2_gain","btn1_gain","addInput","min","max","step","AudioContext","webkitAudioContext","hihat","sizzle","tunnel","low_and_slow","throat","fly","short"],"sourceRoot":""}
\ No newline at end of file
diff --git a/public/fly_hi_hat.wav b/public/fly_hi_hat.wav
new file mode 100644
index 00000000..3ebc1d49
Binary files /dev/null and b/public/fly_hi_hat.wav differ
diff --git a/public/hi_hat.wav b/public/hi_hat.wav
new file mode 100644
index 00000000..9da0747f
Binary files /dev/null and b/public/hi_hat.wav differ
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 00000000..95b0bf53
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/low_and_slow_kick.wav b/public/low_and_slow_kick.wav
new file mode 100644
index 00000000..5dd0d310
Binary files /dev/null and b/public/low_and_slow_kick.wav differ
diff --git a/public/main.css b/public/main.css
new file mode 100644
index 00000000..33cbd578
--- /dev/null
+++ b/public/main.css
@@ -0,0 +1,160 @@
+body
+{
+ background-color: #C6E2E9;
+}
+
+#launchpad
+{
+ margin: auto;
+ background-color: #28282B;
+ width: 80vh;
+ height: 80vh;
+ border-radius: 5vw;
+ align-items: center;
+ text-align: center;
+}
+
+.pushable {
+ background-color: #CCCCCC;
+ border: none;
+ border-radius: 12px;
+ padding: 0;
+ margin: 20px;
+ cursor: pointer;
+}
+
+.front {
+ display: block;
+ padding: 10vh 10vh;
+ border-radius: 12px;
+ font-size: 1.25rem;
+ background-color: #EFF2EF;
+ color: white;
+ transform: translateY(-7px);
+}
+
+
+
+.front {
+ will-change: transform;
+ transition: transform 250ms;
+ }
+
+.pushable:hover .front {
+ transform: translateY(-6px);
+}
+
+.pushable:active .front {
+ transform: translateY(-2px);
+}
+
+#btn9 .front:active
+{
+ background-color: #e8edbe;
+}
+
+#btn9:active
+{
+ background-color: #babe98;
+}
+
+#btn8 .front:active
+{
+ background-color: #ccedbe;
+}
+
+#btn8:active
+{
+ background-color: #8fa685;
+}
+
+#btn7 .front:active
+{
+ background-color: #beedd7;
+}
+
+#btn7:active
+{
+ background-color: #98beac;
+}
+
+#btn6 .front:active
+{
+ background-color: #bec9ed;
+}
+
+#btn6:active
+{
+ background-color: #98a1be;
+}
+
+#btn5 .front:active
+{
+ background-color: #dcbeed;
+}
+
+#btn5:active
+{
+ background-color: #b098be;
+}
+
+#btn4 .front:active
+{
+ background-color: #edbedd;
+}
+
+#btn4:active
+{
+ background-color: #be98b1;
+}
+
+#btn3 .front:active
+{
+ background-color: #edc4be;
+}
+
+#btn3:active
+{
+ background-color: #be9d98;
+}
+
+#btn2 .front:active
+{
+ background-color: #ede6be;
+}
+
+#btn2:active
+{
+ background-color: #beb898;
+}
+
+#btn1 .front:active
+{
+ background-color: #beedd5;
+}
+
+#btn1:active
+{
+ background-color: #98beaa;
+}
+
+
+#information
+{
+ position: absolute;
+ z-index: 10;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ padding: 10px;
+ align-items: center;
+ text-align: center;
+
+ background-color: #ECB16D;
+ border-radius: 56px;
+ filter: drop-shadow(0px 0px 3px rgba(0, 0, 0, 25%));
+ color: white;
+
+}
+
+/*# sourceMappingURL=main.css.map*/
\ No newline at end of file
diff --git a/public/main.css.map b/public/main.css.map
new file mode 100644
index 00000000..c5a5e9ab
--- /dev/null
+++ b/public/main.css.map
@@ -0,0 +1 @@
+{"version":3,"file":"main.css","mappings":"AAAA;;IAEI,yBAAyB;AAC7B;;AAEA;;IAEI,YAAY;IACZ,yBAAyB;IACzB,WAAW;IACX,YAAY;IACZ,kBAAkB;IAClB,mBAAmB;IACnB,kBAAkB;AACtB;;AAEA;IACI,yBAAyB;IACzB,YAAY;IACZ,mBAAmB;IACnB,UAAU;IACV,YAAY;IACZ,eAAe;AACnB;;AAEA;IACI,cAAc;IACd,kBAAkB;IAClB,mBAAmB;IACnB,kBAAkB;IAClB,yBAAyB;IACzB,YAAY;IACZ,2BAA2B;AAC/B;;;;AAIA;IACI,sBAAsB;IACtB,2BAA2B;EAC7B;;AAEF;IACI,2BAA2B;AAC/B;;AAEA;IACI,2BAA2B;AAC/B;;AAEA;;IAEI,yBAAyB;AAC7B;;AAEA;;IAEI,yBAAyB;AAC7B;;AAEA;;IAEI,yBAAyB;AAC7B;;AAEA;;IAEI,yBAAyB;AAC7B;;AAEA;;IAEI,yBAAyB;AAC7B;;AAEA;;IAEI,yBAAyB;AAC7B;;AAEA;;IAEI,yBAAyB;AAC7B;;AAEA;;IAEI,yBAAyB;AAC7B;;AAEA;;IAEI,yBAAyB;AAC7B;;AAEA;;IAEI,yBAAyB;AAC7B;;AAEA;;IAEI,yBAAyB;AAC7B;;AAEA;;IAEI,yBAAyB;AAC7B;;AAEA;;IAEI,yBAAyB;AAC7B;;AAEA;;IAEI,yBAAyB;AAC7B;;AAEA;;IAEI,yBAAyB;AAC7B;;AAEA;;IAEI,yBAAyB;AAC7B;;AAEA;;IAEI,yBAAyB;AAC7B;;AAEA;;IAEI,yBAAyB;AAC7B;;;AAGA;;IAEI,kBAAkB;IAClB,WAAW;IACX,SAAS;IACT,QAAQ;IACR,gCAAgC;IAChC,aAAa;IACb,mBAAmB;IACnB,kBAAkB;;IAElB,yBAAyB;IACzB,mBAAmB;IACnB,mDAAmD;IACnD,YAAY;;AAEhB,C","sources":["webpack:///./src/style.css"],"sourcesContent":["body\r\n{\r\n background-color: #C6E2E9;\r\n}\r\n\r\n#launchpad\r\n{\r\n margin: auto;\r\n background-color: #28282B;\r\n width: 80vh;\r\n height: 80vh;\r\n border-radius: 5vw;\r\n align-items: center;\r\n text-align: center;\r\n}\r\n\r\n.pushable {\r\n background-color: #CCCCCC;\r\n border: none;\r\n border-radius: 12px;\r\n padding: 0;\r\n margin: 20px;\r\n cursor: pointer;\r\n}\r\n\r\n.front {\r\n display: block;\r\n padding: 10vh 10vh;\r\n border-radius: 12px;\r\n font-size: 1.25rem;\r\n background-color: #EFF2EF;\r\n color: white;\r\n transform: translateY(-7px);\r\n}\r\n\r\n\r\n\r\n.front {\r\n will-change: transform;\r\n transition: transform 250ms;\r\n }\r\n\r\n.pushable:hover .front {\r\n transform: translateY(-6px);\r\n}\r\n \r\n.pushable:active .front {\r\n transform: translateY(-2px);\r\n}\r\n\r\n#btn9 .front:active\r\n{\r\n background-color: #e8edbe;\r\n}\r\n\r\n#btn9:active\r\n{\r\n background-color: #babe98;\r\n}\r\n\r\n#btn8 .front:active \r\n{\r\n background-color: #ccedbe;\r\n}\r\n\r\n#btn8:active \r\n{\r\n background-color: #8fa685;\r\n}\r\n\r\n#btn7 .front:active \r\n{\r\n background-color: #beedd7;\r\n}\r\n\r\n#btn7:active \r\n{\r\n background-color: #98beac;\r\n}\r\n\r\n#btn6 .front:active \r\n{\r\n background-color: #bec9ed;\r\n}\r\n\r\n#btn6:active \r\n{\r\n background-color: #98a1be;\r\n}\r\n\r\n#btn5 .front:active \r\n{\r\n background-color: #dcbeed;\r\n}\r\n\r\n#btn5:active \r\n{\r\n background-color: #b098be;\r\n}\r\n\r\n#btn4 .front:active \r\n{\r\n background-color: #edbedd;\r\n}\r\n\r\n#btn4:active \r\n{\r\n background-color: #be98b1;\r\n}\r\n\r\n#btn3 .front:active \r\n{\r\n background-color: #edc4be;\r\n}\r\n\r\n#btn3:active \r\n{\r\n background-color: #be9d98;\r\n}\r\n\r\n#btn2 .front:active \r\n{\r\n background-color: #ede6be;\r\n}\r\n\r\n#btn2:active \r\n{\r\n background-color: #beb898;\r\n}\r\n\r\n#btn1 .front:active \r\n{\r\n background-color: #beedd5;\r\n}\r\n\r\n#btn1:active \r\n{\r\n background-color: #98beaa;\r\n}\r\n\r\n\r\n#information\r\n{\r\n position: absolute;\r\n z-index: 10;\r\n left: 50%;\r\n top: 50%;\r\n transform: translate(-50%, -50%);\r\n padding: 10px;\r\n align-items: center;\r\n text-align: center;\r\n\r\n background-color: #ECB16D;\r\n border-radius: 56px;\r\n filter: drop-shadow(0px 0px 3px rgba(0, 0, 0, 25%));\r\n color: white;\r\n\r\n}"],"names":[],"sourceRoot":""}
\ No newline at end of file
diff --git a/public/short_and_sweet_hi_hat.wav b/public/short_and_sweet_hi_hat.wav
new file mode 100644
index 00000000..f77f2c84
Binary files /dev/null and b/public/short_and_sweet_hi_hat.wav differ
diff --git a/public/sizzel_snap.wav b/public/sizzel_snap.wav
new file mode 100644
index 00000000..9b3b327e
Binary files /dev/null and b/public/sizzel_snap.wav differ
diff --git a/public/throat_kick.wav b/public/throat_kick.wav
new file mode 100644
index 00000000..45e7cedc
Binary files /dev/null and b/public/throat_kick.wav differ
diff --git a/public/tunnel_shaker.wav b/public/tunnel_shaker.wav
new file mode 100644
index 00000000..b358ffbe
Binary files /dev/null and b/public/tunnel_shaker.wav differ
diff --git a/server.js b/server.js
new file mode 100644
index 00000000..7cbddc9e
--- /dev/null
+++ b/server.js
@@ -0,0 +1,8 @@
+const express = require("express"),
+app = express()
+
+app.use(express.static("public"))
+app.use(express.static("views"))
+app.use(express.json())
+
+app.listen(process.env.PORT || 3000)
\ No newline at end of file
diff --git a/src/experience/Experience.js b/src/experience/Experience.js
new file mode 100644
index 00000000..ba3374ff
--- /dev/null
+++ b/src/experience/Experience.js
@@ -0,0 +1,117 @@
+import LoadedSounded from './sounds/api/LoadedSound'
+import HiHat from './sounds/HiHat'
+import Kick from './sounds/Kick'
+import Snare from './sounds/Snare'
+import ButtonPressHandler from './util/ButtonPressHandler'
+import KeyPressHandler from './util/KeyPressHandler'
+
+let instance = null
+//https://analogcases.com/blogs/news/analog-supplies-vol-3
+export default class Experience
+{
+ constructor(_svgID)
+ {
+ if(instance)
+ {
+ return instance
+ }
+ instance = this
+ this.pane = new Tweakpane.Pane()
+ this.tweekPaneInit()
+
+ this.kick = null
+ this.snare = null
+ this.buttonPressHandler = new ButtonPressHandler()
+ this.keyPressHandler = new KeyPressHandler()
+
+ this.buttonPressHandler.addButtonPressEvent("start", () => (this.onInfoPress()))
+ }
+
+ onInfoPress()
+ {
+ if(!this.audioContext)
+ this.setup()
+
+ document.getElementById("information").style.visibility = "hidden"
+ }
+
+ tweekPaneInit()
+ {
+ this.params = {
+ kickTimeMultiplier: 1,
+ btn9_gain: 1,
+ btn8_gain: 1,
+ btn7_gain: 1,
+ btn6_gain: 1,
+ btn5_gain: 1,
+ btn4_gain: 1,
+ btn3_gain: 1,
+ btn2_gain: 1,
+ btn1_gain: 1,
+ }
+
+ this.pane.addInput(
+ this.params, "kickTimeMultiplier",
+ {min: .1, max: 5, step: .1},
+ )
+
+ this.pane.addInput(
+ this.params, "btn9_gain",
+ {min: .1, max: 5, step: .1},
+ )
+
+ this.pane.addInput(
+ this.params, "btn8_gain",
+ {min: .1, max: 5, step: .1},
+ )
+
+ this.pane.addInput(
+ this.params, "btn7_gain",
+ {min: .1, max: 5, step: .1},
+ )
+
+ this.pane.addInput(
+ this.params, "btn6_gain",
+ {min: .1, max: 5, step: .1},
+ )
+
+ this.pane.addInput(
+ this.params, "btn5_gain",
+ {min: .1, max: 5, step: .1},
+ )
+
+ this.pane.addInput(
+ this.params, "btn4_gain",
+ {min: .1, max: 5, step: .1},
+ )
+
+ this.pane.addInput(
+ this.params, "btn3_gain",
+ {min: .1, max: 5, step: .1},
+ )
+
+ this.pane.addInput(
+ this.params, "btn2_gain",
+ {min: .1, max: 5, step: .1},
+ )
+
+ this.pane.addInput(
+ this.params, "btn1_gain",
+ {min: .1, max: 5, step: .1},
+ )
+ }
+
+ setup()
+ {
+ this.audioContext = new (window.AudioContext || window.webkitAudioContext)
+ this.kick = new Kick("9")
+ this.snare = new Snare("8")
+ this.hihat = new LoadedSounded("hi_hat.wav", "7")
+ this.sizzle = new LoadedSounded("sizzel_snap.wav", "6")
+ this.tunnel = new LoadedSounded("tunnel_shaker.wav", "5")
+ this.low_and_slow = new LoadedSounded("low_and_slow_kick.wav", "4")
+ this.throat = new LoadedSounded("throat_kick.wav", "3")
+ this.fly = new LoadedSounded("fly_hi_hat.wav", "2")
+ this.short = new LoadedSounded("short_and_sweet_hi_hat.wav", "1")
+ }
+}
\ No newline at end of file
diff --git a/src/experience/sounds/HiHat.js b/src/experience/sounds/HiHat.js
new file mode 100644
index 00000000..e8163283
--- /dev/null
+++ b/src/experience/sounds/HiHat.js
@@ -0,0 +1,22 @@
+import LoadedSounded from "./api/LoadedSound";
+
+export default class HiHat extends LoadedSounded
+{
+ constructor(triggerID)
+ {
+ super(triggerID, "hi_hat.wav")
+ }
+
+ _setup()
+ {
+ this.source = this.experience.audioContext.createBufferSource()
+ this.source.buffer = this.buffer
+ this.source.connect(this.experience.audioContext.destination)
+
+ }
+
+ _playSound()
+ {
+ this.source.start(this.experience.audioContext.currentTime)
+ }
+}9
\ No newline at end of file
diff --git a/src/experience/sounds/Kick.js b/src/experience/sounds/Kick.js
new file mode 100644
index 00000000..07296d7a
--- /dev/null
+++ b/src/experience/sounds/Kick.js
@@ -0,0 +1,32 @@
+import Sound from "./api/Sound";
+
+export default class Kick extends Sound
+{
+ constructor(triggerID)
+ {
+ super(triggerID)
+ }
+
+ _setup()
+ {
+ this.oscillator = this.experience.audioContext.createOscillator();
+ this.gain = this.experience.audioContext.createGain();
+
+ //connect oscillator node to gain node
+ this.oscillator.connect(this.gain)
+ //connect gain node to speakers
+ this.gain.connect(this.experience.audioContext.destination)
+ }
+
+ _playSound()
+ {
+ let time = this.experience.audioContext.currentTime
+ this.gain.gain.setValueAtTime(1, time)
+ this.gain.gain.exponentialRampToValueAtTime(0.001, time + 0.5 * this.experience.params.kickTimeMultiplier)
+ this.oscillator.frequency.setValueAtTime(150 * this.experience.params.kickTimeMultiplier, time)
+ this.oscillator.frequency.exponentialRampToValueAtTime(0.001, time + 0.5 * this.experience.params.kickTimeMultiplier)
+
+ this.oscillator.start(time)
+ this.oscillator.stop(time + 2 * this.experience.params.kickTimeMultiplier)
+ }
+}
\ No newline at end of file
diff --git a/src/experience/sounds/Snare.js b/src/experience/sounds/Snare.js
new file mode 100644
index 00000000..c7153a83
--- /dev/null
+++ b/src/experience/sounds/Snare.js
@@ -0,0 +1,65 @@
+import Sound from "./api/Sound";
+
+export default class Snare extends Sound
+{
+ constructor(triggerID)
+ {
+ super(triggerID)
+ }
+
+ noiseBuffer()
+ {
+ let bufferSize = this.experience.audioContext.sampleRate;
+ let buffer = this.experience.audioContext.createBuffer(1, bufferSize, this.experience.audioContext.sampleRate)
+ let output = buffer.getChannelData(0)
+
+ for(let i = 0; i < bufferSize; i++)
+ {
+ output[i] = Math.random() * 2 - 1
+ }
+
+ return buffer;
+ }
+
+ _setup()
+ {
+ this.noise = this.experience.audioContext.createBufferSource();
+ this.noise.buffer = this.noiseBuffer()
+
+ let noiseFilter = this.experience.audioContext.createBiquadFilter()
+ noiseFilter.type = "highpass"
+ noiseFilter.frequency.value = 1000;
+ this.noise.connect(noiseFilter)
+
+ this.noiseEnvelope = this.experience.audioContext.createGain()
+ noiseFilter.connect(this.noiseEnvelope)
+ this.noiseEnvelope.connect(this.experience.audioContext.destination)
+
+ //Create Snap effect of snare
+ this.oscillator = this.experience.audioContext.createOscillator()
+ this.oscillator.type = "triangle"
+
+ this.oscillatorEnvelope = this.experience.audioContext.createGain()
+ this.oscillator.connect(this.oscillatorEnvelope)
+ this.oscillatorEnvelope.connect(this.experience.audioContext.destination)
+ }
+
+ _playSound()
+ {
+ let time = this.experience.audioContext.currentTime
+
+ this.noiseEnvelope.gain.setValueAtTime(1, time)
+
+ this.noiseEnvelope.gain.exponentialRampToValueAtTime(0.01, time + 0.2)
+ this.noise.start(time)
+
+ this.oscillator.frequency.setValueAtTime(100, time)
+ this.oscillatorEnvelope.gain.setValueAtTime(0.7, time)
+ this.oscillatorEnvelope.gain.exponentialRampToValueAtTime(0.01, time + 0,1)
+ this.oscillator.start(time)
+
+ this.oscillator.stop(time + 0.2)
+ this.noise.stop(time + 0.2)
+ }
+
+}
\ No newline at end of file
diff --git a/src/experience/sounds/api/LoadedSound.js b/src/experience/sounds/api/LoadedSound.js
new file mode 100644
index 00000000..45745741
--- /dev/null
+++ b/src/experience/sounds/api/LoadedSound.js
@@ -0,0 +1,59 @@
+import Sound from "./Sound";
+
+export default class LoadedSounded extends Sound
+{
+ constructor(fileName, triggerID)
+ {
+ super(triggerID)
+ this.fileName = fileName
+ this.loaded = false
+ this._loadSound()
+ }
+
+ _setup()
+ {
+ this.source = this.experience.audioContext.createBufferSource()
+ this.source.buffer = this.buffer
+ this.gain = this.experience.audioContext.createGain()
+ this.source.connect(this.gain)
+
+ this.gain.connect(this.experience.audioContext.destination)
+
+ }
+
+ _playSound()
+ {
+ let gain = this.experience.params[`btn${this.triggerID}_gain`]
+ console.log(gain)
+ let time = this.experience.audioContext.currentTime
+ this.gain.gain.setValueAtTime(gain, time)
+ this.source.start(this.experience.audioContext.currentTime)
+ }
+
+ trigger()
+ {
+ if(!this.loaded)
+ throw new Error("Sound not loaded!")
+
+ this._setup()
+ this._playSound()
+ }
+
+ _loadSound()
+ {
+ fetch( `${this.fileName}`, {
+ method:'GET'
+ })
+ .then(response => {
+ if(!response.ok)
+ throw new Error(`HTTP error, status = ${response.status}`)
+
+ return response.arrayBuffer()
+ })
+ .then(buffer => this.experience.audioContext.decodeAudioData(buffer))
+ .then(decodedData => {
+ this.buffer = decodedData;
+ this.loaded = true;
+ })
+ }
+}
\ No newline at end of file
diff --git a/src/experience/sounds/api/Sound.js b/src/experience/sounds/api/Sound.js
new file mode 100644
index 00000000..a8260adf
--- /dev/null
+++ b/src/experience/sounds/api/Sound.js
@@ -0,0 +1,29 @@
+import Experience from "../../Experience"
+
+export default class Sound
+{
+ constructor(triggerID)
+ {
+ this.triggerID = triggerID;
+ this.experience = new Experience()
+ this.experience.buttonPressHandler.addButtonPressEvent(`btn${triggerID}`, () => (this.trigger()))
+ this.experience.keyPressHandler.addKeyPress(`${triggerID}`, () => (this.trigger()))
+ }
+
+ _setup()
+ {
+ throw new Error("Method '_setup()' must be implemented.")
+
+ }
+
+ _playSound()
+ {
+ throw new Error("Method '_playSound()' must be implemented.")
+ }
+
+ trigger()
+ {
+ this._setup()
+ this._playSound()
+ }
+}
\ No newline at end of file
diff --git a/src/experience/util/ButtonPressHandler.js b/src/experience/util/ButtonPressHandler.js
new file mode 100644
index 00000000..6c656c71
--- /dev/null
+++ b/src/experience/util/ButtonPressHandler.js
@@ -0,0 +1,15 @@
+import Experience from "../Experience";
+
+export default class ButtonPressHandler
+{
+
+ constructor()
+ {
+ this.experience = new Experience();
+ }
+
+ addButtonPressEvent(elementID, triggerFunction)
+ {
+ document.getElementById(elementID).addEventListener('click', () => triggerFunction())
+ }
+}
\ No newline at end of file
diff --git a/src/experience/util/KeyPressHandler.js b/src/experience/util/KeyPressHandler.js
new file mode 100644
index 00000000..901b7688
--- /dev/null
+++ b/src/experience/util/KeyPressHandler.js
@@ -0,0 +1,59 @@
+import Experience from "../Experience";
+
+export default class KeyPressHandler
+{
+ constructor()
+ {
+ this.experience = new Experience()
+ //stores keycode, function to call
+ this.keyMap = new Map();
+ window.addEventListener("keydown", (event) => (this._onKeyPress(event)))
+ }
+
+ addKeyPress(key, triggerFunction)
+ {
+ this.keyMap.set(key.toUpperCase().charCodeAt(0), triggerFunction)
+ this.keyMap.set(this._getNumPadKey(key), triggerFunction)
+ }
+
+ removeKeyPress(key)
+ {
+ this.keyMap.delete(key)
+ }
+
+ _onKeyPress(event)
+ {
+ let key = event.keyCode
+ let triggerFunction = this.keyMap.get(key)
+
+ if(triggerFunction)
+ triggerFunction()
+ }
+
+ _getNumPadKey(key)
+ {
+ switch(key)
+ {
+ case "9":
+ return 33
+ case "8":
+ return 38
+ case "7":
+ return 36
+ case "6":
+ return 39
+ case "5":
+ return 12
+ case "4":
+ return 32
+ case "3":
+ return 34
+ case "2":
+ return 40
+ case "1":
+ return 35
+ }
+
+ return key;
+ }
+}
\ No newline at end of file
diff --git a/src/index.html b/src/index.html
new file mode 100644
index 00000000..093050a9
--- /dev/null
+++ b/src/index.html
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/script.js b/src/script.js
new file mode 100644
index 00000000..d2f94a26
--- /dev/null
+++ b/src/script.js
@@ -0,0 +1,5 @@
+import './style.css'
+import Experience from "./experience/Experience";
+
+let experience = new Experience("experience")
+window.experience = experience
\ No newline at end of file
diff --git a/src/style.css b/src/style.css
new file mode 100644
index 00000000..e349d184
--- /dev/null
+++ b/src/style.css
@@ -0,0 +1,158 @@
+body
+{
+ background-color: #C6E2E9;
+}
+
+#launchpad
+{
+ margin: auto;
+ background-color: #28282B;
+ width: 80vh;
+ height: 80vh;
+ border-radius: 5vw;
+ align-items: center;
+ text-align: center;
+}
+
+.pushable {
+ background-color: #CCCCCC;
+ border: none;
+ border-radius: 12px;
+ padding: 0;
+ margin: 20px;
+ cursor: pointer;
+}
+
+.front {
+ display: block;
+ padding: 10vh 10vh;
+ border-radius: 12px;
+ font-size: 1.25rem;
+ background-color: #EFF2EF;
+ color: white;
+ transform: translateY(-7px);
+}
+
+
+
+.front {
+ will-change: transform;
+ transition: transform 250ms;
+ }
+
+.pushable:hover .front {
+ transform: translateY(-6px);
+}
+
+.pushable:active .front {
+ transform: translateY(-2px);
+}
+
+#btn9 .front:active
+{
+ background-color: #e8edbe;
+}
+
+#btn9:active
+{
+ background-color: #babe98;
+}
+
+#btn8 .front:active
+{
+ background-color: #ccedbe;
+}
+
+#btn8:active
+{
+ background-color: #8fa685;
+}
+
+#btn7 .front:active
+{
+ background-color: #beedd7;
+}
+
+#btn7:active
+{
+ background-color: #98beac;
+}
+
+#btn6 .front:active
+{
+ background-color: #bec9ed;
+}
+
+#btn6:active
+{
+ background-color: #98a1be;
+}
+
+#btn5 .front:active
+{
+ background-color: #dcbeed;
+}
+
+#btn5:active
+{
+ background-color: #b098be;
+}
+
+#btn4 .front:active
+{
+ background-color: #edbedd;
+}
+
+#btn4:active
+{
+ background-color: #be98b1;
+}
+
+#btn3 .front:active
+{
+ background-color: #edc4be;
+}
+
+#btn3:active
+{
+ background-color: #be9d98;
+}
+
+#btn2 .front:active
+{
+ background-color: #ede6be;
+}
+
+#btn2:active
+{
+ background-color: #beb898;
+}
+
+#btn1 .front:active
+{
+ background-color: #beedd5;
+}
+
+#btn1:active
+{
+ background-color: #98beaa;
+}
+
+
+#information
+{
+ position: absolute;
+ z-index: 10;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ padding: 10px;
+ align-items: center;
+ text-align: center;
+
+ background-color: #ECB16D;
+ border-radius: 56px;
+ filter: drop-shadow(0px 0px 3px rgba(0, 0, 0, 25%));
+ color: white;
+
+}
\ No newline at end of file
diff --git a/static/fly_hi_hat.wav b/static/fly_hi_hat.wav
new file mode 100644
index 00000000..3ebc1d49
Binary files /dev/null and b/static/fly_hi_hat.wav differ
diff --git a/static/hi_hat.wav b/static/hi_hat.wav
new file mode 100644
index 00000000..9da0747f
Binary files /dev/null and b/static/hi_hat.wav differ
diff --git a/static/low_and_slow_kick.wav b/static/low_and_slow_kick.wav
new file mode 100644
index 00000000..5dd0d310
Binary files /dev/null and b/static/low_and_slow_kick.wav differ
diff --git a/static/short_and_sweet_hi_hat.wav b/static/short_and_sweet_hi_hat.wav
new file mode 100644
index 00000000..f77f2c84
Binary files /dev/null and b/static/short_and_sweet_hi_hat.wav differ
diff --git a/static/sizzel_snap.wav b/static/sizzel_snap.wav
new file mode 100644
index 00000000..9b3b327e
Binary files /dev/null and b/static/sizzel_snap.wav differ
diff --git a/static/throat_kick.wav b/static/throat_kick.wav
new file mode 100644
index 00000000..45e7cedc
Binary files /dev/null and b/static/throat_kick.wav differ
diff --git a/static/tunnel_shaker.wav b/static/tunnel_shaker.wav
new file mode 100644
index 00000000..b358ffbe
Binary files /dev/null and b/static/tunnel_shaker.wav differ