;; Woods Recipe - OPTIMIZED VERSION ;; ;; Uses fused-pipeline for GPU acceleration when available, ;; falls back to individual primitives on CPU. ;; ;; Key optimizations: ;; 1. Uses streaming_gpu primitives with fast CUDA kernels ;; 2. Uses fused-pipeline to batch effects into single kernel passes ;; 3. GPU persistence - frames stay on GPU throughout pipeline (stream "woods-lowres" :fps 30 :width 640 :height 360 :seed 42 ;; Load standard primitives (includes proper asset resolution) ;; Auto-selects GPU versions when available, falls back to CPU (include :name "tpl-standard-primitives") ;; === SOURCES (using streaming: which has proper asset resolution) === (def sources [ (streaming:make-video-source "woods-1" 30) (streaming:make-video-source "woods-2" 30) (streaming:make-video-source "woods-3" 30) (streaming:make-video-source "woods-4" 30) (streaming:make-video-source "woods-5" 30) (streaming:make-video-source "woods-6" 30) (streaming:make-video-source "woods-7" 30) (streaming:make-video-source "woods-8" 30) ]) ;; Per-pair config (def pair-configs [ {:dir -1 :rot-a 45 :rot-b -45 :zoom-a 1.5 :zoom-b 0.5} {:dir 1 :rot-a 45 :rot-b -45 :zoom-a 1.5 :zoom-b 0.5} {:dir 1 :rot-a 45 :rot-b -45 :zoom-a 1.5 :zoom-b 0.5} {:dir -1 :rot-a -45 :rot-b 45 :zoom-a 0.5 :zoom-b 1.5} {:dir -1 :rot-a 45 :rot-b -45 :zoom-a 1.5 :zoom-b 0.5} {:dir 1 :rot-a 30 :rot-b -30 :zoom-a 1.3 :zoom-b 0.7} {:dir -1 :rot-a -45 :rot-b 45 :zoom-a 0.5 :zoom-b 1.5} {:dir 1 :rot-a 45 :rot-b -45 :zoom-a 1.5 :zoom-b 0.5} ]) ;; Audio (def music (streaming:make-audio-analyzer "woods-audio")) (audio-playback "woods-audio") ;; === SCANS === ;; Cycle state (scan cycle (streaming:audio-beat music t) :init {:active 0 :beat 0 :clen 16} :step (if (< (+ beat 1) clen) (dict :active active :beat (+ beat 1) :clen clen) (dict :active (mod (+ active 1) (len sources)) :beat 0 :clen (+ 8 (mod (* (streaming:audio-beat-count music t) 7) 17))))) ;; Spin scan (scan spin (streaming:audio-beat music t) :init {:angle 0 :dir 1 :speed 2} :step (let [new-dir (if (< (core:rand) 0.05) (* dir -1) dir) new-speed (if (< (core:rand) 0.1) (+ 1 (core:rand-int 1 4)) speed)] (dict :angle (+ angle (* new-dir new-speed)) :dir new-dir :speed new-speed))) ;; Ripple scan - raindrop style, all params randomized ;; Higher freq = bigger gaps between waves (formula is dist/freq) (scan ripple-state (streaming:audio-beat music t) :init {:gate 0 :cx 320 :cy 180 :freq 20 :decay 6 :amp-mult 1.0} :step (let [new-gate (if (< (core:rand) 0.2) (+ 2 (core:rand-int 0 4)) (core:max 0 (- gate 1))) triggered (> new-gate gate) new-cx (if triggered (core:rand-int 50 590) cx) new-cy (if triggered (core:rand-int 50 310) cy) new-freq (if triggered (+ 15 (core:rand-int 0 20)) freq) new-decay (if triggered (+ 5 (core:rand-int 0 4)) decay) new-amp-mult (if triggered (+ 0.8 (* (core:rand) 1.2)) amp-mult)] (dict :gate new-gate :cx new-cx :cy new-cy :freq new-freq :decay new-decay :amp-mult new-amp-mult))) ;; Pair states (scan pairs (streaming:audio-beat music t) :init {:states (map (core:range (len sources)) (lambda (_) {:inv-a 0 :inv-b 0 :hue-a 0 :hue-b 0 :hue-a-val 0 :hue-b-val 0 :mix 0.5 :mix-rem 5 :angle 0 :rot-beat 0 :rot-clen 25}))} :step (dict :states (map states (lambda (p) (let [new-inv-a (if (< (core:rand) 0.1) (+ 1 (core:rand-int 1 4)) (core:max 0 (- (get p :inv-a) 1))) new-inv-b (if (< (core:rand) 0.1) (+ 1 (core:rand-int 1 4)) (core:max 0 (- (get p :inv-b) 1))) old-hue-a (get p :hue-a) old-hue-b (get p :hue-b) new-hue-a (if (< (core:rand) 0.1) (+ 1 (core:rand-int 1 4)) (core:max 0 (- old-hue-a 1))) new-hue-b (if (< (core:rand) 0.1) (+ 1 (core:rand-int 1 4)) (core:max 0 (- old-hue-b 1))) new-hue-a-val (if (> new-hue-a old-hue-a) (+ 30 (* (core:rand) 300)) (get p :hue-a-val)) new-hue-b-val (if (> new-hue-b old-hue-b) (+ 30 (* (core:rand) 300)) (get p :hue-b-val)) mix-rem (get p :mix-rem) old-mix (get p :mix) new-mix-rem (if (> mix-rem 0) (- mix-rem 1) (+ 1 (core:rand-int 1 10))) new-mix (if (> mix-rem 0) old-mix (* (core:rand-int 0 2) 0.5)) rot-beat (get p :rot-beat) rot-clen (get p :rot-clen) old-angle (get p :angle) new-rot-beat (if (< (+ rot-beat 1) rot-clen) (+ rot-beat 1) 0) new-rot-clen (if (< (+ rot-beat 1) rot-clen) rot-clen (+ 20 (core:rand-int 0 10))) new-angle (+ old-angle (/ 360 rot-clen))] (dict :inv-a new-inv-a :inv-b new-inv-b :hue-a new-hue-a :hue-b new-hue-b :hue-a-val new-hue-a-val :hue-b-val new-hue-b-val :mix new-mix :mix-rem new-mix-rem :angle new-angle :rot-beat new-rot-beat :rot-clen new-rot-clen)))))) ;; === OPTIMIZED PROCESS-PAIR MACRO === ;; Uses fused-pipeline to batch rotate+hue+invert into single kernel (defmacro process-pair-fast (idx) (let [;; Get sources for this pair (with safe modulo indexing) num-sources (len sources) src-a (nth sources (mod (* idx 2) num-sources)) src-b (nth sources (mod (+ (* idx 2) 1) num-sources)) cfg (nth pair-configs idx) pstate (nth (bind pairs :states) idx) ;; Read frames (GPU decode, stays on GPU) frame-a (streaming:source-read src-a t) frame-b (streaming:source-read src-b t) ;; Get state values dir (get cfg :dir) rot-max-a (get cfg :rot-a) rot-max-b (get cfg :rot-b) zoom-max-a (get cfg :zoom-a) zoom-max-b (get cfg :zoom-b) pair-angle (get pstate :angle) inv-a-on (> (get pstate :inv-a) 0) inv-b-on (> (get pstate :inv-b) 0) hue-a-on (> (get pstate :hue-a) 0) hue-b-on (> (get pstate :hue-b) 0) hue-a-val (get pstate :hue-a-val) hue-b-val (get pstate :hue-b-val) mix-ratio (get pstate :mix) ;; Calculate rotation angles angle-a (* dir pair-angle rot-max-a 0.01) angle-b (* dir pair-angle rot-max-b 0.01) ;; Energy-driven zoom (maps audio energy 0-1 to 1-max) zoom-a (core:map-range e 0 1 1 zoom-max-a) zoom-b (core:map-range e 0 1 1 zoom-max-b) ;; Define effect pipelines for each source ;; These get compiled to single CUDA kernels! ;; First resize to target resolution, then apply effects effects-a [{:op "resize" :width 640 :height 360} {:op "zoom" :amount zoom-a} {:op "rotate" :angle angle-a} {:op "hue_shift" :degrees (if hue-a-on hue-a-val 0)} {:op "invert" :amount (if inv-a-on 1 0)}] effects-b [{:op "resize" :width 640 :height 360} {:op "zoom" :amount zoom-b} {:op "rotate" :angle angle-b} {:op "hue_shift" :degrees (if hue-b-on hue-b-val 0)} {:op "invert" :amount (if inv-b-on 1 0)}] ;; Apply fused pipelines (single kernel per source!) processed-a (streaming:fused-pipeline frame-a effects-a) processed-b (streaming:fused-pipeline frame-b effects-b)] ;; Blend the two processed frames (blending:blend-images processed-a processed-b mix-ratio))) ;; === FRAME PIPELINE === (frame (let [now t e (streaming:audio-energy music now) ;; Get cycle state active (bind cycle :active) beat-pos (bind cycle :beat) clen (bind cycle :clen) ;; Transition logic phase3 (* beat-pos 3) fading (and (>= phase3 (* clen 2)) (< phase3 (* clen 3))) fade-amt (if fading (/ (- phase3 (* clen 2)) clen) 0) next-idx (mod (+ active 1) (len sources)) ;; Process active pair with fused pipeline active-frame (process-pair-fast active) ;; Crossfade with zoom during transition ;; Old pair: zooms out (1.0 -> 2.0) and fades out ;; New pair: starts small (0.1), zooms in (-> 1.0) and fades in result (if fading (let [next-frame (process-pair-fast next-idx) ;; Active zooms out as it fades active-zoom (+ 1.0 fade-amt) active-zoomed (streaming:fused-pipeline active-frame [{:op "zoom" :amount active-zoom}]) ;; Next starts small and zooms in next-zoom (+ 0.1 (* fade-amt 0.9)) next-zoomed (streaming:fused-pipeline next-frame [{:op "zoom" :amount next-zoom}])] (blending:blend-images active-zoomed next-zoomed fade-amt)) active-frame) ;; Final effects pipeline (fused!) spin-angle (bind spin :angle) ;; Ripple params - all randomized per ripple trigger rip-gate (bind ripple-state :gate) rip-amp-mult (bind ripple-state :amp-mult) rip-amp (* rip-gate rip-amp-mult (core:map-range e 0 1 50 200)) rip-cx (bind ripple-state :cx) rip-cy (bind ripple-state :cy) rip-freq (bind ripple-state :freq) rip-decay (bind ripple-state :decay) ;; Fused final effects final-effects [{:op "rotate" :angle spin-angle} {:op "ripple" :amplitude rip-amp :frequency rip-freq :decay rip-decay :phase (* now 5) :center_x rip-cx :center_y rip-cy}]] ;; Apply final fused pipeline (streaming:fused-pipeline result final-effects :rotate_angle spin-angle :ripple_phase (* now 5) :ripple_amplitude rip-amp))))