Resources Numerical accuracy Calculation policy

Narration cue times

Places the spoken lines of a walkthrough narration in time, keeps their start times and writes them as subtitles.

How the precision is kept

Cue start times are rounded to a tenth of a second when they are read and when they are stored. A line without its own time is placed after the previous line by a reading time of 2.5 words per second, never under 2 seconds. Subtitle times are written in whole milliseconds.

Calculations under this policy

3 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.

narration.js BimCraft 0.8.1 64-bit 3 calculations Reviewed

Tests tools/bimcraft-test/r47-test.mjs

Reading time of a line

64-bit

How long a spoken line takes, for placing the next one.

max(2, round(words ÷ 2.5, 1 decimal))

Words are counted as integers, divided by 2.5 and rounded with Math.round(x × 10) ÷ 10. The minimum is 2 seconds.

  • narration.js
  • lines 26 to 29
  • BimCraft 0.8.1
export function readSeconds(text) {
  const words = String(text).trim().split(/\s+/).filter(Boolean).length;
  return Math.max(2, Math.round((words / 2.5) * 10) / 10);
}

Cue times from a script

64-bit

The start time of each cue from a typed script, and the squeeze when the speech is longer than the walk.

t = round(min × 60 + s, 1 decimal); squeezed t = round(t × duration ÷ end, 1 decimal)

A typed time is minutes × 60 + seconds in 64-bit and then rounded to 0.1 s. When untimed lines run past the duration, every time is multiplied by duration ÷ end and rounded to 0.1 s again, so the cues carry two roundings.

  • narration.js
  • lines 47 to 69
  • BimCraft 0.8.1
export function parseScript(text, duration = 0) {
  const lines = String(text ?? '').split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
  const cues = [], problems = [];
  let at = 0;
  for (const line of lines) {
    const m = TIME_RE.exec(line);
    let t, body;
    if (m && /^\d/.test(line)) { t = (m[1] ? Number(m[1]) * 60 : 0) + Number(m[2]); body = clean(m[3]); }
    else { t = at; body = clean(line); }
    if (!body) continue;
    if (cues.length >= MAX_CUES) { problems.push({ code: 'narr.toomany', severity: 'warning', text: 'Only the first ' + MAX_CUES + ' cues are kept.' }); break; }
    if (cues.length && t < cues[cues.length - 1].t) { t = cues[cues.length - 1].t; problems.push({ code: 'narr.order', severity: 'note', text: 'A cue started before the one above it; it follows it.' }); }
    cues.push({ t: Math.round(t * 10) / 10, text: body });
    at = t + readSeconds(body);
  }
  // plain lines that run past the walk are squeezed into it (only the lines without their own time were placed by reading time)
  if (duration > 0 && cues.length && at > duration && !lines.some((l) => TIME_RE.test(l) && /^\d/.test(l))) {
    const k = duration / at;
    for (const c of cues) c.t = Math.round(c.t * k * 10) / 10;
    problems.push({ code: 'narr.squeezed', severity: 'note', text: 'The narration was longer than the walk (' + Math.round(at) + ' s of speech in ' + Math.round(duration) + ' s); the cues were placed closer together.' });
  }
  return { cues, problems };
}

Subtitle timestamp

64-bit

A cue time as an SRT or WebVTT timestamp.

ms = round(s × 1000); h, m, s, ms by integer division

Seconds are multiplied by 1000 and rounded to whole milliseconds first; hours, minutes and seconds are then taken from that integer, so no fraction is lost between fields.

  • narration.js
  • lines 89 to 92
  • BimCraft 0.8.1
const clock = (s, comma) => {
  const ms = Math.round(s * 1000), h = Math.floor(ms / 3600000), m = Math.floor((ms % 3600000) / 60000), sec = Math.floor((ms % 60000) / 1000), r = ms % 1000;
  return pad(h) + ':' + pad(m) + ':' + pad(sec) + (comma ? ',' : '.') + pad(r, 3);
};

In detail

Method
Reading time = words ÷ 2.5 s, rounded to 0.1 s, at least 2 s. Lines that run past the walk are scaled by duration ÷ end of speech.
Number types
64-bit numbers; seconds.
Tolerances
Times are limited to 0 to 86400 s and 200 cues.
Limits
A time read as seconds is rounded to 0.1 s before it is kept, so a time typed as 12.34 is stored as 12.3.

Approach: 64-bit, direct

Every value is a 64-bit number from input to result. Formulas are closed form or a direct solve, and every comparison uses a stated tolerance.

Every policy that takes this approach