feat: Adding audio player + music to play
This commit is contained in:
+52
-5
@@ -26,6 +26,9 @@ import { createEndgame } from "./ui/endgame.js";
|
||||
import { createMenu } from "./ui/menu.js";
|
||||
import { createShelf } from "./ui/shelf.js";
|
||||
import { createAudio } from "./ui/audio.js";
|
||||
import { createMusic } from "./ui/music.js";
|
||||
import { createWalkman } from "./ui/walkman.js";
|
||||
import { DEFAULT_MIX } from "./save/storage.js";
|
||||
|
||||
const params = new URLSearchParams(location.search);
|
||||
const getJson = (url) => fetch(url).then((r) => r.json());
|
||||
@@ -54,7 +57,23 @@ let settings = loadSettings(storage);
|
||||
const browserLang = navigator.language?.startsWith("fr") ? "fr" : "en";
|
||||
await initI18n(params.get("lang") ?? settings.lang ?? browserLang);
|
||||
|
||||
const audio = createAudio({ enabled: () => settings.sound, volume: () => settings.volume });
|
||||
const audio = createAudio({ enabled: () => settings.sound, volume: () => settings.volume, mix: () => settings.mix });
|
||||
|
||||
// Background music from the music folder, driven by the cassette player on the desk.
|
||||
const music = createMusic({
|
||||
getVolume: () => settings.volume * settings.mix.music,
|
||||
onUserChoice: (on) => { if (settings.musicOn !== on) applySettings({ musicOn: on }); },
|
||||
});
|
||||
let gestured = false;
|
||||
const startMusic = () => gestured && music.unlockedByGesture(settings.musicOn);
|
||||
for (const ev of ["pointerdown", "keydown"]) {
|
||||
document.addEventListener(ev, (e) => {
|
||||
if (e.target.closest?.("#walkman")) return; // pressing a walkman key is its own choice
|
||||
gestured = true;
|
||||
startMusic();
|
||||
}, { passive: true });
|
||||
}
|
||||
music.load().then(startMusic);
|
||||
|
||||
const DEV_PARAMS = ["seed", "pronouns", "alloc", "skip", "card", "dev", "new"];
|
||||
const devRun = DEV_PARAMS.some((k) => params.has(k)) && !params.has("menu");
|
||||
@@ -79,9 +98,11 @@ const el = {
|
||||
news: document.getElementById("newspaper"),
|
||||
menu: document.getElementById("menu"),
|
||||
shelf: document.getElementById("shelf"),
|
||||
walkman: document.getElementById("walkman"),
|
||||
};
|
||||
const pad = document.getElementById("pad");
|
||||
for (const id of ["dash", "folder", "cal", "news"]) tilt(el[id], id);
|
||||
tilt(el.walkman, "walkman", 0.5);
|
||||
|
||||
// A new week brings a proposal to the tray; with none eligible the week is simply empty.
|
||||
function prepareWeek() {
|
||||
@@ -136,6 +157,7 @@ function build() {
|
||||
ledger: createLedger(el.ledger, scenario, { onConfirm: () => { audio.play("sign"); render(); } }),
|
||||
cal: createCalendar(el.cal, balance, { onSpeed, onMenu: openMenu }),
|
||||
shelf: createShelf(el.shelf, balance, { getView: () => view, onView: setView, onSpeed, onMenu: openMenu }),
|
||||
walkman: createWalkman(el.walkman, { music, onPress: () => audio.play("click") }),
|
||||
news: createNewspaper(el.news, newsTpl),
|
||||
alerts: createTelegram(el.alerts, { alerts: endgame.alerts, onDismiss }),
|
||||
endgame: createEndgame(el.endgame, { newsName: newsTpl.name, endgame, cards, onAgain: quitToMenu }),
|
||||
@@ -218,7 +240,7 @@ function quitToMenu() {
|
||||
panels?.decision.destroy();
|
||||
state = null;
|
||||
panels = null;
|
||||
for (const id of ["dash", "folder", "ledger", "alerts", "endgame", "cal", "news", "shelf"]) el[id].innerHTML = "";
|
||||
for (const id of ["dash", "folder", "ledger", "alerts", "endgame", "cal", "news", "shelf", "walkman"]) el[id].innerHTML = "";
|
||||
document.title = t("app.title");
|
||||
el.ledger.hidden = true;
|
||||
el.alerts.hidden = true;
|
||||
@@ -231,9 +253,12 @@ function quitToMenu() {
|
||||
|
||||
function applySettings(patch) {
|
||||
const langChanged = patch.lang && patch.lang !== getLang();
|
||||
settings = { ...settings, ...patch };
|
||||
const { resetMix, ...rest } = patch;
|
||||
settings = { ...settings, ...rest, mix: resetMix ? { ...DEFAULT_MIX } : { ...settings.mix, ...rest.mix } };
|
||||
if (resetMix) settings.volume = 0.6;
|
||||
saveSettings(storage, settings);
|
||||
if (patch.sound === true || patch.volume !== undefined) audio.play("click");
|
||||
if (patch.sound === true) audio.play("click");
|
||||
if (patch.volume !== undefined || patch.mix?.music !== undefined || resetMix) music.applyVolume();
|
||||
if (!langChanged) return;
|
||||
setLang(patch.lang);
|
||||
document.title = t("app.title");
|
||||
@@ -241,6 +266,12 @@ function applySettings(patch) {
|
||||
menu.refresh();
|
||||
}
|
||||
|
||||
// The sound heard when a mixer slider is released.
|
||||
const PREVIEW = {
|
||||
master: "chime", stamp: "stampYes", paper: "paper", page: "page", sign: "sign", telegram: "telegram", alert: "alert",
|
||||
chime: "chime", tick: "tick", countdown: "clockTick", click: "click", ending: "fanfare",
|
||||
};
|
||||
|
||||
const menu = createMenu(el.menu, {
|
||||
content,
|
||||
storage,
|
||||
@@ -252,6 +283,7 @@ const menu = createMenu(el.menu, {
|
||||
onResume: closeMenu,
|
||||
onQuit: quitToMenu,
|
||||
onSettings: applySettings,
|
||||
onPreview: (key) => audio.play(PREVIEW[key] ?? "click"),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -345,7 +377,7 @@ function step(now) {
|
||||
if (!r.advanced) break;
|
||||
advanced = true;
|
||||
if (r.events.weeklyCard) prepareWeek();
|
||||
if (!document.hidden && state.speed <= 2) audio.play(state.day % 2 ? "tick" : "tock");
|
||||
if (!document.hidden && state.speed <= 2 && !isDayBeforeIssue()) audio.play(state.day % 2 ? "tick" : "tock");
|
||||
if (isWaiting(state) || state.over) break;
|
||||
}
|
||||
if (advanced) render();
|
||||
@@ -355,12 +387,27 @@ function step(now) {
|
||||
const devMsPerDay = devRun && params.has("msPerDay") ? Number(params.get("msPerDay")) : 0; // dev: faster days for tests
|
||||
const msPerDay = () => devMsPerDay || (settings.slow ? balance.time.slowMsPerDay : balance.time.msPerDay);
|
||||
|
||||
// The day before a new proposal arrives (the Sunday before each Monday), a small clock ticks while that day runs.
|
||||
const isDayBeforeIssue = () => (state.day + 1) % balance.time.daysPerWeek === 0;
|
||||
let lastCountdown = "";
|
||||
|
||||
function countdownTicks(p) {
|
||||
if (!state || menuOpen || state.speed <= 0 || state.over || isWaiting(state) || document.hidden || !isDayBeforeIssue()) return;
|
||||
const perDay = state.speed >= 4 ? 10 : 20; // the ticks follow the dial: faster at higher speeds
|
||||
const n = Math.floor(p * perDay);
|
||||
const key = `${state.day}:${n}`;
|
||||
if (key === lastCountdown) return;
|
||||
lastCountdown = key;
|
||||
audio.play(n % 2 ? "clockTock" : "clockTick");
|
||||
}
|
||||
|
||||
function frame(now) {
|
||||
step(now);
|
||||
if (panels) {
|
||||
const p = Math.min(1, clock.acc / msPerDay());
|
||||
panels.cal.clockHand(p);
|
||||
panels.shelf.clockHand(p);
|
||||
countdownTicks(p);
|
||||
}
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user