Random and noise
Seeded randomness and smooth noise: motion that looks organic in the editor and renders identically on every machine.
Every random source is SEEDED: the numbers derive from the document seed, the script and the frame, so scrubbing, replaying and farm rendering all see identical values. Nothing here can make a render differ between machines.
random
random(): number
A seeded pseudo-random number in [0, 1), deterministic per (document seed, script, frame). Math.random is the same stream.
Determinism: seeded.
Worked example: Deterministic scatter
The scene. One orange square resting on a dark 640 by 360 stage.
- The comp Drift: 640 by 360 at 25 fps, 100 frames (4 seconds).
- Square: a solid layer, at [320, 180], 60 by 60, filled f2a833.
The script. Add a project script named Scatter and paste:
export const outputs = ["layer_1:transform.position"];
export function frame() {
const jx = (random() - 0.5) * 200;
const jy = (random() - 0.5) * 120;
return [value[0] + jx, value[1] + jy];
}
Line by line:
- Lines 4-5: random() answers a number in [0, 1) - a fresh one per call, but the SEQUENCE is seeded per (document, script, frame). Frame 7 always produces the same two numbers, on your machine and on the render farm alike.
- Line 6: Centred jitter: up to 100 pixels sideways and 60 vertically, snapping to a new spot each frame.
What you see. The square teleports to a new nearby spot every frame - jittery and chaotic to watch, yet identical on every play, scrub and render.
gaussRandom
gaussRandom(): number
A seeded standard-normal random number (Box-Muller over random()).
Determinism: seeded.
Worked example: Bell-curved jitter
The scene. One orange square resting on a dark 640 by 360 stage.
- The comp Drift: 640 by 360 at 25 fps, 100 frames (4 seconds).
- Square: a solid layer, at [320, 180], 60 by 60, filled f2a833.
The script. Add a project script named Nervous and paste:
export const outputs = ["layer_1:transform.position"];
export function frame() {
return [value[0] + gaussRandom() * 12, value[1] + gaussRandom() * 12];
}
Line by line:
- Line 4: gaussRandom() draws from a standard bell curve: usually near zero, occasionally further out. Multiplied by 12, the Square mostly trembles close to home with the odd larger twitch - more natural than the flat spread of random().
What you see. The square trembles around its resting spot - small nervous movements most frames, an occasional larger twitch, identical on every playback.
seedRandom
seedRandom(offset: number, timeless?: boolean): void
Reseed the random()/gaussRandom() stream for this frame (timeless omits the frame from the seed - the AE switch).
Determinism: seeded.
Worked example: Replay the same numbers
The scene. One orange square resting on a dark 640 by 360 stage.
- The comp Drift: 640 by 360 at 25 fps, 100 frames (4 seconds).
- Square: a solid layer, at [320, 180], 60 by 60, filled f2a833.
The script. Add a project script named Two dice, one seed and paste:
export const outputs = ["layer_1:transform.opacity"];
export function frame() {
seedRandom(3);
const first = random();
seedRandom(3);
const second = random();
return first === second ? 88 : 0;
}
Line by line:
- Lines 4-5: seedRandom(offset) restarts the random stream for this frame from a seed you choose. The first draw after it is fully determined.
- Lines 6-7: Reseeding with the SAME offset rewinds the stream - the second draw repeats the first exactly.
- Line 8: The equality proves it, and the Square rests at 88 as the visible receipt. Pass true as the second argument to drop the frame from the seed, freezing the numbers across time - the AE timeless switch.
What you see. The square rests steadily at 88 percent opacity - the proof that reseeding with the same offset replays exactly the same random numbers.
noise
noise(t?: number): number
Seeded value noise in [-1, 1] - a PURE function of (seed, t), random-access safe.
Determinism: seeded.
Worked example: Smooth wandering
The scene. One orange square resting on a dark 640 by 360 stage.
- The comp Drift: 640 by 360 at 25 fps, 100 frames (4 seconds).
- Square: a solid layer, at [320, 180], 60 by 60, filled f2a833.
The script. Add a project script named Wander and paste:
export const outputs = ["layer_1:transform.position"];
export function frame() {
return [value[0] + noise(time) * 60, value[1] + noise(time + 50) * 40];
}
Line by line:
- Line 4: noise(t) is smooth value noise in [-1, 1]: neighbouring times give neighbouring values, so the motion flows instead of teleporting (compare random()). It is a pure function of its input - the same t always answers the same number - which is why offsetting the second axis by 50 seconds makes it wander independently.
What you see. The square roams smoothly around its resting spot - up to 60 pixels sideways and 40 vertically - drifting rather than jumping, identical on every playback.
wiggle
wiggle(freq: number, amp: number, t?: number): number
amp x seeded value noise at t x freq - time-pure, random-access safe.
Determinism: seeded.
wiggle() is a pure function of the frame being evaluated: ask for the same frequency, amplitude and time twice and you get the same number back. That is what makes scrubbing safe - the drift at frame 40 is the same whether you played into it or jumped straight there. It also means two calls with the same arguments return the SAME value, so to wiggle two axes independently you offset the time of one call, as the example shows.
Worked example: A gentle drift
The scene. One orange square resting on a dark 640 by 360 stage.
- The comp Drift: 640 by 360 at 25 fps, 100 frames (4 seconds).
- Square: a solid layer, at [320, 180], 60 by 60, filled f2a833.
The script. Add a project script named Drift and paste:
export const outputs = ["layer_1:transform.position"];
export function frame() {
const x = value[0] + wiggle(0.8, 24);
const y = value[1] + wiggle(0.8, 24, time + 100);
return [x, y];
}
Line by line:
- Line 1: Declare what this script is allowed to drive: the Square layer's position. The Scripts panel and the pick whip write these ids for you.
- Line 3: frame() runs once for every frame the comp shows. Whatever it returns becomes the value of the script's one declared output for that frame.
- Line 4: value is the position the Square would have without the script - here its resting [320, 180]. wiggle(0.8, 24) adds a smooth wander that changes about 0.8 times per second and strays up to 24 pixels.
- Line 5: The second axis offsets the wiggle's clock by 100 seconds. Without the offset both axes would get the SAME number and the Square would only slide along a diagonal.
- Line 6: Return the new position as an [x, y] array - positions are arrays in scripts, exactly as the panel shows them as X and Y.
What you see. The square drifts gently around its resting spot, never straying more than about 24 pixels, and the motion loops nowhere - it just keeps wandering.