mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-09-11 03:24:30 +00:00
feat(venue-crowd): toggleable crowd sound reactions on mood transitions
Rank up → the venue's cheer sample, rank down → boos, gated behind the
feedBack-venue-crowd-sfx setting (default off), rate-limited by the
existing transition hysteresis and suppressed during the intro. Sounds
ship per-venue in the pack manifest (sfx: {up, down}).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
4abdfb5f5c
commit
523feb161e
@@ -34,6 +34,7 @@
|
|||||||
const STREAK_MILESTONES = [25, 50, 100];
|
const STREAK_MILESTONES = [25, 50, 100];
|
||||||
const CANPLAY_TIMEOUT_MS = 4000;
|
const CANPLAY_TIMEOUT_MS = 4000;
|
||||||
const DEV_FLAG_KEY = 'feedBack-venue-crowd-dev';
|
const DEV_FLAG_KEY = 'feedBack-venue-crowd-dev';
|
||||||
|
const SFX_KEY = 'feedBack-venue-crowd-sfx'; // 'on' | 'off' (default off)
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
// Pure, clock-injected decision logic (unit-tested in
|
// Pure, clock-injected decision logic (unit-tested in
|
||||||
@@ -144,7 +145,11 @@
|
|||||||
video: abs(m.intro && m.intro.video),
|
video: abs(m.intro && m.intro.video),
|
||||||
audio: abs(m.intro && m.intro.audio),
|
audio: abs(m.intro && m.intro.audio),
|
||||||
};
|
};
|
||||||
return { loops, stingers, intro };
|
const sfx = {
|
||||||
|
up: abs(m.sfx && m.sfx.up),
|
||||||
|
down: abs(m.sfx && m.sfx.down),
|
||||||
|
};
|
||||||
|
return { loops, stingers, intro, sfx };
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureVideos() {
|
function ensureVideos() {
|
||||||
@@ -410,6 +415,30 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _sfxEl = null;
|
||||||
|
|
||||||
|
function sfxEnabled() {
|
||||||
|
try { return localStorage.getItem(SFX_KEY) === 'on'; } catch (_) { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// One-shot crowd reaction on committed mood transitions (toggleable):
|
||||||
|
// up the ladder → cheer, down → boos. Committed transitions are already
|
||||||
|
// hysteresis-limited, so this can't spam.
|
||||||
|
function playMoodSfx(direction) {
|
||||||
|
if (!sfxEnabled() || !_manifest || !_manifest.sfx || _introActive) return;
|
||||||
|
const url = direction > 0 ? _manifest.sfx.up : _manifest.sfx.down;
|
||||||
|
if (!url || typeof document === 'undefined') return;
|
||||||
|
if (!_sfxEl) {
|
||||||
|
_sfxEl = document.createElement('audio');
|
||||||
|
_sfxEl.preload = 'auto';
|
||||||
|
_sfxEl.style.display = 'none';
|
||||||
|
document.body.appendChild(_sfxEl);
|
||||||
|
}
|
||||||
|
_sfxEl.src = url;
|
||||||
|
_sfxEl.volume = 0.6;
|
||||||
|
_sfxEl.play().catch(() => { /* pre-gesture; skip silently */ });
|
||||||
|
}
|
||||||
|
|
||||||
function onSongPlay() {
|
function onSongPlay() {
|
||||||
// Song audio starting is the hard cue: the ambience must yield.
|
// Song audio starting is the hard cue: the ambience must yield.
|
||||||
fadeAudioOut(1000);
|
fadeAudioOut(1000);
|
||||||
@@ -430,8 +459,10 @@
|
|||||||
if (sting && !_introActive && CROWD_RANK[machine.current] >= CROWD_RANK.neutral) {
|
if (sting && !_introActive && CROWD_RANK[machine.current] >= CROWD_RANK.neutral) {
|
||||||
playStinger(sting);
|
playStinger(sting);
|
||||||
}
|
}
|
||||||
|
const prevRank = CROWD_RANK[machine.current];
|
||||||
const next = machine.update(d.state, now());
|
const next = machine.update(d.state, now());
|
||||||
if (next) {
|
if (next) {
|
||||||
|
playMoodSfx(CROWD_RANK[next] - prevRank);
|
||||||
// A stinger or the intro owns the idle layer; defer the switch.
|
// A stinger or the intro owns the idle layer; defer the switch.
|
||||||
if (_stingerUntilEnded || _introActive) _pendingLoop = next;
|
if (_stingerUntilEnded || _introActive) _pendingLoop = next;
|
||||||
else showLoop(next, FADE_MS);
|
else showLoop(next, FADE_MS);
|
||||||
@@ -489,6 +520,7 @@
|
|||||||
_introGen++;
|
_introGen++;
|
||||||
_introActive = false;
|
_introActive = false;
|
||||||
stopAudio();
|
stopAudio();
|
||||||
|
if (_sfxEl && !_sfxEl.paused) _sfxEl.pause();
|
||||||
_stingerUntilEnded = false;
|
_stingerUntilEnded = false;
|
||||||
_pendingLoop = null;
|
_pendingLoop = null;
|
||||||
_loadingLoop = null;
|
_loadingLoop = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user