# /// script # requires-python = ">=3.10" # dependencies = ["numpy", "opencv-python"] # /// """ @effect wave @version 1.0.0 @author artdag @description Sine wave displacement distortion. Creates wavy, liquid-like warping. Great for psychedelic and underwater effects synced to music. @param amplitude float @range 0 100 @default 10 Wave height in pixels. Bind to bass for punchy distortion. @param wavelength float @range 10 500 @default 50 Distance between wave peaks in pixels. @param speed float @range 0 10 @default 1 Wave animation speed. Uses state to track phase over time. @param direction string @enum horizontal vertical both @default horizontal Wave direction: - horizontal: waves move left-right - vertical: waves move up-down - both: waves in both directions @state phase float Tracks wave animation phase across frames. @example (effect wave :amplitude 20 :wavelength 100) @example ;; Bass-reactive waves (effect wave :amplitude (bind bass :range [0 50] :transform sqrt)) """ import numpy as np import cv2 def process_frame(frame: np.ndarray, params: dict, state: dict) -> tuple: """ Apply wave distortion to a video frame. Args: frame: Input frame as numpy array (H, W, 3) RGB uint8 params: Effect parameters - amplitude: wave height in pixels (default 10) - wavelength: distance between peaks (default 50) - speed: animation speed (default 1) - direction: horizontal/vertical/both (default horizontal) state: Persistent state dict - phase: current wave phase Returns: Tuple of (processed_frame, new_state) """ amplitude = params.get("amplitude", 10) wavelength = params.get("wavelength", 50) speed = params.get("speed", 1) direction = params.get("direction", "horizontal") if state is None: state = {} if amplitude == 0: return frame, state h, w = frame.shape[:2] # Update phase for animation phase = state.get("phase", 0) phase += speed * 0.1 state["phase"] = phase # Create coordinate maps map_x = np.tile(np.arange(w, dtype=np.float32), (h, 1)) map_y = np.tile(np.arange(h, dtype=np.float32).reshape(-1, 1), (1, w)) if direction in ("horizontal", "both"): # Horizontal waves: displace X based on Y wave = np.sin(2 * np.pi * map_y / wavelength + phase) * amplitude map_x = map_x + wave if direction in ("vertical", "both"): # Vertical waves: displace Y based on X wave = np.sin(2 * np.pi * map_x / wavelength + phase) * amplitude map_y = map_y + wave # Apply distortion result = cv2.remap( frame, map_x, map_y, cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT ) return result, state