Backend:
- winamp.GetCurrentFile() reads file path via IPC_GETPLAYLISTFILE (211)
+ ReadProcessMemory, same pattern as playlist titles
- internal/rating: Get/Set POPM frame via bogem/id3v2
- Email: rating@winamp.com (Winamp standard)
- Byte scale: 0/1/64/128/196/255 = 0-5 stars
- Compatible with Windows Explorer and Winamp
- GET /api/rating -> {stars: N}
- POST /api/rating {stars: N} -> writes POPM, returns {stars: N}
Frontend:
- 5 stars in track-info, gold when lit
- Fetched automatically on track change
- Tap to rate; tap same star again to remove rating
- Optimistic update with revert on error
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
os.Create truncates the target immediately; a failed write or flush
leaves the file empty/corrupt. Write to a temp file in the same
directory, flush, close, then os.Rename — the rename is atomic on
Windows (same volume). Also check WriteString return values.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Winamp IPC_GETPLAYLISTTITLE returns ANSI (char*) strings in its process
memory. Blindly casting bytes to string garbles umlauts and other non-ASCII
chars. Fix: decode via MultiByteToWideChar(CP_ACP) → UTF-16 → Go string,
using the same code page Windows uses for the title.
Also check ReadProcessMemory return value — failures now return empty
string instead of garbage.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract saveResumeState helper: saves when playing OR paused (not just
paused). Both WS and REST stop handlers now call it.
- restoreResume now jumps to saved PlaylistPos before seeking, so the
correct track is loaded even when playlist order differs.
- Title validation: if the loaded title does not match the window title
after jumping, abort restore and delete stale resume file.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
runtime.LockOSThread/UnlockOSThread added to withEndpointVolume (volume)
and Capturer.run (viz). COM apartments are thread-affine on Windows;
without the lock the Go scheduler can migrate a goroutine mid-call to a
thread that never called CoInitializeEx, causing sporadic failures.
Also check CoInitializeEx HRESULT: S_OK (0) and S_FALSE (1) are success,
anything else is a real error.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Playlist browser:
- GET /api/playlist returns all track titles via ReadProcessMemory
- WS command {cmd:jump, index:N} jumps to track (0-based)
- Full-screen overlay with scrollable list
- Current track highlighted, auto-scrolls into view on open
- Live highlight update when track changes while panel is open
PWA:
- manifest.json with standalone display + theme colour
- sw.js: cache-first service worker for shell files
- icon.svg: music-note icon on dark background
- Apple/Android meta tags for Add to Homescreen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add host field to Config (default 0.0.0.0)
- Change default port from 8080 to 8889
- Listen on host:port instead of :port
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add build.ps1: one-shot PowerShell build with version from git tag
- winamp_path config is now optional; roadamp works with any running
Winamp instance regardless of install location (FindWindow does the
discovery). Path is only needed if roadamp should launch Winamp itself.
- Remove hardcoded C:\Program Files\Winamp default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Static files are now compiled into the binary using //go:embed.
- Add web/embed.go with //go:embed static directive
- Update server.New() to accept fs.FS parameter
- Switch file server from http.Dir to http.FS
Deployment is now: roadamp.exe + config.yaml + winamp/
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Canvas / viz:
- Idle animation when Winamp stopped/paused: slow sine-wave breathing
across bars at low amplitude, dim colours — canvas is never dead-black
- 'PLAY' label overlay when stopped, 'NO SIGNAL' when WS disconnected
- hasSignal check: viz data must be < 1.5s old AND Winamp playing
- DPR resize uses Math.round() to avoid sub-pixel canvas size mismatch
- Peak indicators fade quickly in idle mode
winamp.go:
- GetPosition: clamp 0xFFFFFFFF sentinel at source (> 0xF0000000 → 0)
instead of in server.go; also removed redundant clamp from statusMsg
- GetTitle: use strings.LastIndex/Index instead of hand-rolled helpers
Returns empty string when window title is just 'Winamp' (stopped/empty)
Playlist prefix strip is now bounded (dot <= 4) so track titles with
'. ' in them are not accidentally trimmed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- viz/capture.go: waveFormatExtensibleEx now flat struct (not embedded)
Go pads waveFormatEx to 20 bytes (uint32 alignment), but the Windows
C struct is 18 bytes — embedding caused SubFormat to land at wrong
offset, breaking float32 detection (float=false bug)
- server.go: clamp position/length values > 24h to 0
Winamp returns 0xFFFFFFFF for these fields when stopped/no track loaded
- .gitignore: exclude winamp/ dir and runtime .dat files
- config.yaml: added (gitignored) pointing to winamp/winamp.exe
Tested: Winamp 5.9 portable in winamp/, IPC connection verified,
viz loopback correctly detects 48kHz float32 stereo
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
WebSocket (/ws):
- Replaces 2s HTTP polling with persistent WS connection
- Server pushes status updates every 500ms to all clients
- Server pushes FFT spectrum frames at ~30fps
- Bidirectional: client sends commands as JSON ({cmd, delta, level, ...})
- Auto-reconnect on disconnect (3s backoff)
- Status pushed immediately on connect
Visualisation (internal/viz/capture.go):
- WASAPI loopback capture via IAudioClient (same COM approach as volume)
- Captures whatever is playing through the default render device
- 2048-sample Hanning-windowed FFT (pure Go, no deps)
- 64 log-spaced bars, 40Hz-20kHz
- Fast attack / slow decay smoothing per bar
Canvas renderer (app.js):
- requestAnimationFrame loop, DPR-aware resize
- Green->yellow->red HSL gradient by amplitude
- Peak-hold indicators with 1.2%/frame decay
- Graceful: canvas stays dark if no viz data (Winamp paused/stopped)
Architecture:
- internal/server/hub.go: gorilla/websocket hub (register/broadcast/unregister)
- internal/server/server.go: full //go:build windows, REST API kept for debug
- frontend uses WS for all commands, REST only for killist list fetch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- internal/volume: IAudioEndpointVolume via COM (pure Go, no CGO)
- Get/Set master volume (0-100%)
- GetMute/SetMute
- Uses WASAPI instead of deprecated MMSystem mixer API
- server: /api/volume now uses system volume (0-100, was Winamp 0-255)
- server: new /api/mute endpoint (GET + POST ?muted=true|false)
- server: volume+mute included in /api/status response
- frontend: mute button with visual state (🔊/🔇, red bar when muted)
- frontend: volume display as percentage label
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>