<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>KORG Kaoscillator - Digital Hardcore (Low-Latency)</title>
<style>
:root {
--bg-color: #0d0d0d;
--panel-bg: #18181f;
--accent-yellow: #ffcc00;
--accent-red: #ff3333;
}
body {
background-color: var(--bg-color);
color: #fff;
font-family: 'Courier New', Courier, monospace;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
user-select: none;
}
.kaoss-container {
background: var(--panel-bg);
border: 4px solid #333;
border-radius: 16px;
padding: 20px;
box-shadow: 0 10px 30px rgba(0,0,0,0.9), inset 0 0 15px rgba(255,255,255,0.05);
width: 380px;
display: flex;
flex-direction: column;
gap: 12px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid #333;
padding-bottom: 6px;
}
.title {
font-weight: bold;
font-size: 1.1rem;
color: var(--accent-yellow);
letter-spacing: 2px;
}
.subtitle {
font-size: 0.65rem;
color: var(--accent-red);
font-weight: bold;
}
.display {
background: #000;
border: 2px solid #444;
border-radius: 6px;
padding: 8px 12px;
display: flex;
flex-direction: column;
gap: 4px;
font-family: monospace;
color: var(--accent-yellow);
text-shadow: 0 0 5px var(--accent-yellow);
font-size: 0.85rem;
}
.display-row {
display: flex;
justify-content: space-between;
}
.pad-container {
position: relative;
width: 100%;
height: 260px;
background: #050505;
border: 3px solid #444;
border-radius: 12px;
overflow: hidden;
cursor: crosshair;
touch-action: none;
}
canvas {
width: 100%;
height: 100%;
}
.controls {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.control-group {
display: flex;
flex-direction: column;
gap: 3px;
}
label {
font-size: 0.65rem;
color: #aaa;
text-transform: uppercase;
font-weight: bold;
}
select, button, input {
background: #252530;
color: #fff;
border: 1px solid #444;
padding: 6px;
border-radius: 4px;
font-family: inherit;
font-size: 0.78rem;
font-weight: bold;
cursor: pointer;
}
select:focus, button:focus, input:focus {
outline: none;
border-color: var(--accent-yellow);
}
button.active {
background: var(--accent-red);
color: #fff;
box-shadow: 0 0 10px var(--accent-red);
}
</style>
</head>
<body>
<div class="kaoss-container">
<div class="header">
<div class="title">KAOSS-DHC</div>
<div class="subtitle">ZERO-LATENCY EDITION</div>
</div>
<div class="display">
<div class="display-row">
<span id="disp-preset">SYNTH: DHC SAW</span>
<span id="disp-bpm">180 BPM</span>
</div>
<div class="display-row">
<span id="disp-scale">SKALA: PHRYGIAN DOM</span>
<span id="disp-pattern">BEAT: DHC BREAK</span>
</div>
</div>
<div class="pad-container" id="pad">
<canvas id="visualizer"></canvas>
</div>
<div class="controls">
<div class="control-group">
<label for="preset">Preset</label>
<select id="preset">
<option value="saw">Aggro Saw Lead</option>
<option value="acid">Screaming 303</option>
<option value="sub">Distorted Sub</option>
<option value="noise">Cyber Chaos</option>
</select>
</div>
<div class="control-group">
<label for="scale">Skala (Exotisch)</label>
<select id="scale">
<option value="phrygianDom">Phrygisch-Dominant</option>
<option value="hirajoshi">Hirajoshi (Japan)</option>
<option value="hungarianMin">Ungarisch Moll</option>
<option value="arabic">Arabisch (Bayati)</option>
<option value="pelog">Pelog (Bali)</option>
<option value="minor">Min-Pentatonisch</option>
<option value="blues">Blues</option>
<option value="chromatic">Chromatisch</option>
<option value="raw">Unquantisiert</option>
</select>
</div>
<div class="control-group">
<label for="beat-pattern">Drum Pattern</label>
<select id="beat-pattern">
<option value="dhc">DHC Breakbeat</option>
<option value="amen">Amen Cyber Cut</option>
<option value="gabber">Gabber 4-to-Floor</option>
<option value="industrial">Industrial Sync</option>
<option value="speedcore">Speedcore Terror</option>
</select>
</div>
<div class="control-group">
<label for="bpm">Tempo / Beat</label>
<div style="display: flex; gap: 4px;">
<input type="number" id="bpm" value="180" min="120" max="240" style="width: 45%;">
<button id="beat-btn" style="width: 55%;">BEAT RUN</button>
</div>
</div>
</div>
</div>
<script>
let audioCtx = null;
let masterGain, driveNode, filterNode, mainOsc, subOsc;
let noiseBuffer = null;
let isTouching = false;
let beatPlaying = false;
// Scheduler Variables (Low Latency Web Audio Clock)
let lookahead = 25.0; // ms
let scheduleAheadTime = 0.1; // seconds
let nextNoteTime = 0.0;
let current16thNote = 0;
let timerID = null;
const baseFreq = 110; // A2
// Exotic & Standard Scales
const scales = {
phrygianDom: [0, 1, 4, 5, 7, 8, 10, 12, 13, 16, 17, 19, 20, 22, 24],
hirajoshi: [0, 2, 3, 7, 8, 12, 14, 15, 19, 20, 24],
hungarianMin: [0, 2, 3, 6, 7, 8, 11, 12, 14, 15, 18, 19, 20, 23, 24],
arabic: [0, 1, 4, 5, 7, 8, 11, 12, 13, 16, 17, 19, 20, 23, 24],
pelog: [0, 1, 3, 7, 8, 12, 13, 15, 19, 20, 24],
minor: [0, 3, 5, 7, 10, 12, 15, 17, 19, 22, 24],
blues: [0, 3, 5, 6, 7, 10, 12, 15, 17, 18, 19, 22, 24],
chromatic: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]
};
// 16-step Drum Patterns
const drumPatterns = {
dhc: {
kick: [1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0],
snare: [0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0],
hat: [1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0]
},
amen: {
kick: [1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0],
snare: [0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1],
hat: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
},
gabber: {
kick: [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
snare: [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
hat: [0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0]
},
industrial: {
kick: [1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0],
snare: [0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0],
hat: [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0]
},
speedcore: {
kick: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
snare: [0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0],
hat: [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0]
}
};
function initAudio() {
if (audioCtx) return;
audioCtx = new (window.AudioContext || window.webkitAudioContext)({ latencyHint: 'interactive' });
masterGain = audioCtx.createGain();
masterGain.gain.value = 0.3;
// Hardcore Overdrive Distortion Node
driveNode = audioCtx.createWaveShaper();
driveNode.curve = makeDistortionCurve(95);
driveNode.oversample = '4x';
// Filter Node
filterNode = audioCtx.createBiquadFilter();
filterNode.type = 'lowpass';
filterNode.frequency.value = 2000;
filterNode.Q.value = 6;
filterNode.connect(driveNode);
driveNode.connect(masterGain);
masterGain.connect(audioCtx.destination);
// Pre-generate noise buffer to avoid audio thread stalls
createNoiseBuffer();
}
function createNoiseBuffer() {
const bufferSize = audioCtx.sampleRate * 1.0;
noiseBuffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
const output = noiseBuffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
output[i] = Math.random() * 2 - 1;
}
}
function makeDistortionCurve(amount) {
let k = amount, n = 44100, curve = new Float32Array(n), deg = Math.PI / 180;
for (let i = 0; i < n; ++i) {
let x = i * 2 / n - 1;
curve[i] = (3 + k) * x * 20 * deg / (Math.PI + k * Math.abs(x));
}
return curve;
}
function mtof(note) {
return baseFreq * Math.pow(2, note / 12);
}
function startSynth() {
initAudio();
if (audioCtx.state === 'suspended') audioCtx.resume();
if (mainOsc) return;
const preset = document.getElementById('preset').value;
mainOsc = audioCtx.createOscillator();
subOsc = audioCtx.createOscillator();
if (preset === 'saw') {
mainOsc.type = 'sawtooth'; subOsc.type = 'square';
} else if (preset === 'acid') {
mainOsc.type = 'sawtooth'; subOsc.type = 'sawtooth';
} else if (preset === 'sub') {
mainOsc.type = 'triangle'; subOsc.type = 'sine';
} else if (preset === 'noise') {
mainOsc.type = 'square'; subOsc.type = 'sawtooth';
}
mainOsc.connect(filterNode);
subOsc.connect(filterNode);
mainOsc.start();
subOsc.start();
}
function stopSynth() {
if (mainOsc) { mainOsc.stop(); mainOsc.disconnect(); mainOsc = null; }
if (subOsc) { subOsc.stop(); subOsc.disconnect(); subOsc = null; }
}
function updateSynth(x, y) {
if (!mainOsc) return;
const scaleType = document.getElementById('scale').value;
let freq;
if (scaleType === 'raw') {
freq = 80 * Math.pow(2, x * 4);
} else {
const currentScale = scales[scaleType];
const noteIndex = Math.floor(x * currentScale.length);
const note = currentScale[Math.min(noteIndex, currentScale.length - 1)];
freq = mtof(note);
}
const minVal = 100, maxVal = 10000;
const cutoff = minVal * Math.pow(maxVal / minVal, y);
// Immediate value scheduling for zero latency
const now = audioCtx.currentTime;
mainOsc.frequency.setValueAtTime(freq, now);
subOsc.frequency.setValueAtTime(freq / 2, now);
filterNode.frequency.setValueAtTime(cutoff, now);
if (document.getElementById('preset').value === 'acid') {
filterNode.Q.setValueAtTime(4 + (y * 18), now);
} else {
filterNode.Q.setValueAtTime(4, now);
}
}
// High-Precision Drum Synthesizer
function triggerDrum(type, time) {
if (type === 'kick') {
let osc = audioCtx.createOscillator();
let gain = audioCtx.createGain();
osc.frequency.setValueAtTime(180, time);
osc.frequency.exponentialRampToValueAtTime(30, time + 0.1);
gain.gain.setValueAtTime(1.4, time);
gain.gain.exponentialRampToValueAtTime(0.001, time + 0.14);
osc.connect(driveNode);
osc.start(time); osc.stop(time + 0.14);
} else if (type === 'snare') {
let noiseSrc = audioCtx.createBufferSource();
noiseSrc.buffer = noiseBuffer;
let filter = audioCtx.createBiquadFilter();
filter.type = 'highpass';
filter.frequency.value = 800;
let gain = audioCtx.createGain();
gain.gain.setValueAtTime(0.9, time);
gain.gain.exponentialRampToValueAtTime(0.01, time + 0.09);
noiseSrc.connect(filter);
filter.connect(driveNode);
noiseSrc.start(time); noiseSrc.stop(time + 0.09);
} else if (type === 'hat') {
let noiseSrc = audioCtx.createBufferSource();
noiseSrc.buffer = noiseBuffer;
let filter = audioCtx.createBiquadFilter();
filter.type = 'highpass';
filter.frequency.value = 6000;
let gain = audioCtx.createGain();
gain.gain.setValueAtTime(0.4, time);
gain.gain.exponentialRampToValueAtTime(0.001, time + 0.04);
noiseSrc.connect(filter);
filter.connect(driveNode);
noiseSrc.start(time); noiseSrc.stop(time + 0.04);
}
}
// Web Audio Lookahead Scheduler
function nextNote() {
const bpm = parseInt(document.getElementById('bpm').value);
const secondsPerBeat = 60.0 / bpm;
nextNoteTime += 0.25 * secondsPerBeat; // 16th note step
current16thNote = (current16thNote + 1) % 16;
}
function scheduleNote(step, time) {
const patKey = document.getElementById('beat-pattern').value;
const pat = drumPatterns[patKey];
if (pat.kick[step]) triggerDrum('kick', time);
if (pat.snare[step]) triggerDrum('snare', time);
if (pat.hat[step]) triggerDrum('hat', time);
}
function scheduler() {
while (nextNoteTime < audioCtx.currentTime + scheduleAheadTime) {
scheduleNote(current16thNote, nextNoteTime);
nextNote();
}
if (beatPlaying) {
timerID = window.setTimeout(scheduler, lookahead);
}
}
document.getElementById('beat-btn').addEventListener('click', (e) => {
initAudio();
if (audioCtx.state === 'suspended') audioCtx.resume();
beatPlaying = !beatPlaying;
e.target.classList.toggle('active', beatPlaying);
if (beatPlaying) {
current16thNote = 0;
nextNoteTime = audioCtx.currentTime;
scheduler();
} else {
clearTimeout(timerID);
}
});
// Touch / Mouse Interaction with RAF Decoupling for smooth performance
const pad = document.getElementById('pad');
const canvas = document.getElementById('visualizer');
const ctx = canvas.getContext('2d');
let touchX = 0, touchY = 0;
function resizeCanvas() {
canvas.width = pad.clientWidth; canvas.height = pad.clientHeight;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
function handleInputStart(e) {
isTouching = true;
startSynth();
handleInputMove(e);
}
function handleInputEnd() {
isTouching = false;
stopSynth();
}
function handleInputMove(e) {
if (!isTouching) return;
const rect = pad.getBoundingClientRect();
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
const clientY = e.touches ? e.touches[0].clientY : e.clientY;
touchX = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
touchY = Math.max(0, Math.min(1, 1 - (clientY - rect.top) / rect.height));
updateSynth(touchX, touchY);
}
pad.addEventListener('mousedown', handleInputStart);
window.addEventListener('mouseup', handleInputEnd);
pad.addEventListener('mousemove', handleInputMove);
pad.addEventListener('touchstart', handleInputStart, { passive: true });
window.addEventListener('touchend', handleInputEnd);
pad.addEventListener('touchmove', handleInputMove, { passive: true });
// Canvas Visualization Loop
function renderVisualizer() {
requestAnimationFrame(renderVisualizer);
ctx.fillStyle = 'rgba(5, 5, 5, 0.25)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Grid
ctx.strokeStyle = '#1a1a1a';
ctx.lineWidth = 1;
for (let i = 0; i < canvas.width; i += 30) {
ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i, canvas.height); ctx.stroke();
}
for (let j = 0; j < canvas.height; j += 30) {
ctx.beginPath(); ctx.moveTo(0, j); ctx.lineTo(canvas.width, j); ctx.stroke();
}
// Touch Cursor
if (isTouching) {
const cx = touchX * canvas.width;
const cy = (1 - touchY) * canvas.height;
ctx.strokeStyle = 'rgba(255, 204, 0, 0.6)';
ctx.beginPath(); ctx.moveTo(cx, 0); ctx.lineTo(cx, canvas.height); ctx.stroke();
ctx.beginPath(); ctx.moveTo(0, cy); ctx.lineTo(canvas.width, cy); ctx.stroke();
ctx.fillStyle = '#ff3333';
ctx.shadowColor = '#ff3333';
ctx.shadowBlur = 15;
ctx.beginPath();
ctx.arc(cx, cy, 10, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
}
}
renderVisualizer();
// UI Display Listeners
document.getElementById('scale').addEventListener('change', (e) => {
document.getElementById('disp-scale').innerText = `SKALA: ${e.target.options[e.target.selectedIndex].text.toUpperCase()}`;
});
document.getElementById('preset').addEventListener('change', (e) => {
document.getElementById('disp-preset').innerText = `SYNTH: ${e.target.options[e.target.selectedIndex].text.toUpperCase()}`;
if (mainOsc) { stopSynth(); startSynth(); }
});
document.getElementById('beat-pattern').addEventListener('change', (e) => {
document.getElementById('disp-pattern').innerText = `BEAT: ${e.target.options[e.target.selectedIndex].text.toUpperCase()}`;
});
document.getElementById('bpm').addEventListener('change', (e) => {
document.getElementById('disp-bpm').innerText = `${e.target.value} BPM`;
});
</script>
</body>
</html>