From 949d716d9aed289485757a15ce839611deb8772f Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 4 Feb 2026 12:04:00 +0000 Subject: [PATCH] Add dynamic zoom and ripple amplitude to woods recipe - Zoom now driven by audio energy via core:map-range - Ripple amplitude reads from dynamic_params in sexp_to_cuda - Crossfade transition with zoom in/out effect - Move git clone before COPY in Dockerfile for better caching Co-Authored-By: Claude Opus 4.5 --- Dockerfile.gpu | 8 ++++---- recipes/woods-recipe-optimized.sexp | 27 +++++++++++++++++++++------ streaming/sexp_to_cuda.py | 2 +- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/Dockerfile.gpu b/Dockerfile.gpu index 80f896a..967f788 100644 --- a/Dockerfile.gpu +++ b/Dockerfile.gpu @@ -74,12 +74,12 @@ COPY --from=builder /decord-install /usr/local/lib/python3.11/dist-packages/ COPY --from=builder /tmp/decord/build/libdecord.so /usr/local/lib/ RUN ldconfig -# Copy application -COPY . . - -# Clone effects repo +# Clone effects repo (before COPY so it gets cached) RUN git clone https://git.rose-ash.com/art-dag/effects.git /app/artdag-effects +# Copy application (this invalidates cache for any code change) +COPY . . + # Create cache directory RUN mkdir -p /data/cache diff --git a/recipes/woods-recipe-optimized.sexp b/recipes/woods-recipe-optimized.sexp index fd853e6..ebfcbb3 100644 --- a/recipes/woods-recipe-optimized.sexp +++ b/recipes/woods-recipe-optimized.sexp @@ -125,8 +125,8 @@ dir (get cfg :dir) rot-max-a (get cfg :rot-a) rot-max-b (get cfg :rot-b) - zoom-a (get cfg :zoom-a) - zoom-b (get cfg :zoom-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) @@ -140,6 +140,10 @@ 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! effects-a [{:op "zoom" :amount zoom-a} @@ -177,10 +181,20 @@ ;; Process active pair with fused pipeline active-frame (process-pair-fast active) - ;; Crossfade during transition + ;; 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)] - (blending:blend-images active-frame next-frame fade-amt)) + (let [next-frame (process-pair-fast next-idx) + ;; Active zooms out as it fades + active-zoom (+ 1.0 fade-amt) + active-zoomed (streaming_gpu: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_gpu:fused-pipeline next-frame + [{:op "zoom" :amount next-zoom}])] + (blending:blend-images active-zoomed next-zoomed fade-amt)) active-frame) ;; Final effects pipeline (fused!) @@ -198,4 +212,5 @@ ;; Apply final fused pipeline (streaming_gpu:fused-pipeline result final-effects :rotate_angle spin-angle - :ripple_phase (* now 5))))) + :ripple_phase (* now 5) + :ripple_amplitude rip-amp)))) diff --git a/streaming/sexp_to_cuda.py b/streaming/sexp_to_cuda.py index 28719d4..fc3afde 100644 --- a/streaming/sexp_to_cuda.py +++ b/streaming/sexp_to_cuda.py @@ -300,7 +300,7 @@ def _build_params(effects: List[dict], dynamic_params: dict) -> cp.ndarray: elif op == 'zoom': params.append(float(dynamic_params.get('zoom_amount', effect.get('amount', 1.0)))) elif op == 'ripple': - params.append(float(effect.get('amplitude', 10))) + params.append(float(dynamic_params.get('ripple_amplitude', effect.get('amplitude', 10)))) params.append(float(effect.get('frequency', 8))) params.append(float(effect.get('decay', 2))) params.append(float(dynamic_params.get('ripple_phase', effect.get('phase', 0))))