DDirectorWikiDirector & Lingo Encyclopedia

Timing, Timers, and Synchronization

Clocks and timers in Lingo - milliseconds, ticks, the timer, timeout objects, idle, cue points, and frame-rate-independent animation.

The clocks

lingo
the milliseconds     -- ms since player start (the modern choice)
the ticks            -- 60ths of a second since player start
the timer            -- resettable tick counter (startTimer resets)
the systemDate       -- date object
the long time        -- formatted time strings (classic)
_system.milliseconds -- MX 2004 facade for the same counter

the ticks (1/60s) is the Director-native unit; timeouts and click timings are documented in ticks. the milliseconds arrived later and dominates late-era code.

lingo
startTimer                  -- reset the timer
if the timer > 120 then ... -- 2 seconds elapsed (in ticks)

Frame-rate-independent animation

The tempo channel gives frames-per-second, but real elapsed time varies; robust movies animate by clock:

lingo
property pLastMs

on beginSprite me
  pLastMs = the milliseconds
end

on prepareFrame me
  dt = the milliseconds - pLastMs
  pLastMs = the milliseconds
  sprite(me.spriteNum).locH = sprite(me.spriteNum).locH + pSpeed * dt / 1000.0
end

Emulator note: content mixes clock-based and frame-based animation freely. Frame-based movies (move 5px per exitFrame at tempo 30) require honoring the authored tempo; clock-based movies require honest wall-clock properties. Neither can be faked from the other.

Timeout objects (MX 2004 timers)

The precise recurring-callback tool (full object treatment in Parent scripts and objects):

lingo
tm = timeout().new("anim", 100, #tick, me)     -- every 100 ms -> onTick
timeout("anim").period = 250
timeout("anim").forget()
_movie.timeoutList

Delivery model: single-threaded, between frame events, on schedule but never preempting a running handler. A timeout that comes due repeatedly while a handler blocks delivers once when the thread frees (no burst catch-up in practice; verify against target player when it matters).

The classic timeout mechanism (Director 4-6)

Older, global, and still functional in MX-era players (all names present in the native symbol registry):

lingo
the timeoutLength = 60 * 60 * 2        -- 2 minutes, in ticks
the timeoutScript = "onUserIdle"       -- primary handler string
the timeoutLapsed                      -- ticks since last reset
the timeoutKeyDown = TRUE              -- keystrokes reset the timeout
the timeoutMouse = TRUE                -- clicks reset it
the timeoutPlay = FALSE                -- play commands reset it

One global inactivity timer: when timeoutLapsed reaches timeoutLength, the timeOut event fires (routed like other primary events). Kiosk-era attract loops are built on this.

idle

on idle handlers (frame/movie scripts only) run while Director waits out the tempo between enterFrame and exitFrame. the idleHandlerPeriod (ticks) throttles delivery; 0 means continuous. Idle is where cooperative background work happened (network polling, resource loading via the idle-load APIs in Memory and streaming).

Waiting correctly

lingo
-- WRONG for anything over ~100ms: blocks everything
repeat while the milliseconds - t0 < 2000
  nothing
end repeat

-- RIGHT: loop the playhead on a frame
on exitFrame
  if the milliseconds - gT0 > 2000 then go "next"
  else go the frame
end

-- Tempo channel wait states (authored): wait N sec, wait for click/key, wait for cue point
delay 120     -- legacy: block frame advance 2 sec (input still processed for wait-abort)

Cue points and media sync

Sounds (AIFF/WAV/SWA) and QuickTime media carry named cue points:

lingo
on cuePassed me, channelID, cueNumber, cueName
  if cueName = "Chorus" then startChorusAnimation
end

member("song").cuePointNames, member("song").cuePointTimes
isPastCuePoint(sound 1, 2)
the mostRecentCuePoint of sound 1

cuePassed routes like a sprite/frame/movie event (the sprite form fires for sprite-attached media). The tempo channel's "Wait for Cue Point" blocks frame advance until a cue arrives: the authored lip-sync mechanism.

Sound scheduling API on channels: queue() + play() for gapless sequences, breakLoop() to exit a loop section, loopCount/loopStartTime/loopEndTime, and elapsedTime for progress. Gapless queueing matters: rhythm and karaoke titles depend on it.

lingo
the cpuHogTicks         -- (classic Mac) yield granularity to the OS
the netThrottleTicks    -- network servicing granularity (Mac)
the idleHandlerPeriod
the idleLoadPeriod

These tuned cooperative multitasking on 90s machines. Emulators can accept and ignore them, but should keep them readable/writable.

Emulator timing checklist

  1. One thread, no preemption: handlers always run to completion.
  2. Tempo governs head advance; handlers can only slow it (no frame skipping of logic).
  3. the milliseconds/ticks advance during blocking loops (they read the wall clock).
  4. Timeout objects fire between frames; blocked thread delays them.
  5. updateStage() renders mid-handler and triggers stepFrame (see Event lifecycle).
  6. delay blocks advance but keeps processing wait-abort input.
  7. Cue points fire cuePassed with correct channel/name arguments and honor queue order.