From 51387bddbaec61021daf92e56a516a96e7efe20a Mon Sep 17 00:00:00 2001 From: Glenn Thompson Date: Sat, 6 Dec 2025 18:29:53 +0300 Subject: [PATCH] Add spectrum analyzer theming and visualization styles - Add 6 color themes: green, blue, purple, red, amber, rainbow - Add 3 visualization styles: bars, wave, dots - Add UI controls (dropdowns) to change theme and style - Persist user preferences in localStorage - Remove debug logging from parenscript serving and Icecast stats - Fix dots visualization with proper Math.PI handling --- asteroid.lisp | 14 ---- frontend-partials.lisp | 2 - parenscript/spectrum-analyzer.lisp | 115 +++++++++++++++++++++++++---- template/front-page-content.ctml | 21 ++++++ template/front-page.ctml | 21 ++++++ template/player-content.ctml | 21 ++++++ 6 files changed, 165 insertions(+), 29 deletions(-) diff --git a/asteroid.lisp b/asteroid.lisp index c763d20..7a92a4e 100644 --- a/asteroid.lisp +++ b/asteroid.lisp @@ -558,11 +558,9 @@ (cond ;; Serve ParenScript-compiled auth-ui.js ((string= path "js/auth-ui.js") - (format t "~%=== SERVING PARENSCRIPT auth-ui.js ===~%") (setf (content-type *response*) "application/javascript") (handler-case (let ((js (generate-auth-ui-js))) - (format t "DEBUG: Generated JS length: ~a~%" (if js (length js) "NIL")) (if js js "// Error: No JavaScript generated")) (error (e) (format t "ERROR generating auth-ui.js: ~a~%" e) @@ -570,11 +568,9 @@ ;; Serve ParenScript-compiled front-page.js ((string= path "js/front-page.js") - (format t "~%=== SERVING PARENSCRIPT front-page.js ===~%") (setf (content-type *response*) "application/javascript") (handler-case (let ((js (generate-front-page-js))) - (format t "DEBUG: Generated JS length: ~a~%" (if js (length js) "NIL")) (if js js "// Error: No JavaScript generated")) (error (e) (format t "ERROR generating front-page.js: ~a~%" e) @@ -582,11 +578,9 @@ ;; Serve ParenScript-compiled profile.js ((string= path "js/profile.js") - (format t "~%=== SERVING PARENSCRIPT profile.js ===~%") (setf (content-type *response*) "application/javascript") (handler-case (let ((js (generate-profile-js))) - (format t "DEBUG: Generated JS length: ~a~%" (if js (length js) "NIL")) (if js js "// Error: No JavaScript generated")) (error (e) (format t "ERROR generating profile.js: ~a~%" e) @@ -594,11 +588,9 @@ ;; Serve ParenScript-compiled users.js ((string= path "js/users.js") - (format t "~%=== SERVING PARENSCRIPT users.js ===~%") (setf (content-type *response*) "application/javascript") (handler-case (let ((js (generate-users-js))) - (format t "DEBUG: Generated JS length: ~a~%" (if js (length js) "NIL")) (if js js "// Error: No JavaScript generated")) (error (e) (format t "ERROR generating users.js: ~a~%" e) @@ -606,11 +598,9 @@ ;; Serve ParenScript-compiled admin.js ((string= path "js/admin.js") - (format t "~%=== SERVING PARENSCRIPT admin.js ===~%") (setf (content-type *response*) "application/javascript") (handler-case (let ((js (generate-admin-js))) - (format t "DEBUG: Generated JS length: ~a~%" (if js (length js) "NIL")) (if js js "// Error: No JavaScript generated")) (error (e) (format t "ERROR generating admin.js: ~a~%" e) @@ -618,11 +608,9 @@ ;; Serve ParenScript-compiled player.js ((string= path "js/player.js") - (format t "~%=== SERVING PARENSCRIPT player.js ===~%") (setf (content-type *response*) "application/javascript") (handler-case (let ((js (generate-player-js))) - (format t "DEBUG: Generated JS length: ~a~%" (if js (length js) "NIL")) (if js js "// Error: No JavaScript generated")) (error (e) (format t "ERROR generating player.js: ~a~%" e) @@ -630,11 +618,9 @@ ;; Serve ParenScript-compiled recently-played.js ((string= path "js/recently-played.js") - (format t "~%=== SERVING PARENSCRIPT recently-played.js ===~%") (setf (content-type *response*) "application/javascript") (handler-case (let ((js (generate-recently-played-js))) - (format t "DEBUG: Generated JS length: ~a~%" (if js (length js) "NIL")) (if js js "// Error: No JavaScript generated")) (error (e) (format t "ERROR generating recently-played.js: ~a~%" e) diff --git a/frontend-partials.lisp b/frontend-partials.lisp index 047627a..d3a0a1d 100644 --- a/frontend-partials.lisp +++ b/frontend-partials.lisp @@ -10,7 +10,6 @@ (response (drakma:http-request icecast-url :want-stream nil :basic-authorization '("admin" "asteroid_admin_2024")))) - (format t "DEBUG: Fetching Icecast stats from ~a~%" icecast-url) (when response (let ((xml-string (if (stringp response) response @@ -34,7 +33,6 @@ (aref groups 0) "Unknown"))) "Unknown"))) - (format t "DEBUG: Parsed title=~a, total-listeners=~a~%" title total-listeners) ;; Track recently played if title changed (when (and title diff --git a/parenscript/spectrum-analyzer.lisp b/parenscript/spectrum-analyzer.lisp index 01ffd3e..fe03fa6 100644 --- a/parenscript/spectrum-analyzer.lisp +++ b/parenscript/spectrum-analyzer.lisp @@ -14,6 +14,18 @@ (defvar *animation-id* nil) (defvar *media-source* nil) (defvar *current-audio-element* nil) + (defvar *current-theme* "green") + (defvar *current-style* "bars") + + ;; Color themes for spectrum analyzer + (defvar *themes* + (ps:create + "green" (ps:create "top" "#00ff00" "mid" "#00aa00" "bottom" "#005500") + "blue" (ps:create "top" "#00ffff" "mid" "#0088ff" "bottom" "#0044aa") + "purple" (ps:create "top" "#ff00ff" "mid" "#aa00aa" "bottom" "#550055") + "red" (ps:create "top" "#ff0000" "mid" "#aa0000" "bottom" "#550000") + "amber" (ps:create "top" "#ffaa00" "mid" "#ff6600" "bottom" "#aa3300") + "rainbow" (ps:create "top" "#ff00ff" "mid" "#00ffff" "bottom" "#00ff00"))) (defun reset-spectrum-analyzer () "Reset the spectrum analyzer to allow reconnection after audio element reload" @@ -98,21 +110,60 @@ (setf (ps:@ *canvas-ctx* |fillStyle|) "rgba(0, 0, 0, 0.2)") (ps:chain *canvas-ctx* (fill-rect 0 0 width height)) - ;; Draw bars - (dotimes (i buffer-length) - (setf bar-height (/ (* (aref data-array i) height) 256)) - - ;; Create gradient for each bar - (let ((gradient (ps:chain *canvas-ctx* - (create-linear-gradient 0 (- height bar-height) 0 height)))) - (ps:chain gradient (add-color-stop 0 "#00ff00")) - (ps:chain gradient (add-color-stop 0.5 "#00aa00")) - (ps:chain gradient (add-color-stop 1 "#005500")) + ;; Get current theme colors + (let ((theme (ps:getprop *themes* *current-theme*))) + (cond + ;; Bar graph style + ((= *current-style* "bars") + (setf x 0) + (dotimes (i buffer-length) + (setf bar-height (/ (* (aref data-array i) height) 256)) + + ;; Create gradient for each bar using theme colors + (let ((gradient (ps:chain *canvas-ctx* + (create-linear-gradient 0 (- height bar-height) 0 height)))) + (ps:chain gradient (add-color-stop 0 (ps:@ theme top))) + (ps:chain gradient (add-color-stop 0.5 (ps:@ theme mid))) + (ps:chain gradient (add-color-stop 1 (ps:@ theme bottom))) + + (setf (ps:@ *canvas-ctx* |fillStyle|) gradient) + (ps:chain *canvas-ctx* (fill-rect x (- height bar-height) bar-width bar-height)) + + (incf x bar-width)))) + + ;; Wave/line style + ((= *current-style* "wave") + (setf x 0) + (ps:chain *canvas-ctx* (begin-path)) + (setf (ps:@ *canvas-ctx* |lineWidth|) 2) + (setf (ps:@ *canvas-ctx* |strokeStyle|) (ps:@ theme top)) - (setf (ps:@ *canvas-ctx* |fillStyle|) gradient) - (ps:chain *canvas-ctx* (fill-rect x (- height bar-height) bar-width bar-height)) + (dotimes (i buffer-length) + (setf bar-height (/ (* (aref data-array i) height) 256)) + (let ((y (- height bar-height))) + (if (= i 0) + (ps:chain *canvas-ctx* (move-to x y)) + (ps:chain *canvas-ctx* (line-to x y))) + (incf x bar-width))) - (incf x bar-width))) + (ps:chain *canvas-ctx* (stroke))) + + ;; Dots/particles style + ((= *current-style* "dots") + (setf x 0) + (setf (ps:@ *canvas-ctx* |fillStyle|) (ps:@ theme top)) + (dotimes (i buffer-length) + (let* ((value (aref data-array i)) + (normalized-height (/ (* value height) 256)) + (y (- height normalized-height)) + (dot-radius (ps:max 2 (/ normalized-height 20)))) + + (when (> value 0) + (ps:chain *canvas-ctx* (begin-path)) + (ps:chain *canvas-ctx* (arc x y dot-radius 0 6.283185307179586)) + (ps:chain *canvas-ctx* (fill))) + + (incf x bar-width)))))) ;; Draw MUTED indicator if audio is muted (when is-muted @@ -128,9 +179,47 @@ (cancel-animation-frame *animation-id*) (setf *animation-id* nil))) + (defun set-spectrum-theme (theme-name) + "Change the spectrum analyzer color theme" + (when (ps:getprop *themes* theme-name) + (setf *current-theme* theme-name) + (ps:chain local-storage (set-item "spectrum-theme" theme-name)) + (ps:chain console (log (+ "Spectrum theme changed to: " theme-name))))) + + (defun get-available-themes () + "Return array of available theme names" + (ps:chain |Object| (keys *themes*))) + + (defun set-spectrum-style (style-name) + "Change the spectrum analyzer visualization style" + (when (or (= style-name "bars") (= style-name "wave") (= style-name "dots")) + (setf *current-style* style-name) + (ps:chain local-storage (set-item "spectrum-style" style-name)) + (ps:chain console (log (+ "Spectrum style changed to: " style-name))))) + + (defun get-available-styles () + "Return array of available visualization styles" + (array "bars" "wave" "dots")) + ;; Initialize when audio starts playing (ps:chain document (add-event-listener "DOMContentLoaded" (lambda () + ;; Load saved theme and style preferences + (let ((saved-theme (ps:chain local-storage (get-item "spectrum-theme"))) + (saved-style (ps:chain local-storage (get-item "spectrum-style")))) + (when (and saved-theme (ps:getprop *themes* saved-theme)) + (setf *current-theme* saved-theme)) + (when (and saved-style (or (= saved-style "bars") (= saved-style "wave") (= saved-style "dots"))) + (setf *current-style* saved-style)) + + ;; Update UI selectors if they exist + (let ((theme-selector (ps:chain document (get-element-by-id "spectrum-theme-selector"))) + (style-selector (ps:chain document (get-element-by-id "spectrum-style-selector")))) + (when theme-selector + (setf (ps:@ theme-selector value) *current-theme*)) + (when style-selector + (setf (ps:@ style-selector value) *current-style*)))) + (let ((audio-element (or (ps:chain document (get-element-by-id "live-audio")) (ps:chain document (get-element-by-id "persistent-audio"))))) diff --git a/template/front-page-content.ctml b/template/front-page-content.ctml index 0bccad3..4068d12 100644 --- a/template/front-page-content.ctml +++ b/template/front-page-content.ctml @@ -26,6 +26,27 @@
+
+ + +