feat: Stereo L/R split, 3-band routing, BPM sync, analog VU-meter, fluid blob, VHS scanlines and phosphor afterglow

This commit is contained in:
Jannik committed 2026-09-07 17:52:25 +02:00
1 parent 9658cddef2
commit 7bfef73715
6 files changed
+1128 -54

No files matched your search

+769 -53
View File
File diff suppressed because it is too large. Load diff
+163
View File
@@ -280,6 +280,20 @@
<span class="shape-label">3D Tunnel</span>
</div>
</label>
<label class="shape-card">
<input type="radio" name="shapeType" value="blob">
<div class="shape-card-inner">
<span class="shape-icon">🧪</span>
<span class="shape-label">Fluid Blob</span>
</div>
</label>
<label class="shape-card">
<input type="radio" name="shapeType" value="vumeter">
<div class="shape-card-inner">
<span class="shape-icon">📻</span>
<span class="shape-label">VU-Meter</span>
</div>
</label>
</div>
</div>
@@ -493,6 +507,90 @@
<input type="checkbox" id="mirrorSpectrum" checked>
</div>
</div>
<!-- STEREO L/R DUAL-CHANNEL -->
<div class="panel-section">
<div class="section-header-with-toggle">
<h3 class="section-title">Stereo L/R Dual-Channel</h3>
<label class="switch-toggle" title="Echten Stereo-Kanal Split aktivieren">
<input type="checkbox" id="stereoSplit">
<span class="switch-slider"></span>
</label>
</div>
<div id="stereoOptionsContainer" class="hidden">
<div class="control-row">
<label for="stereoMode">Kanal-Aufteilung</label>
<select id="stereoMode" class="select-input">
<option value="split_hemisphere" selected>Hemisphären-Split (L Links / R Rechts)</option>
<option value="interleaved">Verschachtelt (L & R alternierend)</option>
<option value="dual_ring">Dual Ring (L Innen / R Außen)</option>
</select>
</div>
</div>
</div>
<!-- 3-BAND ELEMENT-ROUTING -->
<div class="panel-section">
<h3 class="section-title">3-Band Element-Routing</h3>
<div class="control-row">
<label for="routePulse">Center-Pulse Quelle</label>
<select id="routePulse" class="select-input">
<option value="sub_bass" selected>Sub-Bass (Kick / 808)</option>
<option value="mids">Mitten (Vocals / Lead)</option>
<option value="treble">Höhen (Hi-Hats / Cymbals)</option>
<option value="full_mix">Gesamter Audio-Mix</option>
</select>
</div>
<div class="control-row">
<label for="routeParticles">Partikel-Explosion</label>
<select id="routeParticles" class="select-input">
<option value="treble" selected>Höhen (Hi-Hats / Snares)</option>
<option value="sub_bass">Sub-Bass (Kick-Drops)</option>
<option value="mids">Mitten (Gesang / Lead)</option>
<option value="full_mix">Gesamter Audio-Mix</option>
</select>
</div>
<div class="control-row">
<label for="routeShake">Bass-Shake Quelle</label>
<select id="routeShake" class="select-input">
<option value="sub_bass" selected>Sub-Bass (Tiefe Kicks)</option>
<option value="mids">Mitten (Synth-Drops)</option>
<option value="full_mix">Gesamter Audio-Mix</option>
</select>
</div>
</div>
<!-- BPM & BEAT-SYNCHRONISATION -->
<div class="panel-section">
<div class="section-header-with-toggle">
<h3 class="section-title">BPM & Beat-Synchronisation</h3>
<label class="switch-toggle" title="Vinyl-Drehung, Grid und Puls an Song-BPM koppeln">
<input type="checkbox" id="bpmSync">
<span class="switch-slider"></span>
</label>
</div>
<div id="bpmOptionsContainer" class="hidden">
<div class="control-row">
<label for="bpmValue">Song-Tempo (BPM)</label>
<div class="bpm-input-wrapper">
<input type="number" id="bpmValue" min="40" max="260" value="128" step="1" class="bpm-num-input">
<span class="bpm-badge" id="bpmDetectedBadge">Auto-Detect</span>
<button type="button" id="btnTapTempo" class="btn btn-secondary btn-sm" title="Im Takt mehrfach klicken">Tap</button>
</div>
</div>
<div class="toggle-row">
<label for="bpmSyncRotation">Vinyl-Drehung im Beat synchronisieren</label>
<input type="checkbox" id="bpmSyncRotation" checked>
</div>
<div class="toggle-row">
<label for="bpmSyncGrid">3D Grid-Flug synchronisieren</label>
<input type="checkbox" id="bpmSyncGrid" checked>
</div>
</div>
</div>
</div>
<!-- TAB 4: FX & 3D -->
@@ -614,6 +712,71 @@
<input type="checkbox" id="enableBassZoom" checked>
</div>
</div>
<!-- RETRO VHS & SCANLINES -->
<div class="panel-section">
<div class="section-header-with-toggle">
<h3 class="section-title">Retro VHS & Scanlines</h3>
<label class="switch-toggle" title="Vintage VHS/CRT-Effekt aktivieren">
<input type="checkbox" id="enableVhs">
<span class="switch-slider"></span>
</label>
</div>
<div id="vhsOptionsContainer" class="hidden">
<div class="control-row">
<label for="vhsScanlineIntensity">Scanline-Dichte / Deckkraft</label>
<div class="range-group">
<input type="range" id="vhsScanlineIntensity" min="0.05" max="0.6" value="0.25" step="0.05">
<span class="range-value" id="valVhsScanlineIntensity">25%</span>
</div>
</div>
<div class="control-row">
<label for="vhsNoiseIntensity">VHS Bandrauschen & Jitter</label>
<div class="range-group">
<input type="range" id="vhsNoiseIntensity" min="0" max="0.5" value="0.15" step="0.05">
<span class="range-value" id="valVhsNoiseIntensity">15%</span>
</div>
</div>
<div class="toggle-row">
<label for="vhsCrtCurvature">CRT Bildschirm-Vignette & Krümmung</label>
<input type="checkbox" id="vhsCrtCurvature" checked>
</div>
</div>
</div>
<!-- PHOSPHOR AFTERGLOW / TRAILS -->
<div class="panel-section">
<div class="section-header-with-toggle">
<h3 class="section-title">Phosphor Afterglow (Schweif)</h3>
<label class="switch-toggle" title="Nachleucht-Schweif wie bei Analog-Oszilloskopen">
<input type="checkbox" id="enableAfterglow">
<span class="switch-slider"></span>
</label>
</div>
<div id="afterglowOptionsContainer" class="hidden">
<div class="control-row">
<label for="afterglowDecay">Schweif-Länge (Decay)</label>
<div class="range-group">
<input type="range" id="afterglowDecay" min="0.5" max="0.96" value="0.82" step="0.02">
<span class="range-value" id="valAfterglowDecay">82%</span>
</div>
</div>
<div class="control-row">
<label for="afterglowMode">Phosphor-Farbtönung</label>
<select id="afterglowMode" class="select-input">
<option value="original" selected>Original Visualizer-Farbe</option>
<option value="green_p1">Classic Radar (P1 Phosphor Grün)</option>
<option value="amber_p4">Vintage Amber (P4 Warm-Gold)</option>
<option value="cyan_neon">Cyberpunk Cyan</option>
</select>
</div>
</div>
</div>
</div>
<!-- TAB 5: COLORS & NEON GLOW -->
+81
View File
@@ -0,0 +1,81 @@
{
"name": "Fluid Organic Blob",
"icon": "🫧",
"shapeType": "blob",
"showCover": true,
"coverShape": "circle",
"circleRadius": 160,
"pulseIntensity": 0.45,
"pulseDecay": 0.85,
"showBorder": true,
"borderWidth": 5,
"borderColor": "#00f0ff",
"showProgressRing": false,
"progressRingColor": "#00f0ff",
"progressRingWidth": 5,
"rotationSpeed": 2.0,
"beatSpinImpulse": false,
"showSpectrum": false,
"spectrumMode": "radial_bars",
"linearAlignment": "center_mirrored",
"barCount": 96,
"spectrumMaxHeight": 120,
"barWidth": 4,
"barSpacing": 10,
"barCornerRadius": 2,
"freqSensitivity": 1.3,
"freqFocus": "balanced",
"mirrorSpectrum": false,
"stereoSplit": false,
"stereoMode": "split_hemisphere",
"routePulse": "sub_bass",
"routeParticles": "treble",
"routeShake": "sub_bass",
"bpmSync": false,
"bpmValue": 128,
"bpmSyncRotation": false,
"bpmSyncGrid": false,
"enableParticles": true,
"particleType": "starfield",
"particleCount": 85,
"particleSpeed": 0.9,
"particleWarp": 2.0,
"enableGrid": false,
"colorGrid": "#00f0ff",
"gridPerspective": 0.65,
"gridAudioReactive": 0.3,
"enableShake": false,
"camShakeIntensity": 0.2,
"enableGlitch": false,
"rgbSplitIntensity": 0.1,
"beatFlash": false,
"enableBassZoom": true,
"enableVhs": false,
"vhsScanlineIntensity": 0.2,
"vhsNoiseIntensity": 0.1,
"vhsCrtCurvature": false,
"enableAfterglow": true,
"afterglowDecay": 0.78,
"afterglowMode": "cyan_neon",
"colorMode": "gradient_3",
"colorGrad1": "#00f0ff",
"colorGrad2": "#7000ff",
"colorGrad3": "#ff007f",
"colorParticle": "#00f0ff",
"gradientStyle": "angular",
"enableGlow": true,
"glowIntensity": 0.85,
"glowBlur": 26,
"laserCore": false,
"backgroundMode": "transparent",
"showTextOverlay": true,
"fontFamily": "'Outfit', sans-serif",
"textPosition": "below_circle",
"titleFontSize": 38,
"artistFontSize": 22,
"textPulseWithBeat": true,
"textGlow": true,
"titleColor": "#ffffff",
"artistColor": "#00f0ff",
"timecodeColor": "#7000ff"
}
+81
View File
@@ -0,0 +1,81 @@
{
"name": "Analog VU-Meter",
"icon": "📻",
"shapeType": "vumeter",
"showCover": false,
"coverShape": "circle",
"circleRadius": 150,
"pulseIntensity": 0.4,
"pulseDecay": 0.82,
"showBorder": false,
"borderWidth": 4,
"borderColor": "#ffaa22",
"showProgressRing": false,
"progressRingColor": "#ffaa22",
"progressRingWidth": 5,
"rotationSpeed": 0,
"beatSpinImpulse": false,
"showSpectrum": false,
"spectrumMode": "radial_bars",
"linearAlignment": "center_mirrored",
"barCount": 96,
"spectrumMaxHeight": 120,
"barWidth": 4,
"barSpacing": 10,
"barCornerRadius": 2,
"freqSensitivity": 1.3,
"freqFocus": "balanced",
"mirrorSpectrum": false,
"stereoSplit": true,
"stereoMode": "split_hemisphere",
"routePulse": "sub_bass",
"routeParticles": "treble",
"routeShake": "sub_bass",
"bpmSync": false,
"bpmValue": 128,
"bpmSyncRotation": false,
"bpmSyncGrid": false,
"enableParticles": true,
"particleType": "dust",
"particleCount": 60,
"particleSpeed": 0.5,
"particleWarp": 1.2,
"enableGrid": false,
"colorGrid": "#ffaa22",
"gridPerspective": 0.65,
"gridAudioReactive": 0.3,
"enableShake": false,
"camShakeIntensity": 0.2,
"enableGlitch": false,
"rgbSplitIntensity": 0.1,
"beatFlash": false,
"enableBassZoom": false,
"enableVhs": true,
"vhsScanlineIntensity": 0.2,
"vhsNoiseIntensity": 0.1,
"vhsCrtCurvature": true,
"enableAfterglow": false,
"afterglowDecay": 0.8,
"afterglowMode": "amber_p4",
"colorMode": "gradient_2",
"colorGrad1": "#ffcc44",
"colorGrad2": "#ff4422",
"colorGrad3": "#222222",
"colorParticle": "#ffdd88",
"gradientStyle": "angular",
"enableGlow": true,
"glowIntensity": 0.7,
"glowBlur": 20,
"laserCore": false,
"backgroundMode": "dark_vignette",
"showTextOverlay": true,
"fontFamily": "'Outfit', sans-serif",
"textPosition": "below_circle",
"titleFontSize": 36,
"artistFontSize": 20,
"textPulseWithBeat": false,
"textGlow": true,
"titleColor": "#ffeedd",
"artistColor": "#ccbbaa",
"timecodeColor": "#ffaa22"
}
+33
View File
@@ -1477,3 +1477,36 @@ body {
font-size: 0.75rem;
padding: 4px 8px;
}
/* BPM Controls */
.bpm-input-wrapper {
display: flex;
align-items: center;
gap: 8px;
}
.bpm-num-input {
width: 72px;
background: var(--bg-input);
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
color: var(--text-main);
padding: 6px 8px;
font-family: inherit;
font-weight: 600;
font-size: 0.9rem;
text-align: center;
}
.bpm-num-input:focus {
outline: none;
border-color: var(--border-focus);
}
.bpm-badge {
background: rgba(0, 240, 255, 0.12);
border: 1px solid rgba(0, 240, 255, 0.35);
color: var(--accent-cyan);
font-size: 0.72rem;
font-weight: 700;
padding: 4px 8px;
border-radius: var(--radius-sm);
white-space: nowrap;
}
+1 -1
View File
@@ -146,7 +146,7 @@ app.get('/api/presets', (req, res) => {
const key = file.replace('.json', '');
const data = fs.readFileSync(path.join(presetsDir, file), 'utf-8');
try {
presets[key] = JSON.parse(data);
presets[key] = JSON.parse(data.replace(/^\uFEFF/, ''));
} catch (e) {
console.error(`Error parsing preset ${file}:`, e);
}