Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dhu3iHseRewQ2WpBSVLokG
40 lines
2.7 KiB
JavaScript
40 lines
2.7 KiB
JavaScript
/* ============================================================
|
|
gandora — a generative station.
|
|
seed → DJ (Venice chat) → ACE-Step (Venice /audio/queue) → player.
|
|
Every cover is drawn from the song's own settings: hue from key,
|
|
rings from BPM, density from energy, symmetry from the seed.
|
|
============================================================ */
|
|
export const DIRECT = "https://api.venice.ai/api/v1"; // with a personal key
|
|
export const SHARED = "https://inference.yolopreneur.lol/v1"; // without one: the factory's worker, quota'd per person
|
|
export const $ = (s) => document.querySelector(s);
|
|
export const LS = { key: "gandora.key", dj: "gandora.dj", cost: "gandora.cost" };
|
|
export const state = {
|
|
key: localStorage.getItem(LS.key) || "",
|
|
dj: localStorage.getItem(LS.dj) || "claude-haiku-5-5",
|
|
seed: "", station: "", stationId: "", sinceAd: 0,
|
|
taste: { like: [], avoid: [], notes: [] }, // what the listener has told the station
|
|
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: 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";
|
|
/* hooks: the seams something loaded after the page (brain.js) plugs into. Each is null or a function; the page calls
|
|
them at the named moments. Assign, don't override the page's functions. */
|
|
export const hooks = {
|
|
djPrompt: null, // (prompt) => prompt the DJ's prompt, to add to (brain.js adds the taste text)
|
|
jobMade: null, // (spec) a sheet about to be rendered (brain.js writes spec.guess)
|
|
stationOpened: null, // (seed, opts) after openStation finished
|
|
reacted: null, // (emoji, text) after a reaction was taken in
|
|
};
|
|
|
|
/* ---------- small utils ---------- */
|
|
export const toast = (m, ms = 2400) => { const t = $("#toast"); t.textContent = m; t.classList.add("on"); clearTimeout(toast.h); toast.h = setTimeout(() => t.classList.remove("on"), ms); };
|
|
export const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
|
export const asDataUrl = (blob) => new Promise((ok, no) => { const r = new FileReader(); r.onload = () => ok(r.result); r.onerror = () => no(r.error); r.readAsDataURL(blob); });
|
|
|
|
export const ids = { seq: 0 }; // job ids; brain.js reaches it as window.seq
|
|
|
|
export const mmss = (s) => `${Math.floor(s / 60)}:${String(Math.floor(s % 60)).padStart(2, "0")}`;
|