Describes the architecture for an extensible music visualizer framework: - Audio data layer with frequency, beat detection, and energy bands - Registry system with discriminated union pattern for visualizer types - 10+ planned built-in visualizations (spectrum, particles, 3D, etc.) - Custom visualizer system with sandboxed code execution - AI-powered code generation via mana-llm - Fullscreen visualizer mode and community sharing - Phased implementation roadmap Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
19 KiB
Mukke Visualizer System – Konzept
Vision
Ein erweiterbares Visualisierungs-Framework, das:
- Viele eingebaute Visualisierungen mitbringt (Frequency Bars, Circular, Particles, Waveform, etc.)
- Nutzern ermöglicht, eigene Visualisierungen zu erstellen (Code-Editor oder KI-generiert)
- Visualisierungen als "Community Presets" teilbar macht
- Sich nahtlos in den bestehenden Player (FullPlayer, MiniPlayer, Fullscreen-Modus) integriert
Architektur-Übersicht
┌─────────────────────────────────────────────────────────┐
│ Player UI │
│ FullPlayer │ MiniPlayer │ Fullscreen Visualizer │
└────────────────────────┬────────────────────────────────┘
│
┌────────────────────────▼────────────────────────────────┐
│ VisualizerRenderer.svelte │
│ Wählt den aktiven Renderer, übergibt AudioData + Config│
└────────────────────────┬────────────────────────────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Built-in │ │ Built-in │ │ Custom │
│ Renderer │ │ Renderer │ │ Renderer │
│ (Svelte) │ │ (Svelte) │ │ (Sandboxed)│
└────────────┘ └────────────┘ └────────────┘
│ │ │
└──────────────┼──────────────┘
▼
┌─────────────────────────────────────────────────────────┐
│ Audio Analyzer (Singleton) │
│ AnalyserNode → frequencyData, timeDomainData, volume │
└─────────────────────────────────────────────────────────┘
1. Audio Data Layer
Aktueller Stand
src/lib/visualizer/analyzer.ts liefert bereits:
getFrequencyData()→Uint8Array(Frequenzbänder 0-255)getFrequencyBinCount()→ Anzahl der Bins
Erweiterung
// src/lib/visualizer/audio-data.ts
export interface AudioData {
/** Frequency spectrum (0-255 per bin) */
frequency: Uint8Array;
/** Time domain waveform (-128 to 127, centered at 128) */
timeDomain: Uint8Array;
/** Number of frequency bins */
binCount: number;
/** Current RMS volume (0-1) */
volume: number;
/** Current bass energy (0-1) – average of lowest 1/8 bins */
bass: number;
/** Current mid energy (0-1) */
mid: number;
/** Current high energy (0-1) */
high: number;
/** Beat detected this frame */
beat: boolean;
/** Current playback time in seconds */
currentTime: number;
/** Song duration in seconds */
duration: number;
/** Song BPM (if available) */
bpm: number | null;
}
Die AudioData-Struktur wird einmal pro Frame berechnet und an alle aktiven Renderer weitergegeben. So müssen Visualisierungen selbst keine Audio-Analyse machen.
2. Visualizer Registry
Discriminated Union Pattern (wie Picture App Board Items)
// src/lib/visualizer/types.ts
export interface VisualizerMeta {
id: string;
name: string;
description: string;
author: string;
thumbnail?: string; // Preview-Bild oder generiert
category: VisualizerCategory;
tags: string[];
version: string;
}
export type VisualizerCategory =
| 'spectrum' // Frequency-basiert (Bars, Circular)
| 'waveform' // Wellenform-basiert
| 'particles' // Partikel-Systeme
| 'geometric' // Geometrische Muster
| 'abstract' // Abstrakte/generative Kunst
| 'custom'; // User-created
export interface VisualizerConfig {
[key: string]: unknown;
}
export interface VisualizerDefinition<C extends VisualizerConfig = VisualizerConfig> {
meta: VisualizerMeta;
/** Default-Konfiguration */
defaultConfig: C;
/** JSON-Schema für die Config (für UI-Generierung) */
configSchema: ConfigSchema;
/** Rendering-Typ */
type: 'builtin' | 'custom-canvas' | 'custom-shader';
}
Registry Store
// src/lib/visualizer/registry.svelte.ts
function createVisualizerRegistry() {
let visualizers = $state<Map<string, VisualizerDefinition>>(new Map());
let activeId = $state<string>('frequency-bars');
let activeConfig = $state<VisualizerConfig>({});
return {
get all() { return [...visualizers.values()]; },
get active() { return visualizers.get(activeId) ?? null; },
get activeConfig() { return activeConfig; },
register(def: VisualizerDefinition) { ... },
unregister(id: string) { ... },
setActive(id: string) { ... },
updateConfig(patch: Partial<VisualizerConfig>) { ... },
getByCategory(cat: VisualizerCategory) { ... },
};
}
3. Built-in Visualisierungen
Phase 1 (implementiert)
| ID | Name | Beschreibung |
|---|---|---|
frequency-bars |
Frequency Bars | Klassische EQ-Balken, konfigurierbar (Anzahl, Farbe, Mirror) |
Phase 2 (Editor-Verbesserungen)
| ID | Name | Beschreibung |
|---|---|---|
beat-grid |
Beat Grid | BPM-basiertes Raster über Waveform |
energy-heatmap |
Energy Heatmap | Waveform eingefärbt nach Lautstärke |
Phase 3 (Premium)
| ID | Name | Beschreibung |
|---|---|---|
circular-spectrum |
Circular Spectrum | Kreisförmig, Album-Art in der Mitte, Frequenz als Strahlen |
particles |
Particle Flow | Partikel reagieren auf Bass/Transients |
waveform-3d |
3D Waveform | Scrollende 3D-Wellenform (Terrain-Stil) |
blob |
Reactive Blob | Organische Form, die zum Beat atmet |
bars-circular |
Circular Bars | Bars im Kreis angeordnet |
spectrum-wave |
Spectrum Wave | Smooth-kurvige Frequenzlinie |
stereo-field |
Stereo Field | L/R Panorama-Visualisierung |
kaleidoscope |
Kaleidoscope | Symmetrische Muster aus Frequenzdaten |
4. Rendering-Ansätze
Option A: Svelte Components (Built-ins)
Jede Built-in-Visualisierung ist eine Svelte-Komponente mit standardisiertem Interface:
<script lang="ts">
import type { AudioData, VisualizerConfig } from '$lib/visualizer/types';
interface Props {
audioData: AudioData;
config: VisualizerConfig;
width: number;
height: number;
}
let { audioData, config, width, height }: Props = $props();
// Rendering-Logik mit Canvas 2D, SVG, oder WebGL
</script>
<canvas bind:this={canvas} {width} {height}></canvas>
Pro: Volle Svelte-Reaktivität, Tree-Shakeable, einfach zu warten Contra: Nicht dynamisch ladbar für User-Visualisierungen
Option B: Canvas 2D Render Functions (Custom)
User-Visualisierungen als reine Render-Funktionen:
interface CustomVisualizerFn {
setup?: (ctx: CanvasRenderingContext2D, width: number, height: number) => void;
render: (ctx: CanvasRenderingContext2D, data: AudioData, config: VisualizerConfig,
width: number, height: number) => void;
destroy?: () => void;
}
Pro: Einfach, sicher (nur Canvas-Zugriff), leicht per Code-Editor erstellbar Contra: Nur 2D, kein DOM-Zugriff
Option C: WebGL/Shader (Advanced)
GLSL Fragment Shaders für GPU-beschleunigte Visualisierungen:
// User schreibt nur den Fragment Shader
uniform float u_time;
uniform float u_bass;
uniform float u_mid;
uniform float u_high;
uniform float u_volume;
uniform sampler2D u_frequency; // Frequenzdaten als Textur
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
// ... Shader-Code
gl_FragColor = vec4(color, 1.0);
}
Pro: GPU-beschleunigt, visuell beeindruckend, Shadertoy-kompatibel Contra: Steile Lernkurve, schwer zu debuggen
Empfehlung: Hybrid-Ansatz
| Typ | Technologie | Verwendung |
|---|---|---|
| Built-in | Svelte + Canvas 2D | Alle mitgelieferten Visualisierungen |
| Custom Canvas | Sandboxed Canvas 2D Function | User-erstellte 2D-Visualisierungen |
| Custom Shader | WebGL Fragment Shader | User-erstellte GPU-Visualisierungen |
5. Custom Visualizer System
User-Workflow
- Galerie öffnen → Alle verfügbaren Visualisierungen als Grid mit Live-Preview
- "Create New" → Code-Editor öffnet sich
- Template wählen → Startercode für Canvas 2D oder Shader
- Code schreiben → Live-Preview neben dem Editor
- KI-Assistent → "Erstelle eine Visualisierung die..." → Code wird generiert
- Speichern → In der persönlichen Bibliothek
- Teilen → Als Community Preset veröffentlichen (optional)
Code-Editor Integration
┌─────────────────────────────────────────────────────┐
│ ┌─────────────────────┐ ┌──────────────────────┐ │
│ │ │ │ │ │
│ │ Code Editor │ │ Live Preview │ │
│ │ (Monaco/CM6) │ │ (Canvas) │ │
│ │ │ │ │ │
│ │ │ │ │ │
│ └─────────────────────┘ └──────────────────────┘ │
│ ┌──────────────────────────────────────────────────┐│
│ │ Config Panel: [barCount: 32] [color: #ff0] ││
│ └──────────────────────────────────────────────────┘│
│ [💾 Save] [▶ Test with Audio] [🤖 Ask AI] [📤 Share]│
└─────────────────────────────────────────────────────┘
Sandboxing (Sicherheit)
Custom Code läuft nicht direkt im Hauptthread:
Option 1: new Function() mit Whitelist
- Kein Zugriff auf window, document, fetch, etc.
- Nur ctx (Canvas), data (AudioData), config erreichbar
- Einfach, performant, leichte Einschränkungen
Option 2: Web Worker + OffscreenCanvas
- Code läuft in isoliertem Worker
- Rendert auf OffscreenCanvas, wird in Hauptthread übertragen
- Sicherer, aber komplexer und nicht alle Browser unterstützen OffscreenCanvas
Option 3: iframe Sandbox
- Maximale Isolation
- Overhead durch postMessage-Kommunikation
- Overkill für Canvas-Rendering
Empfehlung: Option 1 (new Function()) für Canvas 2D, direkte WebGL-Ausführung für Shaders (Shader-Code ist von Natur aus sandboxed auf der GPU).
Datenbank-Schema
// Erweiterung der DB (Backend)
visualizers: {
id: uuid,
userId: uuid,
meta: jsonb, // VisualizerMeta
config: jsonb, // Default VisualizerConfig
configSchema: jsonb, // JSON Schema für Config-UI
code: text, // Render-Function oder Shader-Code
type: enum('canvas-2d', 'shader'),
isPublic: boolean,
likes: integer,
createdAt: timestamp,
updatedAt: timestamp,
}
6. Visualizer Galerie UI
┌─────────────────────────────────────────────────────────┐
│ 🎵 Visualisierungen [+ Create New] │
│ │
│ [All] [Spectrum] [Waveform] [Particles] [Community] │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ ▓▓▓▓▓▓▓▓ │ │ ◉ ))) ) │ │ · · · · │ │
│ │ ▓▓▓▓▓▓▓▓ │ │ ◉ )))) ) │ │· · · · │ │
│ │ Freq Bars │ │ Circular │ │ Particles │ │
│ │ ★ Built-in│ │ ★ Built-in│ │ ★ Built-in│ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ ~~~~~~~~ │ │ ╱╲╱╲╱╲╱╲ │ │ ▒▒▒▒▒▒▒▒ │ │
│ │ ~~~~~~~~ │ │ ╱╲╱╲╱╲╱╲ │ │ ▒▒▒▒▒▒▒▒ │ │
│ │ Waveform │ │ Kaleido │ │ My Custom │ │
│ │ ★ Built-in│ │ ★ Built-in│ │ 👤 You │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────┘
Config Panel (pro Visualisierung)
Jede Visualisierung definiert ein configSchema, aus dem automatisch UI generiert wird:
const configSchema: ConfigSchema = {
barCount: { type: 'range', min: 8, max: 128, step: 1, label: 'Bar Count' },
color: { type: 'color', label: 'Color' },
mirror: { type: 'toggle', label: 'Mirror' },
sensitivity: { type: 'range', min: 0, max: 2, step: 0.1, label: 'Sensitivity' },
colorMode: { type: 'select', options: ['gradient', 'solid', 'rainbow'], label: 'Color Mode' },
};
7. KI-Integration
Prompt-basierte Erstellung
User beschreibt die gewünschte Visualisierung in natürlicher Sprache:
"Erstelle einen Visualizer der wie Nordlichter aussieht. Die Farben sollen zwischen Grün und Lila wechseln, basierend auf den Bässen."
→ KI generiert Canvas 2D oder Shader Code → Live-Preview zeigt das Ergebnis sofort → User kann iterieren ("mach die Bewegung schneller", "füge Sterne hinzu")
System Prompt für KI
Du bist ein Musik-Visualisierungs-Experte. Generiere eine render()-Funktion:
function render(ctx, data, config, width, height) {
// ctx: CanvasRenderingContext2D
// data: { frequency, timeDomain, volume, bass, mid, high, beat, currentTime, bpm }
// config: User-konfigurierbare Werte
// width, height: Canvas-Dimensionen
}
Regeln:
- Lösche den Canvas mit ctx.clearRect(0, 0, width, height)
- Nutze data.frequency (Uint8Array, 0-255) für Spektrumdaten
- data.bass/mid/high sind normalisiert (0-1)
- data.beat ist true wenn ein Beat erkannt wurde
- Halte den Code performant (60fps)
- Gib auch ein configSchema-Objekt zurück
Integration über mana-llm Service
User Input → mana-llm → Code generiert → Sandbox → Live Preview
Nutzt den bestehenden services/mana-llm als LLM-Abstraktionsschicht.
8. Fullscreen Visualizer Mode
Neuer Fullscreen-Modus für immersive Erfahrung:
- Visualisierung füllt den gesamten Bildschirm
- Minimale Transport-Controls (transparent overlay, auto-hide)
- Song-Info eingeblendet bei Track-Wechsel (fade in/out)
- Keyboard-Shortcuts (Space = Play/Pause, Esc = Exit, N = Next Viz)
- Screensaver-Modus: Wechselt automatisch zwischen Visualisierungen
9. Datei-Struktur (geplant)
src/lib/visualizer/
├── analyzer.ts # Audio Analyzer (existiert)
├── audio-data.ts # AudioData Interface + Berechnung
├── types.ts # Alle Type-Definitionen
├── registry.svelte.ts # Visualizer Registry Store
├── sandbox.ts # Custom Code Sandboxing
├── FrequencyBars.svelte # Built-in (existiert)
├── renderers/
│ ├── CircularSpectrum.svelte # Kreisförmiges Spektrum
│ ├── ParticleFlow.svelte # Partikel-System
│ ├── Waveform3D.svelte # 3D Waveform
│ ├── ReactiveBlob.svelte # Organische Form
│ ├── CircularBars.svelte # Bars im Kreis
│ ├── SpectrumWave.svelte # Glatte Frequenzkurve
│ ├── StereoField.svelte # Stereo-Analyse
│ └── Kaleidoscope.svelte # Kaleidoskop-Muster
├── components/
│ ├── VisualizerRenderer.svelte # Router: wählt aktiven Renderer
│ ├── VisualizerGallery.svelte # Galerie-Ansicht
│ ├── VisualizerConfig.svelte # Auto-generiertes Config-Panel
│ ├── VisualizerEditor.svelte # Code-Editor für Custom Viz
│ ├── VisualizerPreview.svelte # Live-Preview Canvas
│ └── FullscreenVisualizer.svelte# Fullscreen-Modus
└── templates/
├── canvas-2d-starter.ts # Template für Canvas 2D
└── shader-starter.glsl # Template für GLSL Shader
10. Implementierungs-Reihenfolge
| Phase | Was | Aufwand |
|---|---|---|
| 1 ✅ | Frequency Bars + Analyzer (done) | — |
| 2 | AudioData erweitern (bass/mid/high/beat), Registry Store, VisualizerRenderer | S |
| 3 | 3-4 weitere Built-in Renderer (Circular, Particles, Blob, Wave) | M |
| 4 | Galerie UI + Config Panel + Fullscreen Mode | M |
| 5 | Custom Visualizer: Sandbox + Code-Editor + Templates | L |
| 6 | KI-Integration (mana-llm) für Code-Generierung | M |
| 7 | Community: Teilen, Likes, öffentliche Galerie | L |
| 8 | Backend: visualizers-Tabelle, CRUD API, User-Presets | M |
11. Technologie-Entscheidungen
| Bereich | Entscheidung | Begründung |
|---|---|---|
| Built-in Rendering | Canvas 2D | Performant, einfach, kein Dep overhead |
| GPU-Effekte | Raw WebGL (kein Three.js) | Vermeidet ~150kb Dependency für Shader-only |
| Code Editor | CodeMirror 6 | Leichtgewichtig, Svelte-freundlich, besser als Monaco für embedded |
| Sandboxing | new Function() mit Whitelist |
Guter Kompromiss aus Sicherheit und Performance |
| State | Svelte 5 Runes Store | Konsistent mit Rest der App |
| Persistenz | JSONB in PostgreSQL | Flexibel, keine Migrationen bei Config-Änderungen |
| KI | mana-llm Service | Bereits vorhanden, LLM-agnostisch |