mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-16 09:59:41 +02:00
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>
490 lines
19 KiB
Markdown
490 lines
19 KiB
Markdown
# 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
|
||
|
||
```typescript
|
||
// 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)
|
||
|
||
```typescript
|
||
// 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
|
||
|
||
```typescript
|
||
// 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:
|
||
|
||
```svelte
|
||
<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:
|
||
|
||
```typescript
|
||
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:
|
||
|
||
```glsl
|
||
// 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
|
||
|
||
1. **Galerie öffnen** → Alle verfügbaren Visualisierungen als Grid mit Live-Preview
|
||
2. **"Create New"** → Code-Editor öffnet sich
|
||
3. **Template wählen** → Startercode für Canvas 2D oder Shader
|
||
4. **Code schreiben** → Live-Preview neben dem Editor
|
||
5. **KI-Assistent** → "Erstelle eine Visualisierung die..." → Code wird generiert
|
||
6. **Speichern** → In der persönlichen Bibliothek
|
||
7. **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
|
||
|
||
```typescript
|
||
// 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:
|
||
|
||
```typescript
|
||
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 |
|