I have vague memories of a visualizer that I will likely never encounter without looking for it again. It was a waveform display but it had these rectangles that would ride them like boats, not really like surfing. Now, you might be wondering why I’m doing this when my library creates, at best, video.
Well, I’m not even going to recreate that much of it, doing the discrete waveform handling is always a massive pain, so instead we’ll cheese it with the combined magic of img-genner, and the magic of limits.
(require :img-genner) #|------------------------------------------------------------------------------ | The idea here is that the rectangles ripple along a sine wave, rotating with | it as it moves. | | To determine the angle the derivative of the wave must be known at the | center of the rectangles, and that must be turned into an angle |----------------------------------------------------------------------------|# (defvar *rectangles* (loop for i from 0 below 24 collect(make-instance 'img-genner:rectangle :width 10 :height 10 :origin (img-genner:point (* 30.0 i) 240.0))))
With that in mind we can implement our derivation code, and the slope to angle conversion function. Normally, I wouldn’t recommend using defconstant unless you’re really sure that the constant won’t change.
(defconstant +epsilon+ 0.0001) (defun derive-at-point(function x) (/ (- (funcall function (+ x +epsilon+)) (funcall function x)) +epsilon+)) (defun slope-to-angle(rise-over-run) (atan rise-over-run))
Then we have to write the wave and coordinate conversion functions. We need the coordinate conversions because trigonometric functions aren’t very great over the 640.
(defun wave-1(x) (+ (sin (* 2.0 x)) (sin (/ x 3)) (sin x))) (defun xcoord(x time) (+ (/ x 10.0) time))
Then we need to be able to update each rectangle for a given time and with a given wave function.
#| | Move the rectangles to match the wave's height at that point |# (defun match-wave-height(func time rectangle) (let* ((origin (slot-value rectangle 'img-genner:origin)) (x (xcoord (aref origin 0) time))) (setf (aref origin 1) (+ 240 (* (funcall func x) 10))) ) ) #| | This does the same for the angles |# (defun match-wave-angle(func time rectangle) (let* ((origin (slot-value rectangle 'img-genner:origin)) (x (xcoord (aref origin 0) time)) (dy (derive-at-point func x))) (setf (slot-value rectangle 'img-genner:rotation) (slope-to-angle dy)) ) ) ; And this runs both (defun update-rectangles(func time) (loop for i in *rectangles* do(match-wave-height func time i) do(match-wave-angle func time i)) )
Then we need to handle the streams and the drawing of it all.
(defparameter *image* (img-genner:make-image 640 480)) (defun draw-rectangles() (loop for i in *rectangles* do(img-genner:fill-shape i *image* (img-genner:static-color-stroker (img-genner:rgb 255 0 0))))) (defun draw-wave(func time) (loop for ox from 0 below 640 for x = (+ time (/ ox 10.0)) for y = (- 480 (+ 240 (* 10 (funcall func x)))) ; This is a cheap way to do a thick line poorly do(loop for off from 0 below 2 do(setf (aref *image* (floor (+ y off)) ox 2) 255)) )) (loop for time from 0.0 by (/ 1.0 25.0) for frame from 0 repeat 1000 do(update-rectangles #'wave-1 time) do(reset-image *image*) do(draw-rectangles) do(draw-wave #'wave-1 time) do(img-genner:save-image *image* (uiop:process-info-input *ffmpeg-writer*)) do(print frame) ) (uiop:close-streams *ffmpeg-writer*) (uiop:wait-process *ffmpeg-writer*)
And with that we get the above. There’s a lot of room for improvement
Leave a Reply
Only people in my network can comment.