Port Auto VJ family-pool modes (verified: custom pool respected on the Pi)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2f6999996d
commit
3696669a1e
4 files changed
+58
-6
No files matched your search
@@ -37,6 +37,10 @@ button.wide { width: 100%; margin-top: 10px; font-size: 15px; }
|
||||
#audio-src { width: 100%; }
|
||||
button.danger { border-color: rgba(255,120,120,.5); color: #ffb3b3; background: rgba(255,80,80,.10); }
|
||||
#auto-vj.vj-on { background: #2e7d32; border-color: #4caf50; color: #fff; }
|
||||
.av-fams { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0; }
|
||||
.av-fams.hidden { display: none; }
|
||||
.av-fam { font-size: 11px; padding: 6px 10px; border-radius: 9px; opacity: .7; }
|
||||
.av-fam.on { border-color: #4caf50; background: rgba(76,175,80,.18); color: #d9ffd9; opacity: 1; }
|
||||
button.danger.armed { background: #c0392b; color: #fff; border-color: #c0392b; animation: pulse 1s infinite; }
|
||||
@keyframes pulse { 50% { filter: brightness(1.35); } }
|
||||
input[type=range] { -webkit-appearance: none; appearance: none; background: transparent; padding: 0; height: 34px; border: none; }
|
||||
|
||||
+9
-1
@@ -21,7 +21,15 @@
|
||||
<main>
|
||||
<!-- EFFETTI -->
|
||||
<section class="panel" data-tab="fx">
|
||||
<button id="auto-vj" class="wide" title="L'Auto VJ ascolta il brano (energia, BPM, drop) e cambia effetto da solo a tempo di musica">🤖 Auto VJ</button>
|
||||
<div class="row">
|
||||
<button id="auto-vj" title="L'Auto VJ ascolta il brano (energia, BPM, drop) e cambia effetto da solo a tempo di musica">🤖 Auto VJ</button>
|
||||
<select id="av-mode" title="Da quali famiglie pesca l'Auto VJ">
|
||||
<option value="smart">🎩 Smart (mood)</option>
|
||||
<option value="all">🌐 Tutte</option>
|
||||
<option value="custom">🎯 Scelte…</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="av-fams" class="av-fams hidden"></div>
|
||||
<div class="row">
|
||||
<select id="fx-fam"><option value="-1">Tutte le famiglie</option></select>
|
||||
<input id="fx-search" placeholder="Cerca effetto…" />
|
||||
|
||||
+31
-2
@@ -22,13 +22,42 @@ let curEffect = EFFECTS.list[0];
|
||||
function applyEffect(e) { curEffect = e; send({ type: 'effect', effect: e }); $('#now').textContent = e.name; renderFx(); }
|
||||
|
||||
// Auto VJ: the output-side director picks presets in time with the music.
|
||||
// avMode: 'smart' = curated mood pools, 'all' = every family, 'custom' = the
|
||||
// user's chip selection. Persisted on the device via saveState.
|
||||
let autoVjOn = false;
|
||||
$('#auto-vj').addEventListener('click', () => send({ type: 'autoVj', on: !autoVjOn }));
|
||||
let avCfg = { mode: 'smart', families: [] };
|
||||
function avSend(on) { send({ type: 'autoVj', on, mode: avCfg.mode, families: avCfg.families }); }
|
||||
function avSave() { djv.saveState('autovj', avCfg); if (autoVjOn) avSend(true); }
|
||||
function renderAvFams() {
|
||||
const box = $('#av-fams');
|
||||
box.classList.toggle('hidden', avCfg.mode !== 'custom');
|
||||
if (avCfg.mode !== 'custom') return;
|
||||
box.innerHTML = '';
|
||||
EFFECTS.families.forEach((name) => {
|
||||
const b = document.createElement('button');
|
||||
b.className = 'av-fam' + (avCfg.families.includes(name) ? ' on' : '');
|
||||
b.textContent = name;
|
||||
b.addEventListener('click', () => {
|
||||
avCfg.families = avCfg.families.includes(name)
|
||||
? avCfg.families.filter((f) => f !== name)
|
||||
: avCfg.families.concat(name);
|
||||
b.classList.toggle('on');
|
||||
avSave();
|
||||
});
|
||||
box.appendChild(b);
|
||||
});
|
||||
}
|
||||
$('#av-mode').addEventListener('change', (e) => { avCfg.mode = e.target.value; renderAvFams(); avSave(); });
|
||||
djv.loadState('autovj').then((s) => {
|
||||
if (s && s.mode) avCfg = { mode: s.mode, families: s.families || [] };
|
||||
$('#av-mode').value = avCfg.mode; renderAvFams();
|
||||
});
|
||||
$('#auto-vj').addEventListener('click', () => avSend(!autoVjOn));
|
||||
function setAutoVj(m) {
|
||||
autoVjOn = !!m.on;
|
||||
const b = $('#auto-vj');
|
||||
b.classList.toggle('vj-on', autoVjOn);
|
||||
b.textContent = autoVjOn ? '🤖 Auto VJ ● attivo' : '🤖 Auto VJ';
|
||||
b.textContent = autoVjOn ? '🤖 Auto VJ ●' : '🤖 Auto VJ';
|
||||
if (autoVjOn && m.name) $('#now').textContent = m.name + (m.bpm ? ' · ' + m.bpm + ' BPM' : '');
|
||||
}
|
||||
function renderFx() {
|
||||
|
||||
+14
-3
@@ -321,6 +321,8 @@ djv.onControl(async (m) => {
|
||||
break;
|
||||
case 'autoVj':
|
||||
autoVj = !!m.on;
|
||||
if (m.mode) avMode = m.mode;
|
||||
avFamilies = Array.isArray(m.families) ? m.families : avFamilies;
|
||||
if (autoVj) { avLastSwitch = 0; avBeatMark = audio.beatCount || 0; }
|
||||
djv.report({ type: 'autoVj', on: autoVj });
|
||||
break;
|
||||
@@ -498,10 +500,19 @@ const AV_POOLS = {
|
||||
peak: ['Julia', 'Iperspazio', 'Tunnel', 'Moiré', 'Fluido Anello', 'Fluido Fuoco',
|
||||
'Vortice', 'Griglia Neon', 'Cristalli']
|
||||
};
|
||||
let avMode = 'smart', avFamilies = []; // 'smart' | 'all' | 'custom'
|
||||
function avPick(mood) {
|
||||
const fams = AV_POOLS[mood] || AV_POOLS.groove;
|
||||
const cands = window.EFFECTS.list.filter(e => fams.indexOf(e.familyName) >= 0 &&
|
||||
(mood === 'peak' ? e.speed >= 1.0 : e.speed <= 1.4) && avRecent.indexOf(e.name) < 0);
|
||||
// Family pool: smart = curated per-mood lists; custom = the user's checklist
|
||||
// (mood still shapes the speed variant); all = no family restriction.
|
||||
let fams = null;
|
||||
if (avMode === 'smart') fams = AV_POOLS[mood] || AV_POOLS.groove;
|
||||
else if (avMode === 'custom' && avFamilies.length) fams = avFamilies;
|
||||
const speedOk = (e) => (mood === 'peak' ? e.speed >= 1.0 : e.speed <= 1.4);
|
||||
let cands = window.EFFECTS.list.filter(e => (!fams || fams.indexOf(e.familyName) >= 0) &&
|
||||
speedOk(e) && avRecent.indexOf(e.name) < 0);
|
||||
// A tight custom list can exhaust the speed filter: retry without it.
|
||||
if (!cands.length) cands = window.EFFECTS.list.filter(e =>
|
||||
(!fams || fams.indexOf(e.familyName) >= 0) && avRecent.indexOf(e.name) < 0);
|
||||
const e = cands.length ? cands[Math.floor(Math.random() * cands.length)] : window.EFFECTS.list[0];
|
||||
avRecent = [e.name].concat(avRecent).slice(0, 6); // don't repeat the last few
|
||||
return e;
|
||||
|
||||
Reference in new issue
Block a user