hold: the level is baked into the sample (iOS ignores element volume); OFF holds on pure silence
shots / shots (push) Successful in 2m21s
shots / shots (push) Successful in 2m21s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K6897PWcUEwHiqZMcVnNF6
This commit is contained in:
1 parent
bc7a16bb34
commit
d97628d1ad
1 file changed
+8
-3
+8
-3
@@ -639,7 +639,7 @@ function drawRings(canvas, spec, { lit = 1, pulse = null } = {}) {
|
||||
/* vinyl crackle under the gaps: a few seconds of soft noise and pops, made once, looped quietly in a second <audio>
|
||||
while a track cooks or between tracks. Not Web Audio, so it behaves on iOS like the music does. */
|
||||
const crackle = { el: null, on: false };
|
||||
function makeCrackle(seconds = 12) {
|
||||
function makeCrackle(seconds = 12, gain = 1) {
|
||||
const sr = 22050, n = sr * seconds, mix = new Float32Array(n), out = new Int16Array(n), rnd = prng(0x5eed);
|
||||
let lp = 0;
|
||||
for (let i = 0; i < n; i++) {
|
||||
@@ -647,7 +647,7 @@ function makeCrackle(seconds = 12) {
|
||||
mix[i] += lp * 1.6;
|
||||
if (rnd() < 0.0015) { const len = 20 + Math.floor(rnd() * 80), amp = 0.3 + rnd() * 0.5; for (let k = 0; k < len && i + k < n; k++) mix[i + k] += amp * (1 - k / len) * (rnd() * 2 - 1); } // a pop
|
||||
}
|
||||
for (let i = 0; i < n; i++) out[i] = Math.max(-1, Math.min(1, mix[i])) * 32767;
|
||||
for (let i = 0; i < n; i++) out[i] = Math.max(-1, Math.min(1, mix[i] * gain)) * 32767;
|
||||
const buf = new ArrayBuffer(44 + n * 2), dv = new DataView(buf);
|
||||
const str = (o, s) => { for (let i = 0; i < s.length; i++) dv.setUint8(o + i, s.charCodeAt(i)); };
|
||||
str(0, "RIFF"); dv.setUint32(4, 36 + n * 2, true); str(8, "WAVE"); str(12, "fmt "); dv.setUint32(16, 16, true); dv.setUint16(20, 1, true); dv.setUint16(22, 1, true);
|
||||
@@ -1365,7 +1365,12 @@ function toCooking(job) {
|
||||
}
|
||||
/* → holding, inside `ended`: nothing is cooked, so the player loops the crackle in its own element to stay alive */
|
||||
function toHolding() {
|
||||
try { crackle.hold ||= makeCrackle(4); crackleOff(); setSource(crackle.hold.src, true); if (state.holdVol == null) { state.holdVol = state.audio.volume; state.audio.volume = bedOff() ? 0 : Math.min(state.audio.volume, crackle.base ?? 0.18); } state.phase = "holding"; const p = state.audio.play(); if (p && p.catch) p.catch(() => {}); } catch {}
|
||||
try {
|
||||
// the level is baked into the sample: iOS ignores an element's volume. OFF holds on four seconds of nothing (its own
|
||||
// URL, not SILENCE, so `ended` still fires and hands over).
|
||||
const lvl = bedOff() ? 0 : Math.round((crackle.base ?? 0.18) * 100) / 100;
|
||||
if (!crackle.hold || crackle.hold.lvl !== lvl) { crackle.hold = lvl ? makeCrackle(4, lvl) : Object.assign(new Audio(URL.createObjectURL(wavBlob(new Int16Array(8000 * 4), 8000))), { loop: true }); crackle.hold.lvl = lvl; }
|
||||
crackleOff(); setSource(crackle.hold.src, true); state.phase = "holding"; const p = state.audio.play(); if (p && p.catch) p.catch(() => {}); } catch {}
|
||||
}
|
||||
/* a landed track while the player holds: it queues behind the crackle, the loop is switched off so the pass ends, and
|
||||
handOver starts it inside that `ended`. Resolves once the track has been handed over (or dropped). */
|
||||
|
||||
Reference in new issue
Block a user