Commit Graph
4 Commits
Author SHA1 Message Date
rin.kagamine 1e21b7dcc8 feat: BPM and musical key detection for all clips
Backend:
- New analysis.py module using librosa for BPM + key detection
  - detect_bpm(): librosa.beat.beat_track() returns tempo
  - detect_key(): Krumhansl-Schmuckler key profiles on chroma features
  - analyze_clip(): combined call returning {bpm, key}
- New dependency: librosa==0.10.2 + soundfile==0.12.1
- Database migrations: bpm (REAL) and key (TEXT) columns added
- New set_analysis() function in database.py
- New endpoint: GET /api/clips/{id}/analysis
  - Returns cached analysis if available
  - Computes on-demand if missing, caches result
  - 400 if clip not ready, 404 if no audio file

Frontend:
- Clip interface extended with bpm?: number | null, key?: string | null
- Gallery: BPM and key badges shown in ribbon and tile views
  - BPM badge: lyria-accent background
  - Key badge: white/5 background
- DetailPanel: BPM and key shown in metadata grid when available
- App.tsx: handleSelectClip() fetches analysis on-demand when opening
  detail panel if not already cached

Docs:
- Architecture KB updated with Audio Analysis section
- Tasks KB: T-028 marked In Progress → will mark Done after commit
2026-05-29 21:09:04 -04:00
rin.kagamine 8f5c8a87d1 feat: Add themed FFT waveform visualization to detail panel
Backend:
- Install ffmpeg + numpy in Docker image
- New waveform.py: FFT-based SVG generator with 64 frequency bands
- GET /api/clips/{id}/waveform — generates on demand, caches to /data/waveforms/
- DELETE /api/clips/{id} now cascade-removes waveform SVG
- Warm amber-to-orange HSL gradient (25° to 35° hue)
- Mirrored bars: top full opacity, bottom 60% opacity

Frontend:
- DetailPanel shows waveform image beneath album art (80px height)
- Only shown for clips with status='ready'

Storage: /data/waveforms/ persisted via existing ./data volume mount
2026-05-29 20:11:01 -04:00
rin.kagamine 23672387e5 Fix Lyria API response parsing — audio in parts[1], not parts[0]
The Lyria API returns a candidates[].content.parts[] array where:
- parts[0] = text metadata (e.g. '<instrumental>')
- parts[1] = inlineData with the actual audio payload

Previous code only checked parts[0] for inlineData, which always failed
with 'No audio data in response'. Fixed by iterating all parts to find
the inlineData entry.

Successfully generated first real audio clip (lo-fi hip hop, 30s, 728KB MP3).

Also includes: .env.example, requirements updates, docker-compose fixes,
frontend vite config, logger setup.
2026-05-29 16:59:28 -04:00
rin.kagamine a2bc785b14 Initial scaffold: FastAPI backend + React frontend + Docker Compose
- Backend: FastAPI with SQLite, clip CRUD, health check
- Frontend: React + Vite + TypeScript + Tailwind, dark theme
- Components: Generator, Gallery, Player
- Docker: Compose setup for backend + frontend
- Architecture docs in Palace KB
2026-05-26 18:18:55 -04:00