From 3ee513ad34098c5a320c643a9664660b113d732e Mon Sep 17 00:00:00 2001 From: teahubxyz Date: Wed, 21 Oct 2026 04:32:36 +0000 Subject: [PATCH] volume and room-tone sliders in the station sheet, remembered Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01K6897PWcUEwHiqZMcVnNF6 --- index.html | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 15f6b35..ca7c734 100644 --- a/index.html +++ b/index.html @@ -88,6 +88,10 @@ input,textarea{font:inherit;color:inherit} #stationSheet label{display:block;color:var(--dim);font-size:12px;margin:8px 0 4px} #stationSheet input{width:100%;background:var(--field);border:1px solid var(--line);border-radius:10px;padding:10px 12px;outline:none} #stationSheet .row{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px} +#stationSheet .sl{display:flex;align-items:center;gap:12px;margin-top:6px} +#stationSheet .sl label{margin:0;width:84px} +#stationSheet input[type=range]{flex:1;accent-color:var(--ink);margin:0} +#stationSheet .sl span{font:11px var(--font);color:var(--dim);width:32px;text-align:right} #stationSheet .row button{padding:9px 14px;border-radius:999px;border:1px solid var(--line);font:600 11px var(--font);letter-spacing:.14em} #stationSheet .row button.go{background:var(--acc);color:var(--onacc);border-color:var(--acc)} #stationSheet .row button.bad{color:hsl(0 60% 70%);border-color:hsl(0 30% 35%)} @@ -405,7 +409,7 @@ const state = { history: [], // tracks played this session (specs) current: null, nextJob: null, playing: false, recent: [], pos: -1, // ids of tracks played this session, and where the player stands in them - audio: new Audio(), cost: parseFloat(localStorage.getItem(LS.cost) || "0"), + audio: Object.assign(new Audio(), { volume: Math.min(1, Math.max(0, parseFloat(localStorage.getItem("gandora.vol") || "1"))) }), cost: parseFloat(localStorage.getItem(LS.cost) || "0"), }; state.audio.preload = "auto"; @@ -633,13 +637,13 @@ function makeCrackle() { 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); dv.setUint32(24, sr, true); dv.setUint32(28, sr * 2, true); dv.setUint16(32, 2, true); dv.setUint16(34, 16, true); str(36, "data"); dv.setUint32(40, n * 2, true); new Int16Array(buf, 44).set(out); - const el = new Audio(URL.createObjectURL(new Blob([buf], { type: "audio/wav" }))); el.loop = true; el.volume = 0.45; el.preload = "auto"; + const el = new Audio(URL.createObjectURL(new Blob([buf], { type: "audio/wav" }))); el.loop = true; el.volume = crackle.base = parseFloat(localStorage.getItem("gandora.bed") || "0.45"); el.preload = "auto"; return el; } /* the bed: crackle whenever no music is playing. Decided from state, called whenever state changes. */ function bed() { const want = state.primed && !state.playing && !state.holding; if (want) crackleOn(); else crackleOff(); } -function crackleOn() { if (crackle.on || state.holding) return; crackle.el ||= makeCrackle(); crackle.on = true; crackle.el.currentTime = 0; crackle.el.play().catch(() => {}); } -function crackleOff() { if (!crackle.on) return; crackle.on = false; const el = crackle.el, t0 = el.volume; let v = t0; const fade = setInterval(() => { v -= 0.04; if (v <= 0 || crackle.on) { clearInterval(fade); if (!crackle.on) el.pause(); el.volume = t0; } else el.volume = v; }, 40); } +function crackleOn() { if (crackle.on || state.holding) return; crackle.el ||= makeCrackle(); crackle.on = true; crackle.el.volume = crackle.base ?? crackle.el.volume; crackle.el.currentTime = 0; crackle.el.play().catch(() => {}); } +function crackleOff() { if (!crackle.on) return; crackle.on = false; const el = crackle.el, t0 = crackle.base ?? el.volume; let v = el.volume; const fade = setInterval(() => { v -= 0.04; if (v <= 0 || crackle.on) { clearInterval(fade); if (!crackle.on) el.pause(); el.volume = t0; } else el.volume = v; }, 40); } /* 50 ms of silence as a wav, built here rather than pasted */ function wavBlob(samples, sr) { const n = samples.length, buf = new ArrayBuffer(44 + n * 2), dv = new DataView(buf); @@ -921,6 +925,14 @@ async function stationSheet(id) { palRow.append(b); } p.append(palRow); + // volume and room tone, for everything (not just this station) + for (const [label, key, get, set] of [["volume", "gandora.vol", () => state.audio.volume, (v) => { state.audio.volume = v; }], ["room tone", "gandora.bed", () => (crackle.el ||= makeCrackle()).volume, (v) => { (crackle.el ||= makeCrackle()).volume = v; crackle.base = v; }]]) { + const sl = document.createElement("div"); sl.className = "sl"; + const r = document.createElement("input"); r.type = "range"; r.min = 0; r.max = 100; r.value = Math.round(get() * 100); + const out = Object.assign(document.createElement("span"), { textContent: r.value + "%" }); + r.addEventListener("input", () => { set(r.value / 100); out.textContent = r.value + "%"; localStorage.setItem(key, String(r.value / 100)); }); + sl.append(Object.assign(document.createElement("label"), { textContent: label }), r, out); p.append(sl); + } if (state.stationId === id) { const more = document.createElement("div"); more.className = "row"; more.style.marginTop = "10px"; more.style.justifyContent = "flex-start"; const fromT = Object.assign(document.createElement("button"), { textContent: "* STATION FROM THIS TRACK", title: "a new station grown from the track playing now" });