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)12title>
<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 12style>
12head>
<12body>
<12div 10class=05"kaoss-container">
<12div 10class=05"header">
<12div 10class=05"title">KAOSS-DHC12div>
<12div 10class=05"subtitle">ZERO-LATENCY EDITION12div>
12div>
<12div 10class=05"display">
<12div 10class=05"display-row">
<12span 10id=05"disp-preset">SYNTH: DHC SAW12span>
<12span 10id=05"disp-bpm">180 BPM12span>
12div>
<12div 10class=05"display-row">
<12span 10id=05"disp-scale">SKALA: PHRYGIAN DOM12span>
<12span 10id=05"disp-pattern">BEAT: DHC BREAK12span>
12div>
12div>
<12div 10class=05"pad-container" 10id=05"pad">
<12canvas 10id=05"visualizer">12canvas>
12div>
<12div 10class=05"controls">
<12div 10class=05"control-group">
<12label 10for=05"preset">Preset12label>
<12select 10id=05"preset">
<12option 10value=05"saw">Aggro Saw Lead12option>
<12option 10value=05"acid">Screaming 30312option>
<12option 10value=05"sub">Distorted Sub12option>
<12option 10value=05"noise">Cyber Chaos12option>
12select>
12div>
<12div 10class=05"control-group">
<12label 10for=05"scale">Skala (Exotisch)12label>
<12select 10id=05"scale">
<12option 10value=05"phrygianDom">Phrygisch-Dominant12option>
<12option 10value=05"hirajoshi">Hirajoshi (Japan)12option>
<12option 10value=05"hungarianMin">Ungarisch Moll12option>
<12option 10value=05"arabic">Arabisch (Bayati)12option>
<12option 10value=05"pelog">Pelog (Bali)12option>
<12option 10value=05"minor">Min-Pentatonisch12option>
<12option 10value=05"blues">Blues12option>
<12option 10value=05"chromatic">Chromatisch12option>
<12option 10value=05"raw">Unquantisiert12option>
12select>
12div>
<12div 10class=05"control-group">
<12label 10for=05"beat-pattern">Drum Pattern12label>
<12select 10id=05"beat-pattern">
<12option 10value=05"dhc">DHC Breakbeat12option>
<12option 10value=05"amen">Amen Cyber Cut12option>
<12option 10value=05"gabber">Gabber 4-to-Floor12option>
<12option 10value=05"industrial">Industrial Sync12option>
<12option 10value=05"speedcore">Speedcore Terror12option>
12select>
12div>
<12div 10class=05"control-group">
<12label 10for=05"bpm">Tempo / Beat12label>
<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 RUN12button>
12div>
12div>
12div>
12div>
<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 });
12script>
12body>
12html>