;; Composable ASCII Art with Per-Zone Expression-Driven Effects ;; Requires ascii primitive library for the ascii-fx-zone primitive (require-primitives "ascii") ;; Two modes of operation: ;; ;; 1. EXPRESSION MODE: Use zone-* variables in expression parameters ;; Zone variables available: ;; zone-row, zone-col: Grid position (integers) ;; zone-row-norm, zone-col-norm: Normalized position (0-1) ;; zone-lum: Cell luminance (0-1) ;; zone-sat: Cell saturation (0-1) ;; zone-hue: Cell hue (0-360) ;; zone-r, zone-g, zone-b: RGB components (0-1) ;; ;; Example: ;; (ascii-fx-zone frame ;; :cols 80 ;; :char_hue (* zone-lum 180) ;; :char_rotation (* zone-col-norm 30)) ;; ;; 2. CELL EFFECT MODE: Pass a lambda to apply arbitrary effects per-cell ;; The lambda receives (cell-image zone-dict) and returns modified cell. ;; Zone dict contains: row, col, row-norm, col-norm, lum, sat, hue, r, g, b, ;; char, color, cell_size, plus any bound analysis values. ;; ;; Any loaded sexp effect can be called on cells - each cell is just a small frame: ;; (blur cell radius) - Gaussian blur ;; (rotate cell angle) - Rotate by angle degrees ;; (brightness cell factor) - Adjust brightness ;; (contrast cell factor) - Adjust contrast ;; (saturation cell factor) - Adjust saturation ;; (hue_shift cell degrees) - Shift hue ;; (rgb_split cell offset_x offset_y) - RGB channel split ;; (invert cell) - Invert colors ;; (pixelate cell block_size) - Pixelate ;; (wave cell amplitude freq) - Wave distortion ;; ... and any other loaded effect ;; ;; Example: ;; (ascii-fx-zone frame ;; :cols 60 ;; :cell_effect (lambda [cell zone] ;; (blur (rotate cell (* (get zone "energy") 45)) ;; (if (> (get zone "lum") 0.5) 3 0)))) (define-effect ascii_fx_zone :params ( (cols :type int :default 80 :range [20 200] :desc "Number of character columns") (char_size :type int :default nil :range [4 32] :desc "Character cell size in pixels (overrides cols if set)") (alphabet :type string :default "standard" :desc "Character set: standard, blocks, simple, digits, or custom string") (color_mode :type string :default "color" :desc "Color mode: color, mono, invert, or any color name/hex") (background :type string :default "black" :desc "Background color name or hex value") (contrast :type float :default 1.5 :range [0.5 3.0] :desc "Contrast for character selection") (char_hue :type any :default nil :desc "Hue shift expression (evaluated per-zone with zone-* vars)") (char_saturation :type any :default nil :desc "Saturation multiplier expression (1.0 = unchanged)") (char_brightness :type any :default nil :desc "Brightness multiplier expression (1.0 = unchanged)") (char_scale :type any :default nil :desc "Character scale expression (1.0 = normal size)") (char_rotation :type any :default nil :desc "Character rotation expression (degrees)") (char_jitter :type any :default nil :desc "Position jitter expression (pixels)") (cell_effect :type any :default nil :desc "Lambda (cell zone) -> cell for arbitrary per-cell effects") ;; Convenience params for staged recipes (avoids compile-time expression issues) (energy :type float :default nil :desc "Energy multiplier (0-1) from audio analysis bind") (rotation_scale :type float :default 0 :desc "Max rotation at top-right when energy=1 (degrees)") ) ;; The ascii-fx-zone special form handles expression params ;; If energy + rotation_scale provided, it builds: energy * scale * position_factor ;; where position_factor = 0 at bottom-left, 3 at top-right ;; If cell_effect provided, each character is rendered to a cell image, ;; passed to the lambda, and the result composited back (ascii-fx-zone frame :cols cols :char_size char_size :alphabet alphabet :color_mode color_mode :background background :contrast contrast :char_hue char_hue :char_saturation char_saturation :char_brightness char_brightness :char_scale char_scale :char_rotation char_rotation :char_jitter char_jitter :cell_effect cell_effect :energy energy :rotation_scale rotation_scale))