Song code evaluated the way the REPL does and played through against a stand-in Jev, for the tests that must prove code runs: every song (allSongs.test.mjs), and the jev reference's examples and the MCP primer's song (reference.test.mjs). Test-only: the browser never imports it (it pulls in upstream's mocked Node runtime).
the REPL's scope, with audio and drawing mocked (upstream's tune tests)
How far the scheduler reads per tick, in cycles.
15export const STEP = 1 / 4;
A stand-in Jev: a valid answer to every question, varied by a seeded generator so forms take different paths, and every request checked against the relay's own rules.
20function standIn(requests, now) { 21 let seed = 7; 22 const random = () => ((seed = (seed * 16807) % 2147483647) - 1) / 2147483646; 23 return async (state, questions) => { 24 const body = { model: JEV_MODEL, questions, state }; 25 requests.push({ 26 why: whyNot(body), 27 bytes: new TextEncoder().encode(JSON.stringify(body)).length, 28 cycle: now(), 29 // a form's request, and whether its history says how sections were arranged 30 form: 'mayComeNext' in (state.form ?? {}) && !('decidedThisSection' in state), 31 arranged: (state.history ?? []).some((row) => row.arrangement), 32 }); 33 return Object.fromEntries( 34 Object.entries(questions).map(([name, q]) => { 35 if (q.type === 'choice') { 36 const options = Object.keys(q.criteria); 37 const pick = options[Math.floor(random() * options.length)]; 38 const probabilities = Object.fromEntries(options.map((o) => [o, o === pick ? 0.7 : 0.3 / (options.length - 1)])); 39 return [name, { type: 'choice', choice: pick, confidence: 0.6, probabilities }]; 40 } 41 if (q.type === 'score') return [name, { type: 'score', score: random() * (q.criteria.length - 1), confidence: 0.5 }]; 42 return [name, { type: 'noul', noul: random() }]; 43 }), 44 ); 45 }; 46}
48const tick = () => new Promise((resolve) => setImmediate(resolve));
Evaluates code with a stand-in Jev, asks about its opening as the page
does before it starts (useReplContext), then plays it a slice at a time
for maxCycles, the answers landing between slices. Returns the requests
Jev was sent, the tempo the code set, how many onsets it played, the
cycle of its last, and the booth's views (each jev()'s decisions).
55export async function playThrough(code, maxCycles) { 56 const requests = []; 57 let t = 0; 58 let cps = null; 59 const jev = createJev(standIn(requests, () => t), { hear: () => null, random: () => 0.5 }); 60 // the sample maps are the site's to serve; nothing plays here 61 await evalScope({ jev, choice, score, noul, walk, samples: async () => {}, setcps: (x) => (cps = x) }); 62 boothStore.beginEval(); 63 const { pattern } = await evaluate(code); 64 boothStore.commitEval(); 65 const views = boothStore.starting(); 66 await askOpening(1000); 67 if (!pattern) throw new Error('the code does not evaluate to a pattern'); 68 let heard = 0; 69 let lastSound = -1; 70 for (; t < maxCycles; t += STEP) { 71 const haps = pattern.queryArc(t, t + STEP).filter((h) => h.hasOnset()); 72 if (haps.length) [heard, lastSound] = [heard + haps.length, t]; 73 await tick(); 74 } 75 return { requests, cps, heard, lastSound, views }; 76}