Page:
patterns-earthbound-HDMA-Scanline-Shift
Pages
Home
nodes-scriptable-conditioning
nodes-scriptable-empty-latent
nodes-scriptable-image
nodes-scriptable-latent
nodes-scriptable-mask
nodes-scriptable-noise
patterns-Overview
patterns-animation-Mandelbrot-Zoom
patterns-animation-Overview
patterns-animation-Perlin-Noise-Fractal
patterns-animation-Plasma-Effect
patterns-animation-Recursive-Pattern
patterns-animation-Sine-Wave-Interference
patterns-earthbound-Color-Cycling-Gradient
patterns-earthbound-Earthbound-Warping-Distortion
patterns-earthbound-Fractal-Brownian-Motion
patterns-earthbound-HDMA-Scanline-Loop
patterns-earthbound-HDMA-Scanline-Shift
patterns-earthbound-Kaleidoscope-Loop
patterns-earthbound-Moire-Interference-Loop
patterns-earthbound-Moire-Interference
patterns-earthbound-Multi-Layer-Plasma
patterns-earthbound-Overview
patterns-earthbound-Radial-Breathing-Loop
patterns-earthbound-Rotating-Fractal-Loop
patterns-earthbound-Seamless-Plasma-Loop
patterns-mathematical-foundations
patterns-psychedelic-Fractal-Noise-Gradient
patterns-psychedelic-Overview
patterns-psychedelic-Plasma-Color-Cycling
patterns-psychedelic-Scanline-Shift
scriptable-conditioning
scriptable-empty-latent
scriptable-image
scriptable-latent
scriptable-mask
scriptable-noise
No results
2
patterns-earthbound-HDMA-Scanline-Shift
Balazs Horvath edited this page 2026-04-18 11:13:11 +02:00
HDMA Scanline Shift
Inspired by EarthBound's HDMA effects, this pattern shifts each scanline horizontally based on sin wave modulation.
Mathematical Formula
\text{shift}_{\text{amount}} = \sin(y \cdot k + t \cdot \omega) \cdot \text{max}_{\text{shift}}
x' = (x - \text{shift}_{\text{amount}}) \bmod \text{width}
Where:
yis the vertical position (0 to height)kis the spatial frequency (vertical)tis time (frame number)\omegais the temporal frequency\text{max}_{\text{shift}}is the maximum pixel shift
How It Works
HDMA (Horizontal Direct Memory Access) allows mid-frame register updates on the SNES. This pattern simulates that effect by:
flowchart TD
A[Create coordinate grid] --> B[Calculate shift amount]
B --> C[Apply shift using roll]
C --> D[Color with phase-shifted sine waves]
D --> E[Output animation]
B --> B1[sin Y * k + t * omega]
B --> B2[max_shift multiplier]
C --> C1[torch.roll]
C --> C2[Horizontal dimension]
D --> D1[120 phase shifts]
D --> D2[RGB channels]
Implementation
import torch
width, height = 512, 512
frames = []
for t in range(30):
# Create coordinate grids
x = torch.linspace(0, 2*torch.pi, width)
y = torch.linspace(0, 2*torch.pi, height)
X, Y = torch.meshgrid(x, y, indexing='ij')
# HDMA-style scanline shift
# Each row is shifted based on its vertical position and time
shift_amount = torch.sin(Y * 2 + t/5) * 20 # Shift up to 20 pixels
# Apply shift using roll
shifted = torch.roll(X, shift_amount.int(), dims=1)
# Color with pleasing Earthbound-style palette
r = torch.sin(shifted + t/10)
g = torch.sin(shifted + t/10 + 2*torch.pi/3)
b = torch.sin(shifted + t/10 + 4*torch.pi/3)
rgb = torch.stack([r, g, b], dim=-1)
rgb = ((rgb + 1) / 2).clamp(0, 1)
frames.append(rgb)
output_image = torch.stack(frames, dim=0) # Shape: [30, H, W, 3]
Line-by-Line Explanation
x = torch.linspace(0, 2*torch.pi, width)
y = torch.linspace(0, 2*torch.pi, height)
Creates coordinate arrays ranging from 0 to 2π across the image dimensions.
X, Y = torch.meshgrid(x, y, indexing='ij')
Creates 2D coordinate grids. indexing='ij' uses matrix indexing (row, column).
shift_amount = torch.sin(Y * 2 + t/5) * 20
Calculates the horizontal shift for each row:
Y * 2: Spatial frequency - how quickly the shift changes verticallyt/5: Temporal frequency - how fast the animation plays* 20: Maximum shift in pixels
shifted = torch.roll(X, shift_amount.int(), dims=1)
Applies the shift by rolling the X coordinate array horizontally. .int() converts to integer pixels.
r = torch.sin(shifted + t/10)
g = torch.sin(shifted + t/10 + 2*torch.pi/3)
b = torch.sin(shifted + t/10 + 4*torch.pi/3)
Color cycling with 120° phase shifts between channels for smooth color transitions.
Customization
Change Shift Amount
shift_amount = torch.sin(Y * 2 + t/5) * 40 # Larger shift
Change Animation Speed
shift_amount = torch.sin(Y * 2 + t/10) * 20 # Slower
Change Spatial Frequency
shift_amount = torch.sin(Y * 4 + t/5) * 20 # More frequent vertical variation
Change Color Palette
# Warm sunset
r = shifted * 0.8 + 0.2
g = shifted * 0.4 + 0.3
b = shifted * 0.2 + 0.6
Performance Notes
torch.roll()is efficient for circular shifts- Precompute coordinate grids if generating many frames
- Use integer shifts for pixel-perfect results