asteroid/spectrum-analyzer.lisp

97 lines
3.9 KiB
Common Lisp

(in-package #:asteroid)
;;; Spectrum Analyzer - Parenscript Implementation
;;; Generates JavaScript for real-time audio visualization
(defun generate-spectrum-analyzer-js ()
"Generate JavaScript code for the spectrum analyzer using Parenscript"
(ps:ps
(defvar *audio-context* nil)
(defvar *analyser* nil)
(defvar *canvas* nil)
(defvar *canvas-ctx* nil)
(defvar *animation-id* nil)
(defun init-spectrum-analyzer ()
"Initialize the spectrum analyzer"
(let ((audio-element (chain document (get-element-by-id "live-audio")))
(canvas-element (chain document (get-element-by-id "spectrum-canvas"))))
(when (and audio-element canvas-element)
;; Create Audio Context
(setf *audio-context* (new (or (@ window -audio-context)
(@ window -webkit-audio-context))))
;; Create Analyser Node
(setf *analyser* (chain *audio-context* (create-analyser)))
(setf (@ *analyser* fft-size) 256)
(setf (@ *analyser* smoothing-time-constant) 0.8)
;; Connect audio source to analyser
(let ((source (chain *audio-context* (create-media-element-source audio-element))))
(chain source (connect *analyser*))
(chain *analyser* (connect (@ *audio-context* destination))))
;; Setup canvas
(setf *canvas* canvas-element)
(setf *canvas-ctx* (chain *canvas* (get-context "2d")))
;; Start visualization
(draw-spectrum))))
(defun draw-spectrum ()
"Draw the spectrum analyzer visualization"
(setf *animation-id* (request-animation-frame draw-spectrum))
(let* ((buffer-length (@ *analyser* frequency-bin-count))
(data-array (new (-uint8-array buffer-length)))
(width (@ *canvas* width))
(height (@ *canvas* height))
(bar-width (/ width buffer-length))
(bar-height 0)
(x 0))
(chain *analyser* (get-byte-frequency-data data-array))
;; Clear canvas with fade effect
(setf (@ *canvas-ctx* fill-style) "rgba(0, 0, 0, 0.2)")
(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 (chain *canvas-ctx*
(create-linear-gradient 0 (- height bar-height) 0 height))))
(chain gradient (add-color-stop 0 "#00ff00"))
(chain gradient (add-color-stop 0.5 "#00aa00"))
(chain gradient (add-color-stop 1 "#005500"))
(setf (@ *canvas-ctx* fill-style) gradient)
(chain *canvas-ctx* (fill-rect x (- height bar-height) bar-width bar-height))
(incf x bar-width)))))
(defun stop-spectrum-analyzer ()
"Stop the spectrum analyzer"
(when *animation-id*
(cancel-animation-frame *animation-id*)
(setf *animation-id* nil)))
;; Initialize when audio starts playing
(chain document (add-event-listener "DOMContentLoaded"
(lambda ()
(let ((audio-element (chain document (get-element-by-id "live-audio"))))
(when audio-element
(chain audio-element (add-event-listener "play" init-spectrum-analyzer))
(chain audio-element (add-event-listener "pause" stop-spectrum-analyzer)))))))))
(defun write-spectrum-analyzer-js ()
"Write the generated JavaScript to a file"
(with-open-file (stream (asdf:system-relative-pathname :asteroid "static/js/spectrum-analyzer.js")
:direction :output
:if-exists :supersede
:if-does-not-exist :create)
(write-string (generate-spectrum-analyzer-js) stream)))