feat(ui): numpad, audio readout, and sequence beat controls

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-17 18:32:12 +12:00
parent 964cfc6d91
commit c286e504eb
9 changed files with 779 additions and 159 deletions

View File

@@ -1,14 +1,98 @@
// Sequences: lanes (parallel preset chains); advance is always by audio beats or simulated BPM.
// Debug: in the browser console run setSequenceDebug(true) — toggling logs 1 (on) or 0 (off).
// Debug: in the browser console run setSequenceDebug(true) — session only, not persisted.
const SEQ_DEBUG_STORAGE_KEY = 'led-controller-sequence-debug';
/** @type {'beat'|'downbeat'} */
let sequenceSwitchWaitFor = 'beat';
let sequenceDebugEnabled = false;
let sequenceSwitchSaveInFlight = false;
async function loadSequenceSwitchWaitForFromServer() {
try {
const res = await fetch('/settings', {
cache: 'no-store',
headers: { Accept: 'application/json' },
});
if (!res.ok) return;
const data = await res.json();
const raw = data && data.sequence_switch_wait;
if (raw === 'downbeat' || raw === 'beat') {
sequenceSwitchWaitFor = raw;
} else if (raw === 'phrase') {
sequenceSwitchWaitFor = 'beat';
}
} catch {
/* keep default */
}
}
async function persistSequenceSwitchWaitFor() {
sequenceSwitchSaveInFlight = true;
try {
const res = await fetch('/settings', {
method: 'PUT',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify({ sequence_switch_wait: sequenceSwitchWaitFor }),
});
if (!res.ok) {
console.warn('[sequence] could not save switch wait to server', res.status);
}
} catch (e) {
console.warn('[sequence] could not save switch wait to server', e);
} finally {
sequenceSwitchSaveInFlight = false;
}
}
function getSequenceSwitchWaitFor() {
return sequenceSwitchWaitFor === 'downbeat' ? 'downbeat' : 'beat';
}
async function setSequenceSwitchWaitFor(waitFor) {
sequenceSwitchWaitFor = waitFor === 'downbeat' ? 'downbeat' : 'beat';
updateSequenceSwitchToggleUI();
await persistSequenceSwitchWaitFor();
}
function updateSequenceSwitchToggleUI() {
const mode = getSequenceSwitchWaitFor();
const ariaLabels = {
beat: 'Switch sequence on beat',
downbeat: 'Switch sequence on downbeat',
};
document.querySelectorAll('.seq-switch-toggle').forEach((btn) => {
btn.setAttribute('aria-pressed', mode === 'beat' ? 'false' : 'true');
btn.setAttribute('aria-label', ariaLabels[mode] || ariaLabels.beat);
btn.classList.toggle('seq-switch-toggle--downbeat', mode === 'downbeat');
});
document.querySelectorAll('.seq-switch-toggle-wrap').forEach((wrap) => {
wrap.classList.toggle('nav-slide-toggle-wrap--downbeat', mode === 'downbeat');
});
}
async function initSequenceSwitchToggle() {
await loadSequenceSwitchWaitForFromServer();
updateSequenceSwitchToggleUI();
document.querySelectorAll('.seq-switch-toggle').forEach((btn) => {
btn.addEventListener('click', () => {
void setSequenceSwitchWaitFor(getSequenceSwitchWaitFor() === 'beat' ? 'downbeat' : 'beat');
});
});
}
/** Sync toggle when settings changed elsewhere (e.g. another tab via audio status poll). */
function applySequenceSwitchWaitFromServer(raw) {
if (sequenceSwitchSaveInFlight) return;
let mode = 'beat';
if (raw === 'downbeat') mode = 'downbeat';
else if (raw !== 'beat' && raw !== 'phrase') return;
if (mode === getSequenceSwitchWaitFor()) return;
sequenceSwitchWaitFor = mode;
updateSequenceSwitchToggleUI();
}
function seqDebugEnabled() {
try {
return localStorage.getItem(SEQ_DEBUG_STORAGE_KEY) === '1';
} catch {
return false;
}
return sequenceDebugEnabled;
}
/** @type {ReturnType<typeof setInterval> | null} */
@@ -1120,15 +1204,11 @@ async function loadSequencesModalList() {
});
}
window.applySequenceSwitchWaitFromServer = applySequenceSwitchWaitFromServer;
window.stopZoneSequencePlayback = stopZoneSequencePlayback;
/** @param {boolean} on */
window.setSequenceDebug = function setSequenceDebug(on) {
try {
if (on) localStorage.setItem(SEQ_DEBUG_STORAGE_KEY, '1');
else localStorage.removeItem(SEQ_DEBUG_STORAGE_KEY);
} catch (e) {
console.warn('[sequence] could not persist debug flag', e);
}
sequenceDebugEnabled = !!on;
console.log(seqDebugEnabled() ? 1 : 0);
};
window.appendZoneSequenceTiles = appendZoneSequenceTiles;
@@ -1137,6 +1217,7 @@ window.addSequenceToTab = addSequenceToTab;
window.removeSequenceFromTab = removeSequenceFromTab;
document.addEventListener('DOMContentLoaded', () => {
void initSequenceSwitchToggle();
const btn = document.getElementById('sequences-btn');
const modal = document.getElementById('sequences-modal');
const closeBtn = document.getElementById('sequences-close-btn');