Add generic streaming interpreter with configurable sources/audio

- Add stream_sexp_generic.py: fully generic sexp interpreter
- Add streaming primitives for video sources and audio analysis
- Add config system for external sources and audio files
- Add templates for reusable scans and macros
- Fix video/audio stream mapping in file output
- Add dynamic source cycling based on sources array length
- Remove old Python effect files (migrated to sexp)
- Update sexp effects to use namespaced primitives

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gilesb
2026-02-02 17:48:04 +00:00
parent d241e2a663
commit 95fcc67dcc
179 changed files with 3935 additions and 8226 deletions

View File

@@ -0,0 +1,25 @@
;; Crossfade with Zoom Transition
;;
;; Macro for transitioning between two frames with a zoom effect.
;; Active frame zooms out while next frame zooms in.
;;
;; Required context:
;; - zoom effect must be loaded
;; - blend effect must be loaded
;;
;; Parameters:
;; active-frame: current frame
;; next-frame: frame to transition to
;; fade-amt: transition progress (0 = all active, 1 = all next)
;;
;; Usage:
;; (include :path "../templates/crossfade-zoom.sexp")
;; ...
;; (crossfade-zoom active-frame next-frame 0.5)
(defmacro crossfade-zoom (active-frame next-frame fade-amt)
(let [active-zoom (+ 1.0 fade-amt)
active-zoomed (zoom active-frame :amount active-zoom)
next-zoom (+ 0.1 (* fade-amt 0.9))
next-zoomed (zoom next-frame :amount next-zoom)]
(blend active-zoomed next-zoomed :opacity fade-amt)))

View File

@@ -0,0 +1,28 @@
;; Oscillating Spin Scan
;;
;; Accumulates rotation angle on each beat, reversing direction
;; periodically for an oscillating effect.
;;
;; Required context:
;; - music: audio analyzer from (streaming:make-audio-analyzer ...)
;;
;; Provides scan: spin
;; Bind with: (bind spin :angle) ;; cumulative rotation angle
;;
;; Behavior:
;; - Rotates 14.4 degrees per beat (completes 360 in 25 beats)
;; - After 20-30 beats, reverses direction
;; - Creates a swinging/oscillating rotation effect
;;
;; Usage:
;; (include :path "../templates/scan-oscillating-spin.sexp")
;;
;; In frame:
;; (rotate frame :angle (bind spin :angle))
(scan spin (streaming:audio-beat music t)
:init {:angle 0 :dir 1 :left 25}
:step (if (> left 0)
(dict :angle (+ angle (* dir 14.4)) :dir dir :left (- left 1))
(dict :angle angle :dir (* dir -1)
:left (+ 20 (mod (streaming:audio-beat-count music t) 11)))))

View File

@@ -0,0 +1,41 @@
;; Beat-Triggered Ripple Drops Scan
;;
;; Creates random ripple drops triggered by audio beats.
;; Each drop has a random center position and duration.
;;
;; Required context:
;; - music: audio analyzer from (streaming:make-audio-analyzer ...)
;; - core primitives loaded
;;
;; Provides scan: ripple-state
;; Bind with: (bind ripple-state :gate) ;; 0 or 1
;; (bind ripple-state :cx) ;; center x (0-1)
;; (bind ripple-state :cy) ;; center y (0-1)
;;
;; Parameters:
;; trigger-chance: probability per beat (default 0.15)
;; min-duration: minimum beats (default 1)
;; max-duration: maximum beats (default 15)
;;
;; Usage:
;; (include :path "../templates/scan-ripple-drops.sexp")
;; ;; Uses default: 15% chance, 1-15 beat duration
;;
;; In frame:
;; (let [rip-gate (bind ripple-state :gate)
;; rip-amp (* rip-gate (core:map-range e 0 1 5 50))]
;; (ripple frame
;; :amplitude rip-amp
;; :center_x (bind ripple-state :cx)
;; :center_y (bind ripple-state :cy)))
(scan ripple-state (streaming:audio-beat music t)
:init {:gate 0 :cx 0.5 :cy 0.5 :left 0}
:step (if (> left 0)
(dict :gate 1 :cx cx :cy cy :left (- left 1))
(if (< (core:rand) 0.15)
(dict :gate 1
:cx (+ 0.2 (* (core:rand) 0.6))
:cy (+ 0.2 (* (core:rand) 0.6))
:left (+ 1 (mod (streaming:audio-beat-count music t) 15)))
(dict :gate 0 :cx 0.5 :cy 0.5 :left 0))))

View File

@@ -0,0 +1,22 @@
;; Standard Effects Bundle
;;
;; Loads commonly-used video effects.
;; Include after primitives are loaded.
;;
;; Effects provided:
;; - rotate: rotation by angle
;; - zoom: scale in/out
;; - blend: alpha blend two frames
;; - ripple: water ripple distortion
;; - invert: color inversion
;; - hue_shift: hue rotation
;;
;; Usage:
;; (include :path "../templates/standard-effects.sexp")
(effect rotate :path "../sexp_effects/effects/rotate.sexp")
(effect zoom :path "../sexp_effects/effects/zoom.sexp")
(effect blend :path "../sexp_effects/effects/blend.sexp")
(effect ripple :path "../sexp_effects/effects/ripple.sexp")
(effect invert :path "../sexp_effects/effects/invert.sexp")
(effect hue_shift :path "../sexp_effects/effects/hue_shift.sexp")

View File

@@ -0,0 +1,14 @@
;; Standard Primitives Bundle
;;
;; Loads all commonly-used primitive libraries.
;; Include this at the top of streaming recipes.
;;
;; Usage:
;; (include :path "../templates/standard-primitives.sexp")
(require-primitives "geometry")
(require-primitives "core")
(require-primitives "image")
(require-primitives "blending")
(require-primitives "color_ops")
(require-primitives "streaming")

View File

@@ -0,0 +1,72 @@
;; stream-process-pair template (streaming-compatible)
;;
;; Macro for processing a video source pair with full effects.
;; Reads source, applies A/B effects (rotate, zoom, invert, hue), blends,
;; and applies pair-level rotation.
;;
;; Required context (must be defined in calling scope):
;; - sources: array of video sources
;; - pair-configs: array of {:dir :rot-a :rot-b :zoom-a :zoom-b} configs
;; - pair-states: array from (bind pairs :states)
;; - now: current time (t)
;; - e: audio energy (0-1)
;;
;; Required effects (must be loaded):
;; - rotate, zoom, invert, hue_shift, blend
;;
;; Usage:
;; (include :path "../templates/stream-process-pair.sexp")
;; ...in frame pipeline...
;; (let [pair-states (bind pairs :states)
;; now t
;; e (streaming:audio-energy music now)]
;; (process-pair 0)) ;; process source at index 0
(require-primitives "core")
(defmacro process-pair (src-idx)
(let [src (nth sources src-idx)
frame (streaming:source-read src now)
cfg (nth pair-configs src-idx)
state (nth pair-states src-idx)
;; Get state values (invert uses countdown > 0)
inv-a-active (if (> (get state :inv-a) 0) 1 0)
inv-b-active (if (> (get state :inv-b) 0) 1 0)
;; Hue is active only when countdown > 0
hue-a-val (if (> (get state :hue-a) 0) (get state :hue-a-val) 0)
hue-b-val (if (> (get state :hue-b) 0) (get state :hue-b-val) 0)
mix-opacity (get state :mix)
pair-rot-angle (* (get state :angle) (get cfg :dir))
;; Get config values for energy-mapped ranges
rot-a-max (get cfg :rot-a)
rot-b-max (get cfg :rot-b)
zoom-a-max (get cfg :zoom-a)
zoom-b-max (get cfg :zoom-b)
;; Energy-driven rotation and zoom
rot-a (core:map-range e 0 1 0 rot-a-max)
rot-b (core:map-range e 0 1 0 rot-b-max)
zoom-a (core:map-range e 0 1 1 zoom-a-max)
zoom-b (core:map-range e 0 1 1 zoom-b-max)
;; Apply effects to clip A
clip-a (-> frame
(rotate :angle rot-a)
(zoom :amount zoom-a)
(invert :amount inv-a-active)
(hue_shift :degrees hue-a-val))
;; Apply effects to clip B
clip-b (-> frame
(rotate :angle rot-b)
(zoom :amount zoom-b)
(invert :amount inv-b-active)
(hue_shift :degrees hue-b-val))
;; Blend A+B
blended (blend clip-a clip-b :opacity mix-opacity)]
;; Apply pair-level rotation
(rotate blended :angle pair-rot-angle)))