Feature: blackout to emphasize music drops and calm sections
This commit is contained in:
parent
8b2d1aed53
commit
72a2fe7de2
@ -103,6 +103,11 @@ export class ConfigUI extends SceneFeature {
|
|||||||
rightContainer.appendChild(row);
|
rightContainer.appendChild(row);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Blackout Toggle
|
||||||
|
createToggle('BLACKOUT', 'blackout', (enabled) => {
|
||||||
|
state.blackoutMode = enabled;
|
||||||
|
});
|
||||||
|
|
||||||
// Torches Toggle
|
// Torches Toggle
|
||||||
createToggle('Stage Torches', 'torchesEnabled');
|
createToggle('Stage Torches', 'torchesEnabled');
|
||||||
|
|
||||||
@ -475,6 +480,7 @@ export class ConfigUI extends SceneFeature {
|
|||||||
lightBarsEnabled: true,
|
lightBarsEnabled: true,
|
||||||
laserColorMode: 'RUNNING',
|
laserColorMode: 'RUNNING',
|
||||||
guestCount: 150,
|
guestCount: 150,
|
||||||
|
blackout: false,
|
||||||
djHat: 'None'
|
djHat: 'None'
|
||||||
};
|
};
|
||||||
for (const key in defaults) {
|
for (const key in defaults) {
|
||||||
|
|||||||
@ -186,6 +186,11 @@ export class MusicConsole extends SceneFeature {
|
|||||||
const beatIntensity = state.music.beatIntensity;
|
const beatIntensity = state.music.beatIntensity;
|
||||||
|
|
||||||
this.lights.forEach(light => {
|
this.lights.forEach(light => {
|
||||||
|
if (state.blackoutMode) {
|
||||||
|
light.mesh.material.color.copy(light.offColor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (time > light.nextToggle) {
|
if (time > light.nextToggle) {
|
||||||
// Toggle state
|
// Toggle state
|
||||||
light.active = !light.active;
|
light.active = !light.active;
|
||||||
@ -207,7 +212,7 @@ export class MusicConsole extends SceneFeature {
|
|||||||
|
|
||||||
// Update Front LED Array
|
// Update Front LED Array
|
||||||
if (this.frontLedMesh) {
|
if (this.frontLedMesh) {
|
||||||
if (!state.config.consoleRGBEnabled) {
|
if (!state.config.consoleRGBEnabled || state.blackoutMode) {
|
||||||
this.frontLedMesh.visible = false;
|
this.frontLedMesh.visible = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import sceneFeatureManager from './SceneFeatureManager.js';
|
|||||||
export class MusicVisualizer extends SceneFeature {
|
export class MusicVisualizer extends SceneFeature {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
this.lastStateChangeTime = 0;
|
||||||
|
this.lastBlackoutMode = false;
|
||||||
sceneFeatureManager.register(this);
|
sceneFeatureManager.register(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,6 +36,39 @@ export class MusicVisualizer extends SceneFeature {
|
|||||||
// This creates a very sharp spike for the torch flame effect
|
// This creates a very sharp spike for the torch flame effect
|
||||||
const measureProgress = (time % state.music.measureDuration) / state.music.measureDuration;
|
const measureProgress = (time % state.music.measureDuration) / state.music.measureDuration;
|
||||||
state.music.measurePulse = measureProgress < 0.2 ? Math.sin(measureProgress * Math.PI * 5) : 0;
|
state.music.measurePulse = measureProgress < 0.2 ? Math.sin(measureProgress * Math.PI * 5) : 0;
|
||||||
|
|
||||||
|
// --- Auto-Blackout Logic ---
|
||||||
|
// If blackout is active, monitor for loud events (The Drop) to disable it.
|
||||||
|
if (state.config.blackout) {
|
||||||
|
if (state.blackoutMode !== this.lastBlackoutMode) {
|
||||||
|
this.lastStateChangeTime = time;
|
||||||
|
this.lastBlackoutMode = state.blackoutMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeInState = time - this.lastStateChangeTime;
|
||||||
|
|
||||||
|
// Dynamic Thresholds
|
||||||
|
// Exit blackout: Start high 0.8 and decrease over time
|
||||||
|
let loudThreshold = Math.max(0.4, 0.8 - (timeInState * 0.05));
|
||||||
|
|
||||||
|
// Enter blackout: Start low and increase over time
|
||||||
|
let quietThreshold = Math.min(0.2, 0.05 + (timeInState * 0.01));
|
||||||
|
|
||||||
|
const beatThreshold = 0.8;
|
||||||
|
|
||||||
|
if (state.blackoutMode) {
|
||||||
|
if (state.music.loudness > loudThreshold && state.music.beatIntensity > beatThreshold) {
|
||||||
|
state.blackoutMode = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (state.music.loudness < quietThreshold) {
|
||||||
|
state.blackoutMode = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.blackoutMode = false;
|
||||||
|
this.lastBlackoutMode = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -257,6 +257,10 @@ export class ProjectionScreen extends SceneFeature {
|
|||||||
const beat = (state.music && state.music.beatIntensity) ? state.music.beatIntensity : 0.0;
|
const beat = (state.music && state.music.beatIntensity) ? state.music.beatIntensity : 0.0;
|
||||||
state.screenLight.intensity = state.originalScreenIntensity * (0.5 + beat * 0.5);
|
state.screenLight.intensity = state.originalScreenIntensity * (0.5 + beat * 0.5);
|
||||||
|
|
||||||
|
if (state.blackoutMode) {
|
||||||
|
state.screenLight.intensity = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Update color buffer
|
// Update color buffer
|
||||||
const colors = state.config.lightBarColors;
|
const colors = state.config.lightBarColors;
|
||||||
const colorCount = colors ? Math.min(colors.length, 16) : 0;
|
const colorCount = colors ? Math.min(colors.length, 16) : 0;
|
||||||
@ -277,6 +281,10 @@ export class ProjectionScreen extends SceneFeature {
|
|||||||
if (s.mesh.material.uniforms.u_colorCount) {
|
if (s.mesh.material.uniforms.u_colorCount) {
|
||||||
s.mesh.material.uniforms.u_colorCount.value = colorCount;
|
s.mesh.material.uniforms.u_colorCount.value = colorCount;
|
||||||
}
|
}
|
||||||
|
if (s.mesh.material.uniforms.u_opacity) {
|
||||||
|
const targetOpacity = state.blackoutMode ? 0.1 : state.screenOpacity;
|
||||||
|
s.mesh.material.uniforms.u_opacity.value = targetOpacity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -463,6 +471,11 @@ export function updateScreenEffect() {
|
|||||||
material.uniforms.u_time.value = state.clock.getElapsedTime();
|
material.uniforms.u_time.value = state.clock.getElapsedTime();
|
||||||
material.uniforms.u_effect_type.value = state.screenEffect.type;
|
material.uniforms.u_effect_type.value = state.screenEffect.type;
|
||||||
material.uniforms.u_effect_strength.value = strength;
|
material.uniforms.u_effect_strength.value = strength;
|
||||||
|
|
||||||
|
if (material.uniforms.u_opacity) {
|
||||||
|
const targetOpacity = state.blackoutMode ? 0.1 : (state.screenOpacity !== undefined ? state.screenOpacity : 0.7);
|
||||||
|
material.uniforms.u_opacity.value = targetOpacity;
|
||||||
|
}
|
||||||
|
|
||||||
if (progress >= 1.0) {
|
if (progress >= 1.0) {
|
||||||
material.uniforms.u_effect_type.value = 0.0;
|
material.uniforms.u_effect_type.value = 0.0;
|
||||||
|
|||||||
@ -14,6 +14,8 @@ export class StageLasers extends SceneFeature {
|
|||||||
this.stateTimer = 0;
|
this.stateTimer = 0;
|
||||||
this.initialSilenceSeconds = 10;
|
this.initialSilenceSeconds = 10;
|
||||||
this.currentCycleMode = 'RUNNING';
|
this.currentCycleMode = 'RUNNING';
|
||||||
|
this.dummyColor = new THREE.Color();
|
||||||
|
this.areLasersVisible = true;
|
||||||
sceneFeatureManager.register(this);
|
sceneFeatureManager.register(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,14 +105,20 @@ export class StageLasers extends SceneFeature {
|
|||||||
|
|
||||||
update(deltaTime) {
|
update(deltaTime) {
|
||||||
if (!state.partyStarted) {
|
if (!state.partyStarted) {
|
||||||
this.lasers.forEach(l => l.mesh.visible = false);
|
if (this.areLasersVisible) {
|
||||||
|
this.lasers.forEach(l => l.mesh.visible = false);
|
||||||
|
this.areLasersVisible = false;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const time = state.clock.getElapsedTime();
|
const time = state.clock.getElapsedTime();
|
||||||
|
|
||||||
if (!state.config.lasersEnabled) {
|
if (!state.config.lasersEnabled || state.blackoutMode) {
|
||||||
this.lasers.forEach(l => l.mesh.visible = false);
|
if (this.areLasersVisible) {
|
||||||
|
this.lasers.forEach(l => l.mesh.visible = false);
|
||||||
|
this.areLasersVisible = false;
|
||||||
|
}
|
||||||
if (state.laserData) state.laserData.count = 0;
|
if (state.laserData) state.laserData.count = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -156,6 +164,7 @@ export class StageLasers extends SceneFeature {
|
|||||||
if (this.stateTimer <= 0) {
|
if (this.stateTimer <= 0) {
|
||||||
this.activationState = 'COOLDOWN';
|
this.activationState = 'COOLDOWN';
|
||||||
this.stateTimer = 4.0; // Cooldown duration
|
this.stateTimer = 4.0; // Cooldown duration
|
||||||
|
isActive = false;
|
||||||
}
|
}
|
||||||
} else if (this.activationState === 'COOLDOWN') {
|
} else if (this.activationState === 'COOLDOWN') {
|
||||||
this.stateTimer -= deltaTime;
|
this.stateTimer -= deltaTime;
|
||||||
@ -166,10 +175,18 @@ export class StageLasers extends SceneFeature {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
this.lasers.forEach(l => l.mesh.visible = false);
|
if (this.areLasersVisible) {
|
||||||
|
this.lasers.forEach(l => l.mesh.visible = false);
|
||||||
|
this.areLasersVisible = false;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.areLasersVisible) {
|
||||||
|
this.lasers.forEach(l => l.mesh.visible = true);
|
||||||
|
this.areLasersVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
// --- Pattern Logic ---
|
// --- Pattern Logic ---
|
||||||
if (time - this.lastPatternChange > 8) { // Change every 8 seconds
|
if (time - this.lastPatternChange > 8) { // Change every 8 seconds
|
||||||
this.pattern = (this.pattern + 1) % 4;
|
this.pattern = (this.pattern + 1) % 4;
|
||||||
@ -192,8 +209,6 @@ export class StageLasers extends SceneFeature {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.lasers.forEach(l => {
|
this.lasers.forEach(l => {
|
||||||
l.mesh.visible = !['IDLE', 'COOLDOWN'].includes(this.activationState);
|
|
||||||
|
|
||||||
let currentIntensity = intensity;
|
let currentIntensity = intensity;
|
||||||
let flareScale = 1.0;
|
let flareScale = 1.0;
|
||||||
|
|
||||||
@ -235,8 +250,8 @@ export class StageLasers extends SceneFeature {
|
|||||||
} else {
|
} else {
|
||||||
// Fallback if no palette: Cycle hue
|
// Fallback if no palette: Cycle hue
|
||||||
const hue = (time * 0.1) % 1;
|
const hue = (time * 0.1) % 1;
|
||||||
const c = new THREE.Color().setHSL(hue, 1.0, 0.5);
|
this.dummyColor.setHSL(hue, 1.0, 0.5);
|
||||||
colorHex = c.getHex();
|
colorHex = this.dummyColor.getHex();
|
||||||
}
|
}
|
||||||
|
|
||||||
l.mesh.material.color.set(colorHex);
|
l.mesh.material.color.set(colorHex);
|
||||||
@ -279,7 +294,10 @@ export class StageLasers extends SceneFeature {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onPartyEnd() {
|
onPartyEnd() {
|
||||||
this.lasers.forEach(l => l.mesh.visible = false);
|
if (this.areLasersVisible) {
|
||||||
|
this.lasers.forEach(l => l.mesh.visible = false);
|
||||||
|
this.areLasersVisible = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -114,6 +114,13 @@ export class StageLightBars extends SceneFeature {
|
|||||||
update(deltaTime) {
|
update(deltaTime) {
|
||||||
if (!state.partyStarted) return;
|
if (!state.partyStarted) return;
|
||||||
if (!state.config.lightBarsEnabled) return;
|
if (!state.config.lightBarsEnabled) return;
|
||||||
|
|
||||||
|
if (state.blackoutMode) {
|
||||||
|
this.bars.forEach(bar => {
|
||||||
|
bar.material.emissiveIntensity = 0;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const time = state.clock.getElapsedTime();
|
const time = state.clock.getElapsedTime();
|
||||||
const beatIntensity = state.music ? state.music.beatIntensity : 0;
|
const beatIntensity = state.music ? state.music.beatIntensity : 0;
|
||||||
|
|||||||
@ -136,7 +136,10 @@ export class StageLights extends SceneFeature {
|
|||||||
this.focusPoint.lerp(this.targetFocusPoint, deltaTime * 2.0);
|
this.focusPoint.lerp(this.targetFocusPoint, deltaTime * 2.0);
|
||||||
|
|
||||||
// Update each light
|
// Update each light
|
||||||
const intensity = state.music ? 20 + state.music.beatIntensity * 150 : 50;
|
let intensity = state.music ? 20 + state.music.beatIntensity * 150 : 50;
|
||||||
|
if (state.blackoutMode) {
|
||||||
|
intensity = 0;
|
||||||
|
}
|
||||||
|
|
||||||
const spread = 0.2 + (state.music ? state.music.beatIntensity * 0.4 : 0);
|
const spread = 0.2 + (state.music ? state.music.beatIntensity * 0.4 : 0);
|
||||||
const bounce = state.music ? state.music.beatIntensity * 0.5 : 0;
|
const bounce = state.music ? state.music.beatIntensity * 0.5 : 0;
|
||||||
|
|||||||
@ -104,7 +104,7 @@ export class StageTorches extends SceneFeature {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update(deltaTime) {
|
update(deltaTime) {
|
||||||
const enabled = state.config.torchesEnabled;
|
const enabled = state.config.torchesEnabled && !state.blackoutMode;
|
||||||
this.torches.forEach(torch => {
|
this.torches.forEach(torch => {
|
||||||
if (torch.group.visible !== enabled) torch.group.visible = enabled;
|
if (torch.group.visible !== enabled) torch.group.visible = enabled;
|
||||||
});
|
});
|
||||||
|
|||||||
@ -14,6 +14,7 @@ export function initState() {
|
|||||||
laserColorMode: 'RUNNING', // 'SINGLE', 'RANDOM', 'RUNNING', 'ANY'
|
laserColorMode: 'RUNNING', // 'SINGLE', 'RANDOM', 'RUNNING', 'ANY'
|
||||||
lightBarColors: ['#ff00ff', '#00ffff', '#ffff00'], // Default neon colors
|
lightBarColors: ['#ff00ff', '#00ffff', '#ffff00'], // Default neon colors
|
||||||
guestCount: 150,
|
guestCount: 150,
|
||||||
|
blackout: false,
|
||||||
djHat: 'None' // 'None', 'Santa', 'Top Hat'
|
djHat: 'None' // 'None', 'Santa', 'Top Hat'
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
@ -61,6 +62,7 @@ export function initState() {
|
|||||||
debugLight: false, // Turn on light helpers
|
debugLight: false, // Turn on light helpers
|
||||||
debugCamera: false, // Turn on camera helpers
|
debugCamera: false, // Turn on camera helpers
|
||||||
partyStarted: false,
|
partyStarted: false,
|
||||||
|
blackoutMode: false,
|
||||||
|
|
||||||
// Feature Configuration
|
// Feature Configuration
|
||||||
config: config,
|
config: config,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user