Skip to main content

Measure Audio Drift Between Two Sync Marks in Node.js

Updated 5 min readMarkdown

A sound cue that lines up near the start of a clip can miss its visual cue near the end. Two matched sync marks let you measure how much the offset changed. For example, a reviewer could mark the visible contact and audio impact for two strikes in the same clip.

This small Node.js utility reads two reviewed marks. It reports the signed offsets and checks the change against a project tolerance using exact integer arithmetic.

1. Record two pairs of marks

Each pair names the same event on both tracks. Record videoMs on the reference timeline and audioSample as the sample-frame index from the audio origin. A sample frame covers all channels at one time. Use the sample rate for the timeline you actually measured.

To interpret the individual offsets against picture, align the audio and video origins first. If the audio stream begins later on the container timeline, account for that start offset when preparing the marks. A constant origin difference cancels out of the drift calculation.

Record how you picked each cue. If a visual marker can only be located to the nearest frame, keep that uncertainty beside the integer timestamp.

2. Set the drift limit for the review

Save this fictional fixture as marks.json. The limit of 20 milliseconds is a project choice in the example. The check applies it to the change in offset between the two marks.

{
  "sampleRate": 48000,
  "maxDriftMs": 20,
  "start": {
    "videoMs": 1000,
    "audioSample": 48960
  },
  "end": {
    "videoMs": 9000,
    "audioSample": 433920
  }
}

At 48,000 samples per second, sample 48,960 is at 1,020 milliseconds and sample 433,920 is at 9,040 milliseconds. Relative to the picture marks at 1,000 and 9,000 milliseconds, the audio is 20 milliseconds late at the start and 40 milliseconds late at the end.

3. Compare rational values as integers

Audio time in milliseconds is the sample index multiplied by 1,000 and divided by the sample rate. Subtracting the picture timestamp gives an offset whose numerator is the sample index times 1,000 minus the picture timestamp times the sample rate.

Both offsets have the same denominator. Subtract their numerators to calculate drift. Compare the absolute result with the limit multiplied by the sample rate. This avoids rounding a fractional millisecond across the threshold. BigInt keeps the intermediate products exact. ECMAScript BigInt specification.

4. Implement the check

Save this as sync-drift.mjs and run it with Node.js 24.20.0. The input uses safe integer JSON numbers. The output uses decimal strings for the exact numerators and denominator.

import { readFileSync } from "node:fs";

function nonnegativeInteger(value, name) {
  if (!Number.isSafeInteger(value) || value < 0) {
    throw new Error(name + " must be a nonnegative safe integer");
  }
  return BigInt(value);
}

function readMark(mark, label) {
  return {
    videoMs: nonnegativeInteger(mark?.videoMs, label + ".videoMs"),
    audioSample: nonnegativeInteger(mark?.audioSample, label + ".audioSample"),
  };
}

export function measureDrift(input) {
  const rate = nonnegativeInteger(input?.sampleRate, "sampleRate");
  if (rate === 0n) throw new Error("sampleRate must be positive");

  const limit = nonnegativeInteger(input?.maxDriftMs, "maxDriftMs");
  const start = readMark(input?.start, "start");
  const end = readMark(input?.end, "end");
  if (end.videoMs <= start.videoMs || end.audioSample <= start.audioSample) {
    throw new Error("end must follow start on both tracks");
  }

  const offset = ({ videoMs, audioSample }) =>
    audioSample * 1000n - videoMs * rate;
  const startOffset = offset(start);
  const endOffset = offset(end);
  const drift = endOffset - startOffset;
  const magnitude = drift < 0n ? -drift : drift;

  return {
    withinDriftLimit: magnitude <= limit * rate,
    denominator: rate.toString(),
    startOffsetNumerator: startOffset.toString(),
    endOffsetNumerator: endOffset.toString(),
    driftNumerator: drift.toString(),
  };
}

if (import.meta.main) {
  try {
    if (process.argv.length !== 3) throw new Error("Usage: node sync-drift.mjs marks.json");
    const input = JSON.parse(readFileSync(process.argv[2], "utf8"));
    const report = measureDrift(input);
    console.log(JSON.stringify(report, null, 2));
    process.exitCode = report.withinDriftLimit ? 0 : 1;
  } catch (error) {
    console.error(error.message);
    process.exitCode = 2;
  }
}

Positive offsets put audio later than picture. A positive drift means the offset increased between the two marks. Both tracks must move forward so a duplicated or reversed mark fails input validation.

5. Exercise the boundary from the command line

Save a copy of the fixture as over.json and increase the end audioSample to 433921. Then run all three commands.

node sync-drift.mjs marks.json
node sync-drift.mjs over.json
node sync-drift.mjs

The first command prints the report below and exits 0 because the drift is exactly 20 milliseconds.

{
  "withinDriftLimit": true,
  "denominator": "48000",
  "startOffsetNumerator": "960000",
  "endOffsetNumerator": "1920000",
  "driftNumerator": "960000"
}

One extra sample makes the drift 961,000 divided by 48,000 milliseconds, just over 20, so the second command exits 1. A missing argument, bad JSON or invalid mark exits 2.

6. Read the drift beside the offsets

A fixed late start can produce zero drift. Keep the two offsets visible so a pass on the drift limit still leaves the constant alignment issue visible.

Two marks describe the change between those positions. Add reviewed marks around cuts or other suspicious intervals if you need to locate a jump or a shift that later cancels out.

AI-assisted writing and code drafting. The example was tested using synthetic local JSON on Node.js 24.20.0. No media was generated, decoded, or measured.

This developer workflow follows the discussion of sound cues and picture timing in the PixMind source. Veo 3.1 Complete Guide.

1 view

About the author

  • The PixMind team shares practical AI image and video workflows. PixMind brings text-to-image, reference-based creation, editing, and video tools into one workspace. Website: https://www.pixmind.io/

No comments yet. Be the first to comment.

More from this blog