How to Create Seamless Loop Animations for X (Twitter)
Want to create that "hypnotic" effect on social media where a video seems to play forever? The secret lies in making the first and last frames identical. In this post, I'll show you how to achieve a perfect seamless loop using Processing and some clever math.
Animating Sine Waves with Triple Perlin Noise
This creative coding piece uses a simple sine curve base, layered with Perlin noise for organic movement. To give the wave its complex, fluid feel, I combined three different noise values:
float lNoise = noise(lineRatio, radRatio);
float fNoise = noise(sin(PI * frmRatio));
float eNoise = noise(sin(PI * easeRatio));
The Logic Behind the Loop
The key to the seamless loop is the code sin(PI * frmRatio). Since sin(0) and sin(π) both equal zero, the noise value starts at a specific point, evolves, and eventually returns to exactly where it began. This ensures the Start value = End value, creating a gapless transition when replayed.
Source Code: "The Tide Is High" (Processing)
This code is available under the GPL. I would be honored to see any variations you create based on this logic!
Note: This script doesn't render to the screen in real-time. Instead, it exports each frame to a directory so you can compile them into a high-quality video file. (And no, it's not a candlestick chart! XD)
/**
*
* The Tide Is High.
*
* Processing 3.2.1
* @author @deconbatch
* @version 0.1
* created 0.1 2019.12.29
*/
void setup() {
size(720, 380);
colorMode(HSB, 360, 100, 100, 100);
blendMode(SCREEN);
rectMode(CENTER);
noStroke();
noLoop();
}
void draw() {
int frmMax = 24 * 12; // for 24fps x 12s animation
int lineMax = 50;
int speed = 3;
float rectDiv = 0.15;
float rectWid = 10.0;
float baseHue = random(360.0);
translate(0.0, height * 0.5);
for (int frmCnt = 0; frmCnt < frmMax; ++frmCnt) {
float frmRatio = map(frmCnt, 0, frmMax, 0.0, 1.0);
background(baseHue, 100.0, 40.0, 100.0);
for (int lineCnt = 0; lineCnt < lineMax; lineCnt++) {
float lineRatio = map(lineCnt, 0, lineMax, 0.0, 1.0);
float easeRatio = easeInOutCubic((frmRatio + lineRatio) % 1.0); // add a difference to each line.
for (float rad = 0; rad < TWO_PI; rad += rectDiv) {
float radRatio = map(rad, 0.0, TWO_PI, 0.0, 1.0);
float lNoise = noise(lineRatio, radRatio);
float fNoise = noise(sin(PI * frmRatio));
float eNoise = noise(sin(PI * easeRatio));
float rectX = width * radRatio;
float rectW = (0.2 + lineRatio) * rectWid;
float waveShape = 0.4 * height * sin(rad + TWO_PI * speed * frmRatio + PI * lNoise);
float rectY = waveShape * sin(PI * radRatio) * eNoise;
float rectH = 20.0 + abs(waveShape) * fNoise;
float rectHue = baseHue + 360.0 + sin(TWO_PI * (radRatio + easeRatio)) * 30.0;
fill(rectHue % 360.0, 90.0, 10.0, 100.0);
rect(rectX, rectY, rectW, rectH);
}
}
saveFrame("frames/" + String.format("%04d", frmCnt) + ".png");
}
exit();
}
/**
* easeInOutCubic easing function.
* @param t 0.0 - 1.0 : linear value.
* @return float 0.0 - 1.0 : eased value.
*/
private float easeInOutCubic(float t) {
t *= 2.0;
if (t < 1.0) {
return pow(t, 3) / 2.0;
}
t -= 2.0;
return (pow(t, 3) + 2.0) / 2.0;
}
/*
Copyright (C) 2019- deconbatch
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
Gallery: Visual Variations



