10 <12html 10lang=05"de"> <12head> <12meta 10charset=05"UTF-8"> <12meta 10name=05"viewport" 10content=05"width=device-width, initial-scale=1.0"> <12title>KORG Kaoscillator - Digital Hardcore (Low-Latency) <12style> 15 :14root15 { 15 11--bg-color:15 02#0d0d0d; 15 11--panel-bg:15 02#18181f; 15 11--accent-yellow:15 02#ffcc00; 15 11--accent-red:15 02#ff3333; 15 } 15 12body15 { 15 02background-color:15 07var(11--bg-color); 15 02color:15 02#fff; 15 02font-family:15 05'Courier New',15 Courier,15 02monospace; 15 02display:15 02flex; 15 02justify-content:15 02center; 15 02align-items:15 02center; 15 02min-height:15 0210010vh; 15 02margin:15 020; 15 02user-select:15 02none; 15 } 15 .07kaoss-container15 { 15 02background:15 07var(11--panel-bg); 15 02border:15 02410px15 02solid15 02#333; 15 02border-radius:15 021610px; 15 02padding:15 022010px; 15 02box-shadow:15 02015 021010px15 023010px15 10rgba(020,020,020,020.9),15 02inset15 02015 02015 021510px15 10rgba(02255,02255,02255,020.05); 15 02width:15 0238010px; 15 02display:15 02flex; 15 02flex-direction:15 02column; 15 02gap:15 021210px; 15 } 15 .07header15 { 15 02display:15 02flex; 15 02justify-content:15 02space-between; 15 02align-items:15 02center; 15 02border-bottom:15 02210px15 02solid15 02#333; 15 02padding-bottom:15 02610px; 15 } 15 .07title15 { 15 02font-weight:15 02bold; 15 02font-size:15 021.110rem; 15 02color:15 07var(11--accent-yellow); 15 02letter-spacing:15 02210px; 15 } 15 .07subtitle15 { 15 02font-size:15 020.6510rem; 15 02color:15 07var(11--accent-red); 15 02font-weight:15 02bold; 15 } 15 .07display15 { 15 02background:15 02#000; 15 02border:15 02210px15 02solid15 02#444; 15 02border-radius:15 02610px; 15 02padding:15 02810px15 021210px; 15 02display:15 02flex; 15 02flex-direction:15 02column; 15 02gap:15 02410px; 15 02font-family:15 02monospace; 15 02color:15 07var(11--accent-yellow); 15 02text-shadow:15 02015 02015 02510px15 07var(11--accent-yellow); 15 02font-size:15 020.8510rem; 15 } 15 .07display-row15 { 15 02display:15 02flex; 15 02justify-content:15 02space-between; 15 } 15 .07pad-container15 { 15 02position:15 02relative; 15 02width:15 0210010%; 15 02height:15 0226010px; 15 02background:15 02#050505; 15 02border:15 02310px15 02solid15 02#444; 15 02border-radius:15 021210px; 15 02overflow:15 02hidden; 15 02cursor:15 02crosshair; 15 touch-action:15 02none; 15 } 15 12canvas15 { 15 02width:15 0210010%; 15 02height:15 0210010%; 15 } 15 .07controls15 { 15 02display:15 02grid; 15 02grid-template-columns:15 021fr15 021fr; 15 02gap:15 02810px; 15 } 15 .07control-group15 { 15 02display:15 02flex; 15 02flex-direction:15 02column; 15 02gap:15 02310px; 15 } 15 12label15 { 15 02font-size:15 020.6510rem; 15 02color:15 02#aaa; 15 02text-transform:15 02uppercase; 15 02font-weight:15 02bold; 15 } 15 12select,15 12button,15 12input15 { 15 02background:15 02#252530; 15 02color:15 02#fff; 15 02border:15 02110px15 02solid15 02#444; 15 02padding:15 02610px; 15 02border-radius:15 02410px; 15 02font-family:15 02inherit; 15 02font-size:15 020.7810rem; 15 02font-weight:15 02bold; 15 02cursor:15 02pointer; 15 } 15 12select:14focus,15 12button:14focus,15 12input:14focus15 { 15 02outline:15 02none; 15 02border-color:15 07var(11--accent-yellow); 15 } 15 12button.07active15 { 15 02background:15 07var(11--accent-red); 15 02color:15 02#fff; 15 02box-shadow:15 02015 02015 021010px15 07var(11--accent-red); 15 } 15  <12body> <12div 10class=05"kaoss-container"> <12div 10class=05"header"> <12div 10class=05"title">KAOSS-DHC <12div 10class=05"subtitle">ZERO-LATENCY EDITION <12div 10class=05"display"> <12div 10class=05"display-row"> <12span 10id=05"disp-preset">SYNTH: DHC SAW <12span 10id=05"disp-bpm">180 BPM <12div 10class=05"display-row"> <12span 10id=05"disp-scale">SKALA: PHRYGIAN DOM <12span 10id=05"disp-pattern">BEAT: DHC BREAK <12div 10class=05"pad-container" 10id=05"pad"> <12canvas 10id=05"visualizer"> <12div 10class=05"controls"> <12div 10class=05"control-group"> <12label 10for=05"preset">Preset <12select 10id=05"preset"> <12option 10value=05"saw">Aggro Saw Lead <12option 10value=05"acid">Screaming 303 <12option 10value=05"sub">Distorted Sub <12option 10value=05"noise">Cyber Chaos <12div 10class=05"control-group"> <12label 10for=05"scale">Skala (Exotisch) <12select 10id=05"scale"> <12option 10value=05"phrygianDom">Phrygisch-Dominant <12option 10value=05"hirajoshi">Hirajoshi (Japan) <12option 10value=05"hungarianMin">Ungarisch Moll <12option 10value=05"arabic">Arabisch (Bayati) <12option 10value=05"pelog">Pelog (Bali) <12option 10value=05"minor">Min-Pentatonisch <12option 10value=05"blues">Blues <12option 10value=05"chromatic">Chromatisch <12option 10value=05"raw">Unquantisiert <12div 10class=05"control-group"> <12label 10for=05"beat-pattern">Drum Pattern <12select 10id=05"beat-pattern"> <12option 10value=05"dhc">DHC Breakbeat <12option 10value=05"amen">Amen Cyber Cut <12option 10value=05"gabber">Gabber 4-to-Floor <12option 10value=05"industrial">Industrial Sync <12option 10value=05"speedcore">Speedcore Terror <12div 10class=05"control-group"> <12label 10for=05"bpm">Tempo / Beat <12div 10style=05"display: flex; gap: 4px;"> <12input 10type=05"number" 10id=05"bpm" 10value=05"180" 10min=05"120" 10max=05"240" 10style=05"width: 45%;"> <12button 10id=05"beat-btn" 10style=05"width: 55%;">BEAT RUN <12script> 15 02let15 audioCtx15 =15 02null; 15 02let15 masterGain,15 driveNode,15 filterNode,15 mainOsc,15 subOsc; 15 02let15 noiseBuffer15 =15 02null; 15 02let15 isTouching15 =15 02false; 15 02let15 beatPlaying15 =15 02false; 15 15// Scheduler Variables (Low Latency Web Audio Clock) 15 02let15 lookahead15 =15 0225.0;15 15// ms 15 02let15 scheduleAheadTime15 =15 020.1;15 15// seconds 15 02let15 nextNoteTime15 =15 020.0; 15 02let15 current16thNote15 =15 020; 15 02let15 timerID15 =15 02null; 15 02const15 baseFreq15 =15 02110;15 15// A2 15 15// Exotic & Standard Scales 15 02const15 scales15 =15 { 15 phrygianDom:15 [020,15 021,15 024,15 025,15 027,15 028,15 0210,15 0212,15 0213,15 0216,15 0217,15 0219,15 0220,15 0222,15 0224], 15 hirajoshi:15 [020,15 022,15 023,15 027,15 028,15 0212,15 0214,15 0215,15 0219,15 0220,15 0224], 15 hungarianMin:15 [020,15 022,15 023,15 026,15 027,15 028,15 0211,15 0212,15 0214,15 0215,15 0218,15 0219,15 0220,15 0223,15 0224], 15 arabic:15 [020,15 021,15 024,15 025,15 027,15 028,15 0211,15 0212,15 0213,15 0216,15 0217,15 0219,15 0220,15 0223,15 0224], 15 pelog:15 [020,15 021,15 023,15 027,15 028,15 0212,15 0213,15 0215,15 0219,15 0220,15 0224], 15 minor:15 [020,15 023,15 025,15 027,15 0210,15 0212,15 0215,15 0217,15 0219,15 0222,15 0224], 15 blues:15 [020,15 023,15 025,15 026,15 027,15 0210,15 0212,15 0215,15 0217,15 0218,15 0219,15 0222,15 0224], 15 chromatic:15 [020,15 021,15 022,15 023,15 024,15 025,15 026,15 027,15 028,15 029,15 0210,15 0211,15 0212,15 0213,15 0214,15 0215,15 0216,15 0217,15 0218,15 0219,15 0220,15 0221,15 0222,15 0223,15 0224] 15 }; 15 15// 16-step Drum Patterns 15 02const15 drumPatterns15 =15 { 15 dhc:15 { 15 kick:15 [021,15 020,15 020,15 020,15 021,15 020,15 020,15 021,15 020,15 020,15 021,15 020,15 021,15 020,15 020,15 020], 15 snare:15 [020,15 020,15 021,15 020,15 020,15 020,15 021,15 020,15 020,15 021,15 020,15 020,15 020,15 021,15 021,15 020], 15 hat:15 [021,15 021,15 020,15 021,15 021,15 021,15 020,15 021,15 021,15 021,15 020,15 021,15 021,15 021,15 021,15 020] 15 }, 15 amen:15 { 15 kick:15 [021,15 020,15 020,15 020,15 020,15 020,15 021,15 020,15 020,15 021,15 020,15 020,15 020,15 020,15 020,15 020], 15 snare:15 [020,15 020,15 021,15 020,15 021,15 020,15 020,15 021,15 020,15 020,15 021,15 020,15 021,15 020,15 021,15 021], 15 hat:15 [021,15 021,15 021,15 021,15 021,15 021,15 021,15 021,15 021,15 021,15 021,15 021,15 021,15 021,15 021,15 021] 15 }, 15 gabber:15 { 15 kick:15 [021,15 020,15 020,15 020,15 021,15 020,15 020,15 020,15 021,15 020,15 020,15 020,15 021,15 020,15 020,15 020], 15 snare:15 [020,15 020,15 020,15 020,15 021,15 020,15 020,15 020,15 020,15 020,15 020,15 020,15 021,15 020,15 020,15 020], 15 hat:15 [020,15 020,15 021,15 020,15 020,15 020,15 021,15 020,15 020,15 020,15 021,15 020,15 020,15 020,15 021,15 020] 15 }, 15 industrial:15 { 15 kick:15 [021,15 020,15 021,15 020,15 020,15 020,15 021,15 020,15 021,15 020,15 020,15 021,15 020,15 020,15 021,15 020], 15 snare:15 [020,15 020,15 020,15 021,15 021,15 020,15 020,15 020,15 020,15 020,15 021,15 020,15 021,15 021,15 020,15 020], 15 hat:15 [021,15 020,15 021,15 020,15 021,15 020,15 021,15 020,15 021,15 020,15 021,15 020,15 021,15 020,15 021,15 020] 15 }, 15 speedcore:15 { 15 kick:15 [021,15 021,15 021,15 021,15 021,15 021,15 021,15 021,15 021,15 021,15 021,15 021,15 021,15 021,15 021,15 021], 15 snare:15 [020,15 020,15 021,15 020,15 020,15 020,15 021,15 020,15 020,15 020,15 021,15 020,15 020,15 020,15 021,15 020], 15 hat:15 [021,15 020,15 021,15 020,15 021,15 020,15 021,15 020,15 021,15 020,15 021,15 020,15 021,15 020,15 021,15 020] 15 } 15 }; 15 02function15 initAudio()15 { 15 02if15 (audioCtx)15 02return; 15 audioCtx15 =15 06new15 (10window.AudioContext15 ||15 10window.webkitAudioContext)({15 latencyHint:15 05'interactive'15 }); 15 masterGain15 =15 audioCtx.createGain(); 15 masterGain.gain.value15 =15 020.3; 15 15// Hardcore Overdrive Distortion Node 15 driveNode15 =15 audioCtx.createWaveShaper(); 15 driveNode.curve15 =15 makeDistortionCurve(0295); 15 driveNode.oversample15 =15 05'4x'; 15 15// Filter Node 15 filterNode15 =15 audioCtx.createBiquadFilter(); 15 filterNode.type15 =15 05'lowpass'; 15 filterNode.frequency.value15 =15 022000; 15 filterNode.Q.value15 =15 026; 15 filterNode.connect(driveNode); 15 driveNode.connect(masterGain); 15 masterGain.connect(audioCtx.destination); 15 15// Pre-generate noise buffer to avoid audio thread stalls 15 createNoiseBuffer(); 15 } 15 02function15 createNoiseBuffer()15 { 15 02const15 bufferSize15 =15 audioCtx.sampleRate15 *15 021.0; 15 noiseBuffer15 =15 audioCtx.createBuffer(021,15 bufferSize,15 audioCtx.sampleRate); 15 02const15 output15 =15 noiseBuffer.getChannelData(020); 15 02for15 (02let15 i15 =15 020;15 i15 <15 bufferSize;15 i++)15 { 15 output[i]15 =15 10Math.random()15 *15 02215 -15 021; 15 } 15 } 15 02function15 makeDistortionCurve(amount)15 { 15 02let15 k15 =15 amount,15 n15 =15 0244100,15 curve15 =15 06new15 10Float32Array(n),15 deg15 =15 10Math.PI15 /15 02180; 15 02for15 (02let15 i15 =15 020;15 i15 <15 n;15 ++i)15 { 15 02let15 x15 =15 i15 *15 02215 /15 n15 -15 021; 15 curve[i]15 =15 (02315 +15 k)15 *15 x15 *15 022015 *15 deg15 /15 (10Math.PI15 +15 k15 *15 10Math.abs(x)); 15 } 15 02return15 curve; 15 } 15 02function15 mtof(note)15 { 15 02return15 baseFreq15 *15 10Math.pow(022,15 note15 /15 0212); 15 } 15 02function15 startSynth()15 { 15 initAudio(); 15 02if15 (audioCtx.state15 ===15 05'suspended')15 audioCtx.resume(); 15 02if15 (mainOsc)15 02return; 15 02const15 preset15 =15 10document.getElementById(05'preset').value; 15 mainOsc15 =15 audioCtx.createOscillator(); 15 subOsc15 =15 audioCtx.createOscillator(); 15 02if15 (preset15 ===15 05'saw')15 { 15 mainOsc.type15 =15 05'sawtooth';15 subOsc.type15 =15 05'square'; 15 }15 02else15 02if15 (preset15 ===15 05'acid')15 { 15 mainOsc.type15 =15 05'sawtooth';15 subOsc.type15 =15 05'sawtooth'; 15 }15 02else15 02if15 (preset15 ===15 05'sub')15 { 15 mainOsc.type15 =15 05'triangle';15 subOsc.type15 =15 05'sine'; 15 }15 02else15 02if15 (preset15 ===15 05'noise')15 { 15 mainOsc.type15 =15 05'square';15 subOsc.type15 =15 05'sawtooth'; 15 } 15 mainOsc.connect(filterNode); 15 subOsc.connect(filterNode); 15 mainOsc.start(); 15 subOsc.start(); 15 } 15 02function15 stopSynth()15 { 15 02if15 (mainOsc)15 {15 mainOsc.stop();15 mainOsc.disconnect();15 mainOsc15 =15 02null;15 } 15 02if15 (subOsc)15 {15 subOsc.stop();15 subOsc.disconnect();15 subOsc15 =15 02null;15 } 15 } 15 02function15 updateSynth(x,15 y)15 { 15 02if15 (!mainOsc)15 02return; 15 02const15 scaleType15 =15 10document.getElementById(05'scale').value; 15 02let15 freq; 15 02if15 (scaleType15 ===15 05'raw')15 { 15 freq15 =15 028015 *15 10Math.pow(022,15 x15 *15 024); 15 }15 02else15 { 15 02const15 currentScale15 =15 scales[scaleType]; 15 02const15 noteIndex15 =15 10Math.floor(x15 *15 currentScale.length); 15 02const15 note15 =15 currentScale[10Math.min(noteIndex,15 currentScale.length15 -15 021)]; 15 freq15 =15 mtof(note); 15 } 15 02const15 minVal15 =15 02100,15 maxVal15 =15 0210000; 15 02const15 cutoff15 =15 minVal15 *15 10Math.pow(maxVal15 /15 minVal,15 y); 15 15// Immediate value scheduling for zero latency 15 02const15 now15 =15 audioCtx.currentTime; 15 mainOsc.frequency.setValueAtTime(freq,15 now); 15 subOsc.frequency.setValueAtTime(freq15 /15 022,15 now); 15 filterNode.frequency.setValueAtTime(cutoff,15 now); 15 02if15 (10document.getElementById(05'preset').value15 ===15 05'acid')15 { 15 filterNode.Q.setValueAtTime(02415 +15 (y15 *15 0218),15 now); 15 }15 02else15 { 15 filterNode.Q.setValueAtTime(024,15 now); 15 } 15 } 15 15// High-Precision Drum Synthesizer 15 02function15 triggerDrum(type,15 time)15 { 15 02if15 (type15 ===15 05'kick')15 { 15 02let15 osc15 =15 audioCtx.createOscillator(); 15 02let15 gain15 =15 audioCtx.createGain(); 15 osc.frequency.setValueAtTime(02180,15 time); 15 osc.frequency.exponentialRampToValueAtTime(0230,15 time15 +15 020.1); 15 gain.gain.setValueAtTime(021.4,15 time); 15 gain.gain.exponentialRampToValueAtTime(020.001,15 time15 +15 020.14); 15 osc.connect(driveNode); 15 osc.start(time);15 osc.stop(time15 +15 020.14); 15 }15 02else15 02if15 (type15 ===15 05'snare')15 { 15 02let15 noiseSrc15 =15 audioCtx.createBufferSource(); 15 noiseSrc.buffer15 =15 noiseBuffer; 15 02let15 filter15 =15 audioCtx.createBiquadFilter(); 15 filter.type15 =15 05'highpass'; 15 filter.frequency.value15 =15 02800; 15 02let15 gain15 =15 audioCtx.createGain(); 15 gain.gain.setValueAtTime(020.9,15 time); 15 gain.gain.exponentialRampToValueAtTime(020.01,15 time15 +15 020.09); 15 noiseSrc.connect(filter); 15 filter.connect(driveNode); 15 noiseSrc.start(time);15 noiseSrc.stop(time15 +15 020.09); 15 }15 02else15 02if15 (type15 ===15 05'hat')15 { 15 02let15 noiseSrc15 =15 audioCtx.createBufferSource(); 15 noiseSrc.buffer15 =15 noiseBuffer; 15 02let15 filter15 =15 audioCtx.createBiquadFilter(); 15 filter.type15 =15 05'highpass'; 15 filter.frequency.value15 =15 026000; 15 02let15 gain15 =15 audioCtx.createGain(); 15 gain.gain.setValueAtTime(020.4,15 time); 15 gain.gain.exponentialRampToValueAtTime(020.001,15 time15 +15 020.04); 15 noiseSrc.connect(filter); 15 filter.connect(driveNode); 15 noiseSrc.start(time);15 noiseSrc.stop(time15 +15 020.04); 15 } 15 } 15 15// Web Audio Lookahead Scheduler 15 02function15 nextNote()15 { 15 02const15 bpm15 =15 10parseInt(10document.getElementById(05'bpm').value); 15 02const15 secondsPerBeat15 =15 0260.015 /15 bpm; 15 nextNoteTime15 +=15 020.2515 *15 secondsPerBeat;15 15// 16th note step 15 current16thNote15 =15 (current16thNote15 +15 021)15 %15 0216; 15 } 15 02function15 scheduleNote(step,15 time)15 { 15 02const15 patKey15 =15 10document.getElementById(05'beat-pattern').value; 15 02const15 pat15 =15 drumPatterns[patKey]; 15 02if15 (pat.kick[step])15 triggerDrum(05'kick',15 time); 15 02if15 (pat.snare[step])15 triggerDrum(05'snare',15 time); 15 02if15 (pat.hat[step])15 triggerDrum(05'hat',15 time); 15 } 15 02function15 scheduler()15 { 15 02while15 (nextNoteTime15 <15 audioCtx.currentTime15 +15 scheduleAheadTime)15 { 15 scheduleNote(current16thNote,15 nextNoteTime); 15 nextNote(); 15 } 15 02if15 (beatPlaying)15 { 15 timerID15 =15 10window.setTimeout(scheduler,15 lookahead); 15 } 15 } 15 10document.getElementById(05'beat-btn').addEventListener(05'click',15 (e)15 =>15 { 15 initAudio(); 15 02if15 (audioCtx.state15 ===15 05'suspended')15 audioCtx.resume(); 15 beatPlaying15 =15 !beatPlaying; 15 e.target.classList.toggle(05'active',15 beatPlaying); 15 02if15 (beatPlaying)15 { 15 current16thNote15 =15 020; 15 nextNoteTime15 =15 audioCtx.currentTime; 15 scheduler(); 15 }15 02else15 { 15 clearTimeout(timerID); 15 } 15 }); 15 15// Touch / Mouse Interaction with RAF Decoupling for smooth performance 15 02const15 pad15 =15 10document.getElementById(05'pad'); 15 02const15 canvas15 =15 10document.getElementById(05'visualizer'); 15 02const15 ctx15 =15 canvas.getContext(05'2d'); 15 02let15 touchX15 =15 020,15 touchY15 =15 020; 15 02function15 resizeCanvas()15 { 15 canvas.width15 =15 pad.clientWidth;15 canvas.height15 =15 pad.clientHeight; 15 } 15 10window.addEventListener(05'resize',15 resizeCanvas); 15 resizeCanvas(); 15 02function15 handleInputStart(e)15 { 15 isTouching15 =15 02true; 15 startSynth(); 15 handleInputMove(e); 15 } 15 02function15 handleInputEnd()15 { 15 isTouching15 =15 02false; 15 stopSynth(); 15 } 15 02function15 handleInputMove(e)15 { 15 02if15 (!isTouching)15 02return; 15 02const15 rect15 =15 pad.getBoundingClientRect(); 15 02const15 clientX15 =15 e.touches15 ?15 e.touches[020].clientX15 :15 e.clientX; 15 02const15 clientY15 =15 e.touches15 ?15 e.touches[020].clientY15 :15 e.clientY; 15 touchX15 =15 10Math.max(020,15 10Math.min(021,15 (clientX15 -15 rect.left)15 /15 rect.width)); 15 touchY15 =15 10Math.max(020,15 10Math.min(021,15 02115 -15 (clientY15 -15 rect.top)15 /15 rect.height)); 15 updateSynth(touchX,15 touchY); 15 } 15 pad.addEventListener(05'mousedown',15 handleInputStart); 15 10window.addEventListener(05'mouseup',15 handleInputEnd); 15 pad.addEventListener(05'mousemove',15 handleInputMove); 15 pad.addEventListener(05'touchstart',15 handleInputStart,15 {15 passive:15 02true15 }); 15 10window.addEventListener(05'touchend',15 handleInputEnd); 15 pad.addEventListener(05'touchmove',15 handleInputMove,15 {15 passive:15 02true15 }); 15 15// Canvas Visualization Loop 15 02function15 renderVisualizer()15 { 15 requestAnimationFrame(renderVisualizer); 15 ctx.fillStyle15 =15 05'rgba(5, 5, 5, 0.25)'; 15 ctx.fillRect(020,15 020,15 canvas.width,15 canvas.height); 15 15// Grid 15 ctx.strokeStyle15 =15 05'#1a1a1a'; 15 ctx.lineWidth15 =15 021; 15 02for15 (02let15 i15 =15 020;15 i15 <15 canvas.width;15 i15 +=15 0230)15 { 15 ctx.beginPath();15 ctx.moveTo(i,15 020);15 ctx.lineTo(i,15 canvas.height);15 ctx.stroke(); 15 } 15 02for15 (02let15 j15 =15 020;15 j15 <15 canvas.height;15 j15 +=15 0230)15 { 15 ctx.beginPath();15 ctx.moveTo(020,15 j);15 ctx.lineTo(canvas.width,15 j);15 ctx.stroke(); 15 } 15 15// Touch Cursor 15 02if15 (isTouching)15 { 15 02const15 cx15 =15 touchX15 *15 canvas.width; 15 02const15 cy15 =15 (02115 -15 touchY)15 *15 canvas.height; 15 ctx.strokeStyle15 =15 05'rgba(255, 204, 0, 0.6)'; 15 ctx.beginPath();15 ctx.moveTo(cx,15 020);15 ctx.lineTo(cx,15 canvas.height);15 ctx.stroke(); 15 ctx.beginPath();15 ctx.moveTo(020,15 cy);15 ctx.lineTo(canvas.width,15 cy);15 ctx.stroke(); 15 ctx.fillStyle15 =15 05'#ff3333'; 15 ctx.shadowColor15 =15 05'#ff3333'; 15 ctx.shadowBlur15 =15 0215; 15 ctx.beginPath(); 15 ctx.arc(cx,15 cy,15 0210,15 020,15 10Math.PI15 *15 022); 15 ctx.fill(); 15 ctx.shadowBlur15 =15 020; 15 } 15 } 15 renderVisualizer(); 15 15// UI Display Listeners 15 10document.getElementById(05'scale').addEventListener(05'change',15 (e)15 =>15 { 15 10document.getElementById(05'disp-scale').innerText15 =15 05`05SKALA: 05${e.target.options[e.target.selectedIndex].text.toUpperCase()05}05`; 15 }); 15 10document.getElementById(05'preset').addEventListener(05'change',15 (e)15 =>15 { 15 10document.getElementById(05'disp-preset').innerText15 =15 05`05SYNTH: 05${e.target.options[e.target.selectedIndex].text.toUpperCase()05}05`; 15 02if15 (mainOsc)15 {15 stopSynth();15 startSynth();15 } 15 }); 15 10document.getElementById(05'beat-pattern').addEventListener(05'change',15 (e)15 =>15 { 15 10document.getElementById(05'disp-pattern').innerText15 =15 05`05BEAT: 05${e.target.options[e.target.selectedIndex].text.toUpperCase()05}05`; 15 }); 15 10document.getElementById(05'bpm').addEventListener(05'change',15 (e)15 =>15 { 15 10document.getElementById(05'disp-bpm').innerText15 =15 05`05${e.target.value05}05 BPM05`; 15 });