Jev's takes on a song, for its view (JevTakes.jsx): every recorded performance of it (worker/src/listening.ts, GET /jev/performances?song=), each replayed through the replay link (performance.mjs), and how Jev moves through the song across them (GET /jev/performances/moves?song=), laid out in the song's written order.
6import { PERFORMANCES } from './performance.mjs';
{ song, total, takes: [{ id, day, at, player, ended, codeHash, segments, fallbacks, path }] }
{ song, performances, of, sections: [{ section, plays, asked, fallbacks, ms, next }] }
When a take was recorded, in UTC to the minute. Takes stored before the site kept the time (worker/migrations/0006) are dated noon UTC of their day, and show the day alone rather than an hour nobody recorded.
A song's sections in the order it is written, read from its code: its
walk() form's sections by at (name: { at: 16, next: [...] }, or
at: at.name from a const at = { name: 16, ... }). [] when the code
does not say (a song without a form, or one that builds its sections in
code, like ALL GREEN): the view then uses the booth's form if the song
is playing, or the order Jev plays them in.
38export function writtenOrder(code) { 39 const text = String(code ?? ''); 40 const objects = new Map(); 41 for (const m of text.matchAll(/const\s+([A-Za-z_$][\w$]*)\s*=\s*\{([^{}]*)\}/g)) { 42 objects.set(m[1], Object.fromEntries([...m[2].matchAll(/([A-Za-z_]\w*)\s*:\s*(\d+)/g)].map((e) => [e[1], Number(e[2])]))); 43 } 44 const sections = []; 45 for (const m of text.matchAll(/(?:^|[\s{,])([A-Za-z_]\w*)\s*:\s*\{\s*at\s*:\s*(\d+|[A-Za-z_$][\w$]*\.[A-Za-z_]\w*)/g)) { 46 const [obj, key] = m[2].split('.'); 47 const at = key === undefined ? Number(obj) : objects.get(obj)?.[key]; 48 if (typeof at !== 'number') return []; 49 if (!sections.some((s) => s.name === m[1])) sections.push({ name: m[1], at }); 50 } 51 return sections.length > 1 ? sections.sort((a, b) => a.at - b.at).map((s) => s.name) : []; 52}
Each section's mean place in the takes' paths, to order the sections of a song whose written order is unknown the way Jev plays them.
The form map's rows: every section Jev played, in written order (then any the code no longer has, or, the order unknown, by their place in the takes), a song without a form in section number order; each with the section after it as written (null when unknown), and its next sections marked when one is that.
72export function formMap(moves, written, placed = new Map()) { 73 const played = new Map(moves.sections.map((s) => [s.section, s])); 74 const numbered = !written.length && [...played.keys()].every((n) => /^\d+$/.test(n)); 75 const place = (n) => placed.get(n) ?? Infinity; 76 const order = numbered 77 ? [...played.keys()].sort((a, b) => Number(a) - Number(b)) 78 : [ 79 ...written.filter((n) => played.has(n)), 80 ...[...played.keys()].filter((n) => !written.includes(n)).sort((a, b) => place(a) - place(b) || a.localeCompare(b)), 81 ]; 82 return order.map((name) => { 83 const s = played.get(name); 84 const i = written.indexOf(name); 85 const writtenNext = numbered ? String(Number(name) + 1) : i >= 0 ? (written[i + 1] ?? null) : null; 86 return { 87 ...s, 88 writtenNext, 89 next: s.next.map((n) => ({ ...n, asWritten: n.section === writtenNext })), 90 }; 91 }); 92}
"3 of 12", or a share as a percentage.
95export const pct = (x) => `${Math.round(x * 100)}%`;