<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Misa Kitara Mobile - Digital Synth</title>
<style>
:root {
--bg-color: #08080c;
--panel-bg: #12121c;
--accent-cyan: #00f0ff;
--accent-magenta: #ff0055;
--accent-gold: #ffcc00;
--grid-line: #222233;
}
* {
box-sizing: border-box;
user-select: none;
-webkit-user-select: none;
touch-action: none;
}
body {
background-color: var(--bg-color);
color: #fff;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace;
margin: 0;
padding: 8px;
height: 100vh;
height: 100dvh;
display: flex;
flex-direction: column;
gap: 8px;
overflow: hidden;
}
/* Top Header & Display */
.header {
background: var(--panel-bg);
border: 1px solid #333;
border-radius: 8px;
padding: 6px 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
.title {
font-size: 0.9rem;
font-weight: 900;
color: var(--accent-cyan);
letter-spacing: 1px;
}
.status {
font-size: 0.7rem;
color: var(--accent-gold);
font-family: monospace;
}
/* Fretboard Matrix Area */
.fretboard-container {
background: var(--panel-bg);
border: 1px solid #333;
border-radius: 8px;
padding: 6px;
display: flex;
flex-direction: column;
gap: 4px;
flex-shrink: 0;
}
.string-row {
display: flex;
align-items: center;
gap: 4px;
height: 28px;
}
.string-label {
width: 24px;
font-size: 0.65rem;
font-weight: bold;
color: var(--accent-cyan);
text-align: center;
}
.fret-buttons {
display: flex;
flex: 1;
gap: 3px;
height: 100%;
}
.fret-btn {
flex: 1;
background: #1a1a26;
border: 1px solid #333;
color: #888;
font-size: 0.65rem;
font-weight: bold;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.fret-btn.active {
background: var(--accent-magenta);
color: #fff;
border-color: #ff6699;
box-shadow: 0 0 8px rgba(255, 0, 85, 0.6);
}
/* Kitara XY Performance Touchpad */
.pad-container {
flex: 1;
position: relative;
background: #040406;
border: 2px solid var(--accent-cyan);
border-radius: 10px;
overflow: hidden;
box-shadow: inset 0 0 20px rgba(0, 240, 255, 0.15);
}
canvas {
width: 100%;
height: 100%;
display: block;
}
/* FX Preset Control Bar */
.controls-bar {
display: flex;
gap: 6px;
height: 36px;
}
select, button {
background: #1a1a26;
color: #fff;
border: 1px solid #444;
border-radius: 6px;
padding: 4px 8px;
font-size: 0.75rem;
font-weight: bold;
flex: 1;
}
button.active {
background: var(--accent-cyan);
color: #000;
}
</style>
</head>
<body>
<div class="header">
<div class="title">KITARA-MOBILE</div>
<div class="status" id="status-display">TOUCH PAD TO PLAY</div>
</div>
<div class="fretboard-container" id="fretboard"></div>
<div class="pad-container" id="pad-box">
<canvas id="pad-canvas"></canvas>
</div>
<div class="controls-bar">
<select id="synth-sound">
<option value="lead">Kitara Cyber Lead</option>
<option value="acid">303 Distorted</option>
<option value="poly">Digital PolySaw</option>
<option value="sub">Sub Bass Heavy</option>
</select>
<button id="delay-btn">DELAY: ON</button>
</div>
<script>
// Base Frequencies for 6 Standard Guitar Strings (E2, A2, D3, G3, B3, E4)
const baseNotes = [40, 45, 50, 55, 59, 64];
const availableFrets = [0, 1, 3, 5, 7, 8, 10, 12];
const stringStates = [0, 0, 0, 0, 0, 0]; // Selected fret per string
let audioCtx = null;
let masterGain, filterNode, driveNode, delayNode, delayFeedback;
let delayActive = true;
let activeVoices = {}; // Tracks active touch sound voices
function midiToFreq(midi) {
return 440 * Math.pow(2, (midi - 69) / 12);
}
// Audio Engine Setup
function initAudio() {
if (audioCtx) return;
audioCtx = new (window.AudioContext || window.webkitAudioContext)({ latencyHint: 'interactive' });
masterGain = audioCtx.createGain();
masterGain.gain.value = 0.4;
filterNode = audioCtx.createBiquadFilter();
filterNode.type = 'lowpass';
filterNode.frequency.value = 2500;
filterNode.Q.value = 4;
driveNode = audioCtx.createWaveShaper();
driveNode.curve = makeDistortionCurve(60);
// Delay FX
delayNode = audioCtx.createDelay();
delayNode.delayTime.value = 0.25; // 1/8 note vibe
delayFeedback = audioCtx.createGain();
delayFeedback.gain.value = 0.4;
delayNode.connect(delayFeedback);
delayFeedback.connect(delayNode);
// Routing
filterNode.connect(driveNode);
driveNode.connect(masterGain);
driveNode.connect(delayNode);
delayNode.connect(masterGain);
masterGain.connect(audioCtx.destination);
}
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;
}
// Play Kitara Voice on String Trigger
function startVoice(touchId, stringIndex, xRatio, yRatio) {
initAudio();
if (audioCtx.state === 'suspended') audioCtx.resume();
const midiNote = baseNotes[stringIndex] + stringStates[stringIndex];
const freq = midiToFreq(midiNote);
const preset = document.getElementById('synth-sound').value;
const osc1 = audioCtx.createOscillator();
const osc2 = audioCtx.createOscillator();
const voiceGain = audioCtx.createGain();
if (preset === 'acid') {
osc1.type = 'sawtooth'; osc2.type = 'sawtooth';
osc2.detune.value = 12;
} else if (preset === 'poly') {
osc1.type = 'sawtooth'; osc2.type = 'square';
osc2.detune.value = -7;
} else if (preset === 'sub') {
osc1.type = 'triangle'; osc2.type = 'sine';
} else { // Kitara Lead
osc1.type = 'sawtooth'; osc2.type = 'square';
osc2.detune.value = 5;
}
osc1.frequency.setValueAtTime(freq, audioCtx.currentTime);
osc2.frequency.setValueAtTime(freq, audioCtx.currentTime);
osc1.connect(voiceGain);
osc2.connect(voiceGain);
voiceGain.connect(filterNode);
// Fast Attack Envelope
voiceGain.gain.setValueAtTime(0.01, audioCtx.currentTime);
voiceGain.gain.linearRampToValueAtTime(0.8, audioCtx.currentTime + 0.01);
osc1.start(); osc2.start();
activeVoices[touchId] = { osc1, osc2, voiceGain, stringIndex };
updateSynthParameters(xRatio, yRatio);
}
function updateVoice(touchId, xRatio, yRatio, stringIndex) {
if (!activeVoices[touchId]) return;
// Pitch shift if finger slides to another string zone
if (activeVoices[touchId].stringIndex !== stringIndex) {
const midiNote = baseNotes[stringIndex] + stringStates[stringIndex];
const freq = midiToFreq(midiNote);
const now = audioCtx.currentTime;
activeVoices[touchId].osc1.frequency.setTargetAtTime(freq, now, 0.01);
activeVoices[touchId].osc2.frequency.setTargetAtTime(freq, now, 0.01);
activeVoices[touchId].stringIndex = stringIndex;
}
updateSynthParameters(xRatio, yRatio);
}
function stopVoice(touchId) {
if (!activeVoices[touchId]) return;
const { osc1, osc2, voiceGain } = activeVoices[touchId];
const now = audioCtx.currentTime;
voiceGain.gain.cancelScheduledValues(now);
voiceGain.gain.setTargetAtTime(0.001, now, 0.05);
setTimeout(() => {
try { osc1.stop(); osc2.stop(); osc1.disconnect(); osc2.disconnect(); } catch(e){}
}, 100);
delete activeVoices[touchId];
}
function updateSynthParameters(xRatio, yRatio) {
if (!audioCtx) return;
const now = audioCtx.currentTime;
// X-Axis controls Filter Cutoff (100 Hz - 8000 Hz)
const cutoff = 100 * Math.pow(80, xRatio);
filterNode.frequency.setTargetAtTime(cutoff, now, 0.01);
// Y-Axis controls Resonance & Distortion Gain
filterNode.Q.setTargetAtTime(2 + (yRatio * 16), now, 0.01);
document.getElementById('status-display').innerText =
`CUTOFF: ${Math.round(cutoff)}Hz | RES: ${Math.round(2 + yRatio * 16)}`;
}
// Render Fretboard UI
const fretboardEl = document.getElementById('fretboard');
const stringNames = ['E2', 'A2', 'D3', 'G3', 'B3', 'E4'];
stringNames.forEach((name, sIdx) => {
const row = document.createElement('div');
row.className = 'string-row';
const label = document.createElement('div');
label.className = 'string-label';
label.innerText = name[0] + name[1];
row.appendChild(label);
const fretContainer = document.createElement('div');
fretContainer.className = 'fret-buttons';
availableFrets.forEach((fret) => {
const btn = document.createElement('div');
btn.className = `fret-btn ${fret === 0 ? 'active' : ''}`;
btn.innerText = fret === 0 ? 'O' : fret;
btn.dataset.string = sIdx;
btn.dataset.fret = fret;
btn.addEventListener('pointerdown', (e) => {
e.preventDefault();
stringStates[sIdx] = fret;
row.querySelectorAll('.fret-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
});
fretContainer.appendChild(btn);
});
row.appendChild(fretContainer);
fretboardEl.appendChild(row);
});
// Touchpad & Canvas Performance Rendering
const padBox = document.getElementById('pad-box');
const canvas = document.getElementById('pad-canvas');
const ctx = canvas.getContext('2d');
let touches = [];
function resizeCanvas() {
canvas.width = padBox.clientWidth;
canvas.height = padBox.clientHeight;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
function getPadCoordinates(e) {
const rect = padBox.getBoundingClientRect();
const touchList = e.touches ? Array.from(e.touches) : [e];
return touchList.map(t => {
const x = Math.max(0, Math.min(rect.width, t.clientX - rect.left));
const y = Math.max(0, Math.min(rect.height, t.clientY - rect.top));
const xRatio = x / rect.width;
const yRatio = 1 - (y / rect.height);
// 6 Virtual String Zones on Y-Axis
const stringIndex = Math.min(5, Math.floor((y / rect.height) * 6));
return { id: t.identifier ?? 'mouse', x, y, xRatio, yRatio, stringIndex };
});
}
function handleTouchStart(e) {
e.preventDefault();
const currentTouches = getPadCoordinates(e);
currentTouches.forEach(t => {
startVoice(t.id, t.stringIndex, t.xRatio, t.yRatio);
});
touches = currentTouches;
}
function handleTouchMove(e) {
e.preventDefault();
const currentTouches = getPadCoordinates(e);
currentTouches.forEach(t => {
updateVoice(t.id, t.xRatio, t.yRatio, t.stringIndex);
});
touches = currentTouches;
}
function handleTouchEnd(e) {
e.preventDefault();
const remainingTouches = getPadCoordinates(e);
const remainingIds = new Set(remainingTouches.map(t => t.id));
Object.keys(activeVoices).forEach(id => {
if (!remainingIds.has(id === 'mouse' ? 'mouse' : Number(id))) {
stopVoice(id);
}
});
touches = remainingTouches;
}
padBox.addEventListener('touchstart', handleTouchStart, { passive: false });
padBox.addEventListener('touchmove', handleTouchMove, { passive: false });
padBox.addEventListener('touchend', handleTouchEnd, { passive: false });
let isMouseDown = false;
padBox.addEventListener('mousedown', (e) => { isMouseDown = true; handleTouchStart(e); });
window.addEventListener('mousemove', (e) => { if (isMouseDown) handleTouchMove(e); });
window.addEventListener('mouseup', (e) => { if (isMouseDown) { isMouseDown = false; handleTouchEnd(e); } });
// Visualizer Loop
function drawPad() {
requestAnimationFrame(drawPad);
ctx.fillStyle = '#040406';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw 6 Virtual String Bands (Kitara Style)
const bandHeight = canvas.height / 6;
ctx.lineWidth = 1;
for (let i = 0; i < 6; i++) {
ctx.strokeStyle = '#181828';
ctx.beginPath();
ctx.moveTo(0, i * bandHeight);
ctx.lineTo(canvas.width, i * bandHeight);
ctx.stroke();
// Highlight Active Fretted String Lines
ctx.strokeStyle = 'rgba(0, 240, 255, 0.15)';
ctx.beginPath();
ctx.moveTo(0, i * bandHeight + bandHeight / 2);
ctx.lineTo(canvas.width, i * bandHeight + bandHeight / 2);
ctx.stroke();
}
// Draw Touch Pointers
touches.forEach(t => {
// Horizontal / Vertical Crosshair
ctx.strokeStyle = 'rgba(255, 0, 85, 0.4)';
ctx.beginPath(); ctx.moveTo(t.x, 0); ctx.lineTo(t.x, canvas.height); ctx.stroke();
ctx.beginPath(); ctx.moveTo(0, t.y); ctx.lineTo(canvas.width, t.y); ctx.stroke();
// Glowing Touch Point
ctx.fillStyle = '#00f0ff';
ctx.shadowColor = '#00f0ff';
ctx.shadowBlur = 12;
ctx.beginPath();
ctx.arc(t.x, t.y, 16, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
});
}
drawPad();
// UI Listeners
document.getElementById('delay-btn').addEventListener('click', (e) => {
delayActive = !delayActive;
delayFeedback.gain.value = delayActive ? 0.4 : 0;
e.target.innerText = `DELAY: ${delayActive ? 'ON' : 'OFF'}`;
e.target.classList.toggle('active', delayActive);
});
</script>
</body>
</html>