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>
Replace per-frame hsl(val*120) with a static createLinearGradient
(green->yellow->red, bottom to top) shared across all bars. Colour is
now position-based, not amplitude-based, so it never flickers. Peak
decay slightly slower (0.008/frame) for smoother hold.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three modes on canvas click:
viz -> spectrum analyser (default)
actual -> hh:mm:ss elapsed in white, ELAPSED label
remaining -> -hh:mm:ss countdown in accent red, REMAINING label
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Time display now shows elapsed / -remaining instead of elapsed / total,
matching the Delphi original (reversetime mode). Progress-bar click seek
derives total from current + remaining to stay correct.
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>
sw.js uses __BUILDVER__ token; build.ps1 replaces it with the git
version before go build (which embeds the patched file), then restores
the template via the finally block. Installed PWA clients receive new
assets after every release.
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>