Time and ticks
Every time in a project is an integer number of ticks. This page explains the unit, the ranges built from it, the frame grid, and how a clip turns timeline time into source time. After reading it you can place clips exactly on frame boundaries at any frame rate.
120,000 ticks per second
Section titled “120,000 ticks per second”A time is a MediaTime: a signed integer of ticks, 120,000 to the second. Integers keep arithmetic exact. Two hosts that add the same times get the same answer, with no floating-point drift between a browser and a server.
With 120,000 ticks a second, a frame of every common frame rate lasts a whole number of ticks, including the NTSC rates, whose frames last 1001/30000 of a second and similar:
| Frame rate | frame_rate |
One frame in ticks |
|---|---|---|
| 23.976 | { "num": 24000, "den": 1001 } |
5005 |
| 24 | { "num": 24, "den": 1 } |
5000 |
| 25 | { "num": 25, "den": 1 } |
4800 |
| 29.97 | { "num": 30000, "den": 1001 } |
4004 |
| 30 | { "num": 30, "den": 1 } |
4000 |
| 50 | { "num": 50, "den": 1 } |
2400 |
| 59.94 | { "num": 60000, "den": 1001 } |
2002 |
| 60 | { "num": 60, "den": 1 } |
2000 |
So at 29.97 fps, frame 1 starts at exactly 4004 ticks and frame 30 at 120,120, with no rounding. A millisecond is 120 ticks, which keeps SubRip and WebVTT timestamps exact too.
A frame rate is at most one frame per tick (120,000 fps). Serialized times are at most 2^53 − 1 ticks either way, the largest integer a JavaScript number holds exactly, so every host reads the same JSON times.
Half-open ranges
Section titled “Half-open ranges”A TimeRange is half-open: start is included, end is not. A clip with { "start": 0, "end": 120000 } covers the first second, and the next clip can start at 120000 without overlapping it. A range’s length is end - start.
Clips, transitions, caption cues and run times all use half-open ranges.
The frame grid
Section titled “The frame grid”The frame rate divides the timeline into frames. Frame n starts at floor(n × 120000 × den / num) ticks and lasts until the next one starts. An export walks this grid: it renders one frame per grid step, at export quality, from the range’s start to its end. A still renders the frame of the grid that holds the requested time.
A change you put between two grid points shows at the next frame. The linter reports such edges (off_frame_boundary) and transitions that no frame shows (short_transition).
Converting
Section titled “Converting”- TypeScript (
alpha2/project):secondsToTicks(seconds)andticksToSeconds(ticks), andsamplesToTicksandticksToSamplesfor audio. - Python:
alpha2.TICKS_PER_SECOND. The helpers take times in seconds, or asalpha2.Ticksfor an exact tick count. - Rust (
alpha2::time):TICKS_PER_SECOND, and onFrameRate,frame_start(frame)(the tick a frame starts at) andframe_at_or_before(time)(the frame whose interval holds a time).
secondsToTicks rounds to the nearest tick, so secondsToTicks(ticksToSeconds(t)) === t. Audio sample positions don’t always fall on ticks (at 48 kHz a sample lasts 2.5 ticks): samplesToTicks gives the tick at or before a sample, and ticksToSamples the sample that contains a tick.
The engine’s JavaScript API takes times as bigint (engine.renderAt(120_000n)); the project document holds them as JSON numbers.
From timeline time to source time
Section titled “From timeline time to source time”A clip reads its source_range while it is on the timeline for its timeline_range. Its time_map says how:
forward(the default) stretches the source range over the timeline range. Speed is the ratio of their lengths: a source range twice as long as the timeline range plays at double speed.reversedoes the same from the end of the source range to its start: pictures in reverse order, and sound played backwards.freezeshows onesource_timefor the whole clip, and is silent.loopplays the source range at its own speed for as long as the timeline range lasts,repeat(every pass forward) orping_pong(alternating directions).
Here is a 29.97 fps project with three clips of one camera file, every boundary on the frame grid (multiples of 4004):
[ { "id": "fast", "source_id": "camera", "timeline_range": { "start": 0, "end": 360360 }, "source_range": { "start": 0, "end": 720720 } }, { "id": "backwards", "source_id": "camera", "timeline_range": { "start": 360360, "end": 480480 }, "source_range": { "start": 720720, "end": 840840 }, "time_map": { "type": "reverse" } }, { "id": "still", "source_id": "camera", "timeline_range": { "start": 480480, "end": 600600 }, "source_range": { "start": 840840, "end": 960960 }, "time_map": { "type": "freeze", "source_time": 900900 } }]fastlasts 90 frames (360,360 ticks) and reads 180 frames’ worth of source: double speed.backwardsplays 30 frames of source in reverse.stillholds the source picture at 900,900 ticks for its whole length.
retime in alpha2/edit changes a clip’s length the way an editor does: forward and reversed clips change speed to fit the same source window, frozen clips hold longer or shorter, and loops play more or fewer passes. When a clip plays at a speed other than 1, its audio is resampled, or time-stretched to keep its pitch when audio.preserve_pitch is set.
Composition time
Section titled “Composition time”A clip that shows a composition also maps time onto it, through ClipComposition:
composition time = offset + (t − clip start) × scalefor timeline time t, rounded down to a tick. offset defaults to 0 and scale to { "num": 1, "den": 1 }. Moving a clip keeps its offset, so its animation moves with it. Trimming its start by d adds d × scale to the offset, so the rest stays in place.
Animation explains which values are keyed in timeline time, composition time or picture time.
