A song's samples, loaded before its first bar. superdough fetches a
sample the first time a note plays it, and a note whose file is still
loading when it is due is dropped ("skip hap: still loading"), so on a
first listen a song's first hit of each clip could go silent. The
scheduler awaits beforeStart (useReplContext) before its first tick;
by then the song is evaluated and its sample maps registered, so every
sample the song plays as written starts loading, from the URL its first
hit will resolve (loadBuffer caches by URL). The song waits only for what
its first few cycles play; the rest loads while it plays, well before
it is needed.
The song is queried as written (jevCore's asWritten): a plain query of a section that has not begun would ask Jev about it. Sections Jev may repeat or skip use the same sounds as the written ones.
Every sample (bank and variant) the haps play, with the cycle it is first heard at.
25export function samplesIn(haps, banks) { 26 const wanted = new Map(); 27 for (const hap of haps) { 28 const { value } = hap; 29 if (typeof value !== 'object' || value === null || value.s === undefined) continue; 30 const s = value.bank ? `${value.bank}_${value.s}` : String(value.s); 31 if (!Object.hasOwn(banks, s)) continue; 32 const n = Number(value.n ?? 0); 33 const at = hap.whole?.begin.valueOf() ?? 0; 34 const key = `${s}:${n}`; 35 const seen = wanted.get(key); 36 if (!seen || at < seen.at) wanted.set(key, { s, n, at }); 37 } 38 return [...wanted.values()]; 39}
The song's haps as written, until two chunks in a row play nothing; none when it cannot be read ahead (useReplContext hands them to the preload and to jevCore's setWrittenParts).
54function writtenHaps(pattern) { 55 const haps = []; 56 let empty = 0; 57 for (let from = 0; from < MAX_CYCLES && empty < 2; from += CHUNK) { 58 const chunk = asWritten(() => pattern.queryArc(from, from + CHUNK)); 59 empty = chunk.length ? 0 : empty + 1; 60 haps.push(...chunk); 61 } 62 return haps; 63} 64 65export async function preloadSamples(haps) { 66 if (!haps.length) return; 67 const banks = Object.fromEntries( 68 Object.entries(soundMap.get()) 69 .filter(([, sound]) => sound.data?.type === 'sample' && sound.data.samples) 70 .map(([name, sound]) => [name, sound.data.samples]), 71 ); 72 const wanted = samplesIn(haps, banks); 73 const ac = getAudioContext(); 74 // first heard first, so the ones waited for are fetched ahead of the rest 75 wanted.sort((a, b) => a.at - b.at); 76 const loads = wanted.map(({ s, n, at }) => ({ 77 at, 78 loading: loadBuffer(getSampleInfo({ s, n }, banks[s]).url, ac, s, n).catch(() => {}), 79 })); 80 const soon = loads.filter((l) => l.at < SOON).map((l) => l.loading); 81 if (!soon.length) return; 82 let timer; 83 await Promise.race([Promise.all(soon), new Promise((r) => (timer = setTimeout(r, WAIT_MS)))]); 84 clearTimeout(timer); 85}