|
|
const runStateEl = document.getElementById("runState");
|
|
|
|
|
|
// Call this from script.js when state changes if you want
|
|
|
/*window.setRunningState = function (state) {
|
|
|
running = state;
|
|
|
updateRunState();
|
|
|
};*/
|
|
|
|
|
|
function updateRunState() {
|
|
|
runStateEl.textContent = running ? "running" : "stopped";
|
|
|
runStateEl.classList.toggle("running", running);
|
|
|
|
|
|
startBtn.textContent = running ? "Stop" : "Start";
|
|
|
stepBtn.disabled = running;
|
|
|
}
|
|
|
|
|
|
// Fallback if script.js doesn’t call setRunningState
|
|
|
startBtn.addEventListener("click", () => {
|
|
|
updateRunState();
|
|
|
});
|
|
|
|
|
|
updateRunState();
|
|
|
|
|
|
|
|
|
// ---------- Live slider values ----------
|
|
|
|
|
|
function bindRange(id, suffix = "") {
|
|
|
const input = document.getElementById(id);
|
|
|
const output = document.getElementById(id.replace("Range", "Value"));
|
|
|
|
|
|
const update = () => {
|
|
|
output.textContent = input.value + suffix;
|
|
|
};
|
|
|
|
|
|
input.addEventListener("input", update);
|
|
|
update();
|
|
|
}
|
|
|
|
|
|
bindRange("speedRange", " ms");
|
|
|
bindRange("cellSizeRange", " px");
|
|
|
bindRange("baseFrequencyRange", " Hz");
|
|
|
bindRange("volumeRange", "%");
|
|
|
|
|
|
|
|
|
// ---------- Music mode hint ----------
|
|
|
|
|
|
const modeHints = {
|
|
|
additive: "Each active cell adds a tone to the mix.",
|
|
|
frequencyGrid: "Grid rows map to pitch, density to amplitude.",
|
|
|
granular: "Short sound grains triggered by cell activity.",
|
|
|
polyrhythm: "Independent rhythmic layers from cell clusters."
|
|
|
};
|
|
|
|
|
|
const modeSelect = document.getElementById("musicModeSelect");
|
|
|
const modeHint = document.getElementById("modeHint");
|
|
|
|
|
|
function updateModeHint() {
|
|
|
modeHint.textContent = modeHints[modeSelect.value] || "";
|
|
|
}
|
|
|
|
|
|
modeSelect.addEventListener("change", updateModeHint);
|
|
|
updateModeHint();
|
|
|
|
|
|
|
|
|
// ---------- Keyboard shortcuts ----------
|
|
|
|
|
|
window.addEventListener("keydown", e => {
|
|
|
if (e.target.tagName === "INPUT" || e.target.tagName === "SELECT") return;
|
|
|
|
|
|
switch (e.code) {
|
|
|
case "Space":
|
|
|
e.preventDefault();
|
|
|
startBtn.click();
|
|
|
break;
|
|
|
case "KeyR":
|
|
|
document.getElementById("randomBtn").click();
|
|
|
break;
|
|
|
case "KeyC":
|
|
|
document.getElementById("clearBtn").click();
|
|
|
break;
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// recording button handlers
|
|
|
document.getElementById("recordBtn").addEventListener("click", () => {
|
|
|
if (!lifeMusic) { console.warn("no synth"); return; }
|
|
|
window.recording.start(lifeMusic);
|
|
|
window.recording.visualize.start(lifeMusic.masterGain, lifeMusic.audioContext);
|
|
|
});
|
|
|
|
|
|
document.getElementById("stopRecordBtn").addEventListener("click", async () => {
|
|
|
running = false;
|
|
|
startBtn.textContent = 'Start';
|
|
|
|
|
|
if (timer) clearInterval(timer);
|
|
|
timer = null;
|
|
|
|
|
|
lifeMusic.stop();
|
|
|
lifeMusic.generateFromGrid(grid, cols, rows);
|
|
|
|
|
|
await window.recording.stopAndMerge("music-of-life.wav");
|
|
|
window.recording.visualize.stop();
|
|
|
});
|