Files
Adminator 1e51ef7def Nachtrag: alle bisher untracked Ordner + hängende Änderungen mit-committen
- Konzept/, didaktik_geografie/, didaktik_simulation/, v2-modules/, v2-platform/
- 12 code-workspace-Files
- STATUS-*.md
- viele M/D/R-Änderungen an bereits getrackten Files
- .gitignore verstärkt: **/.humaninput/, **/secret_keys.txt

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-08 02:27:02 +02:00

196 lines
9.0 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Funk-Effekt Test</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Inter',system-ui,sans-serif;background:#f5efe0;padding:2rem;max-width:700px;margin:auto}
h1{font-size:1.2rem;color:#2d5434;margin-bottom:1rem}
.row{display:flex;gap:.5rem;align-items:center;margin-bottom:.6rem;padding:.5rem;background:#fff;border-radius:8px}
.row button{padding:.4rem .8rem;background:#2d5434;color:#fff;border:none;border-radius:6px;font-size:.75rem;font-weight:700;cursor:pointer}
.row span{font-size:.75rem;color:#3d5165;flex:1}
.row .tag{font-size:.6rem;background:#dae8ec;padding:2px 6px;border-radius:4px;color:#4a7c8a;font-weight:600}
</style>
</head>
<body>
<h1>🎙️ Funk-Effekt Vergleich</h1>
<p style="font-size:.8rem;color:#6b7d8f;margin-bottom:.5rem">Gleicher TTS-Clip mit verschiedenen Filter-Ketten.</p>
<button onclick="initAudio()" style="padding:.5rem 1.2rem;background:#2d5434;color:#fff;border:none;border-radius:8px;font-weight:700;cursor:pointer;margin-bottom:.5rem">🔊 Audio aktivieren</button>
<div id="status" style="font-size:.75rem;color:#8a8a8a;margin-bottom:1rem">Klicke "Audio aktivieren" um zu starten.</div>
<div id="tests"></div>
<script>
var audioCtx = null;
var testBuffer = null;
function initAudio() {
if (audioCtx) return;
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
if (audioCtx.state === 'suspended') audioCtx.resume();
var statusEl = document.getElementById('status');
statusEl.textContent = 'Lade Audio...';
var basePath = location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1);
var audioUrl = basePath + 'sounds/radio/tower1.mp3';
statusEl.textContent = 'Lade: ' + audioUrl;
fetch(audioUrl)
.then(function(r) {
statusEl.textContent = 'Dekodiere... (Status: ' + r.status + ')';
return r.arrayBuffer();
})
.then(function(b) { return audioCtx.decodeAudioData(b); })
.then(function(buf) {
testBuffer = buf;
statusEl.textContent = 'Bereit! (' + buf.duration.toFixed(1) + 's, ' + buf.sampleRate + ' Hz)';
statusEl.style.color = '#5a8a5e';
// Auch alle Stimmen laden
var loaded = 0;
['alloy','echo','fable','nova','onyx','shimmer'].forEach(function(v){
var vUrl = basePath + 'sounds/radio/voice_'+v+'.mp3';
fetch(vUrl)
.then(function(r){ if(!r.ok) throw new Error(v+': HTTP '+r.status); return r.arrayBuffer(); })
.then(function(b){ return audioCtx.decodeAudioData(b); })
.then(function(buf){ voiceBuffers[v]=buf; loaded++; statusEl.textContent='Bereit! Stimmen: '+loaded+'/6'; })
.catch(function(e){ console.error('Voice load fail:', v, e); statusEl.textContent += ' ('+v+' fehlt)'; });
});
})
.catch(function(e) {
statusEl.textContent = 'FEHLER: ' + e.message;
statusEl.style.color = '#c85c4a';
});
}
// Lade verschiedene Stimmen zum Vergleich
var voiceBuffers = {};
function loadVoice(name, url) {
fetch(basePath + url).then(function(r){return r.arrayBuffer()}).then(function(b){return audioCtx.decodeAudioData(b)}).then(function(buf){voiceBuffers[name]=buf;});
}
var EFFECTS = [
{name:'Original (kein Effekt)', tag:'clean', build: function(ctx){
var g = ctx.createGain(); g.gain.value = 1.0;
return {input:g, output:g};
}},
{name:'Nur Highpass 800 Hz', tag:'HP800', build: function(ctx){
var hp = ctx.createBiquadFilter(); hp.type='highpass'; hp.frequency.value=800;
return {input:hp, output:hp};
}},
{name:'Bandpass 2500 Hz (Q=1)', tag:'BP2500', build: function(ctx){
var bp = ctx.createBiquadFilter(); bp.type='bandpass'; bp.frequency.value=2500; bp.Q.value=1;
return {input:bp, output:bp};
}},
{name:'HP 800 + Peak 2500 + LP 4000', tag:'HP+Peak+LP', build: function(ctx){
var hp = ctx.createBiquadFilter(); hp.type='highpass'; hp.frequency.value=800;
var pk = ctx.createBiquadFilter(); pk.type='peaking'; pk.frequency.value=2500; pk.gain.value=8; pk.Q.value=1.5;
var lp = ctx.createBiquadFilter(); lp.type='lowpass'; lp.frequency.value=4000;
hp.connect(pk); pk.connect(lp);
return {input:hp, output:lp};
}},
{name:'HP 800 + Peak 2500 + LP 4000 + Soft Distortion', tag:'Funk v1', build: function(ctx){
var hp = ctx.createBiquadFilter(); hp.type='highpass'; hp.frequency.value=800;
var pk = ctx.createBiquadFilter(); pk.type='peaking'; pk.frequency.value=2500; pk.gain.value=8; pk.Q.value=1.5;
var lp = ctx.createBiquadFilter(); lp.type='lowpass'; lp.frequency.value=4000;
var dist = ctx.createWaveShaper();
var c = new Float32Array(256);
for(var i=0;i<256;i++){var x=(i/128)-1;c[i]=Math.tanh(x*1.5);}
dist.curve = c;
hp.connect(pk); pk.connect(lp); lp.connect(dist);
return {input:hp, output:dist};
}},
{name:'HP 600 + Peak 3000 + LP 3500 + Hard Clip', tag:'Funk v2', build: function(ctx){
var hp = ctx.createBiquadFilter(); hp.type='highpass'; hp.frequency.value=600;
var pk = ctx.createBiquadFilter(); pk.type='peaking'; pk.frequency.value=3000; pk.gain.value=12; pk.Q.value=2;
var lp = ctx.createBiquadFilter(); lp.type='lowpass'; lp.frequency.value=3500;
var dist = ctx.createWaveShaper();
var c = new Float32Array(256);
for(var i=0;i<256;i++){var x=(i/128)-1;c[i]=x>0.6?0.6:x<-0.6?-0.6:x;}
dist.curve = c;
hp.connect(pk); pk.connect(lp); lp.connect(dist);
return {input:hp, output:dist};
}},
{name:'Walkie-Talkie (BP 1800, Q=3, Dist)', tag:'Walkie', build: function(ctx){
var bp = ctx.createBiquadFilter(); bp.type='bandpass'; bp.frequency.value=1800; bp.Q.value=3;
var dist = ctx.createWaveShaper();
var c = new Float32Array(256);
for(var i=0;i<256;i++){var x=(i/128)-1;c[i]=Math.sign(x)*Math.pow(Math.abs(x),0.6);}
dist.curve = c;
bp.connect(dist);
return {input:bp, output:dist};
}},
{name:'AM Radio (HP 400, BP 1500 Q=2, LP 3000)', tag:'AM Radio', build: function(ctx){
var hp = ctx.createBiquadFilter(); hp.type='highpass'; hp.frequency.value=400;
var bp = ctx.createBiquadFilter(); bp.type='bandpass'; bp.frequency.value=1500; bp.Q.value=2;
var lp = ctx.createBiquadFilter(); lp.type='lowpass'; lp.frequency.value=3000;
hp.connect(bp); bp.connect(lp);
return {input:hp, output:lp};
}},
];
var html = '<h2 style="font-size:.9rem;color:#2d5434;margin:1rem 0 .5rem">Filter-Effekte (Tower-Stimme)</h2>';
EFFECTS.forEach(function(e, i){
html += '<div class="row"><button onclick="playEffect('+i+')">▶</button><span>'+e.name+'</span><span class="tag">'+e.tag+'</span></div>';
});
// Stimmenvergleich
var VOICES = ['alloy','echo','fable','nova','onyx','shimmer'];
var VOICE_DESC = {
alloy:'Neutral, warm', echo:'Männlich, tief', fable:'Britisch, weich',
nova:'Weiblich, klar', onyx:'Männlich, voll (aktuell)', shimmer:'Weiblich, hell'
};
html += '<h2 style="font-size:.9rem;color:#2d5434;margin:1.5rem 0 .5rem">Stimmen-Vergleich (mit Walkie-Effekt)</h2>';
VOICES.forEach(function(v){
html += '<div class="row"><button onclick="playVoice(\''+v+'\')">▶</button><span><strong>'+v+'</strong> — '+VOICE_DESC[v]+'</span><span class="tag">Stimme</span></div>';
});
html += '<h2 style="font-size:.9rem;color:#2d5434;margin:1.5rem 0 .5rem">Stimmen ohne Effekt (zum Vergleich)</h2>';
VOICES.forEach(function(v){
html += '<div class="row"><button onclick="playVoiceClean(\''+v+'\')">▶</button><span><strong>'+v+'</strong> — '+VOICE_DESC[v]+' (clean)</span><span class="tag">Clean</span></div>';
});
document.getElementById('tests').innerHTML = html;
function playVoice(voice) {
if (!audioCtx || !voiceBuffers[voice]) { alert('Lade zuerst Audio.'); return; }
if (audioCtx.state === 'suspended') audioCtx.resume();
var src = audioCtx.createBufferSource();
src.buffer = voiceBuffers[voice];
// Walkie-Talkie Effekt
var bp = audioCtx.createBiquadFilter(); bp.type='bandpass'; bp.frequency.value=1800; bp.Q.value=3;
var dist = audioCtx.createWaveShaper();
var c = new Float32Array(256);
for(var i=0;i<256;i++){var x=(i/128)-1;c[i]=Math.sign(x)*Math.pow(Math.abs(x),0.6);}
dist.curve = c;
var gain = audioCtx.createGain(); gain.gain.value = 3.0;
src.connect(bp); bp.connect(dist); dist.connect(gain); gain.connect(audioCtx.destination);
src.start(0);
}
function playVoiceClean(voice) {
if (!audioCtx || !voiceBuffers[voice]) { alert('Lade zuerst Audio.'); return; }
if (audioCtx.state === 'suspended') audioCtx.resume();
var src = audioCtx.createBufferSource();
src.buffer = voiceBuffers[voice];
var gain = audioCtx.createGain(); gain.gain.value = 1.5;
src.connect(gain); gain.connect(audioCtx.destination);
src.start(0);
}
function playEffect(idx) {
if (!audioCtx) initAudio();
if (!testBuffer) { alert('Audio noch nicht geladen. Klicke zuerst "Audio aktivieren".'); return; }
if (audioCtx.state === 'suspended') audioCtx.resume();
var src = audioCtx.createBufferSource();
src.buffer = testBuffer;
var chain = EFFECTS[idx].build(audioCtx);
var gain = audioCtx.createGain(); gain.gain.value = 2.5;
src.connect(chain.input);
chain.output.connect(gain);
gain.connect(audioCtx.destination);
src.start(0);
}
</script>
</body>
</html>