Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(light-effects): add support for grids of light fixtures #34

Merged
merged 10 commits into from
Jan 16, 2025
10 changes: 10 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
root = true

[*]
end_of_line = lf
insert_final_newline = true

[*.{js,jsx,ts,tsx,json,yml}]
charset = utf-8
indent_style = space
indent_size = 2
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ async function createApp(): Promise<void> {

const emitterStore = EmitterStore.getInstance();

ArtificialBeatGenerator.getInstance().init(emitterStore.musicEmitter);

const handlerManager = HandlerManager.getInstance(io, emitterStore);
await handlerManager.init();
const socketConnectionManager = new SocketConnectionManager(
Expand All @@ -64,7 +66,6 @@ async function createApp(): Promise<void> {
);

ModeManager.getInstance().init(emitterStore);
ArtificialBeatGenerator.getInstance().init(emitterStore.musicEmitter);

if (
process.env.SPOTIFY_ENABLE === 'true' &&
Expand Down
44 changes: 41 additions & 3 deletions src/modules/handlers/lights/develop-effects-handler.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,51 @@
import EffectsHandler from './effects-handler';
import { LightsGroup } from '../../lights/entities';
import { RgbColor } from '../../lights/color-definitions';
import { Fire, StaticColor } from '../../lights/effects/color';
import { Wave } from '../../lights/effects/color';
import TableRotate from '../../lights/effects/movement/table-rotate';
import logger from '../../../logger';
import { ArtificialBeatGenerator } from '../../beats/artificial-beat-generator';
import {
LightsEffectDirection,
LightsEffectPattern,
} from '../../lights/effects/lights-effect-pattern';

export default class DevelopEffectsHandler extends EffectsHandler {
public registerEntity(entity: LightsGroup) {
if (this.entities.length === 0) {
ArtificialBeatGenerator.getInstance().start(120);
}

super.registerEntity(entity);
this.groupColorEffects.set(entity, [new Fire(entity, {})]);
this.groupMovementEffects.set(entity, [new TableRotate(entity, {})]);
setTimeout(() => {
logger.info(`Set develop effects for ${entity.name}`);
this.groupColorEffects.set(entity, [
// new BeatFadeOut(entity, {
// colors: [
// RgbColor.ORANGE,
// RgbColor.BLUE,
// // RgbColor.GREEN,
// // RgbColor.YELLOW,
// // RgbColor.LIGHTPINK,
// ],
// nrBlacks: 1,
// }),
new Wave(entity, {
colors: [RgbColor.ORANGE],
nrWaves: 1,
pattern: LightsEffectPattern.HORIZONTAL,
direction: LightsEffectDirection.BACKWARDS,
}),
]);
this.groupMovementEffects.set(entity, [new TableRotate(entity, {})]);
}, 3000);
}

public removeEntity(entityCopy: LightsGroup) {
super.removeEntity(entityCopy);

if (this.entities.length === 0) {
ArtificialBeatGenerator.getInstance().stop();
}
}
}
2 changes: 1 addition & 1 deletion src/modules/handlers/lights/random-effects-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default class RandomEffectsHandler extends EffectsHandler {
),
);
} else if (random < 0.9) {
this.groupColorEffects.set(entity, new Wave(entity, { color: this.colors[0] }));
this.groupColorEffects.set(entity, new Wave(entity, { colors: this.colors }));
} else {
this.groupColorEffects.set(entity, new Sparkle(entity, { colors: this.colors }));
}
Expand Down
85 changes: 55 additions & 30 deletions src/modules/lights/effects/color/beat-fade-out.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import LightsEffect, { BaseLightsEffectCreateParams, LightsEffectBuilder } from '../lights-effect';
import LightsEffect, {
BaseLightsEffectCreateParams,
BaseLightsEffectProgressionProps,
BaseLightsEffectProps,
LightsEffectBuilder,
} from '../lights-effect';
import { BeatEvent, TrackPropertiesEvent } from '../../../events/music-emitter-events';
import {
LightsGroup,
Expand All @@ -8,13 +13,16 @@ import {
} from '../../entities';
import { RgbColor } from '../../color-definitions';
import { ColorEffects } from './color-effects';

export interface BeatFadeOutProps {
/**
* One or more colors that should be shown
*/
colors: RgbColor[];

import {
EffectProgressionBeatStrategy,
EffectProgressionTickStrategy,
} from '../progression-strategies';
import EffectProgressionStrategy from '../progression-strategies/effect-progression-strategy';
import LightsGroupFixture from '../../entities/lights-group-fixture';
import { LightsEffectDirection, LightsEffectPattern } from '../lights-effect-pattern';
import EffectProgressionMapFactory from '../progression-strategies/mappers/effect-progression-map-factory';

export interface BeatFadeOutProps extends BaseLightsEffectProps, BaseLightsEffectProgressionProps {
/**
* Whether the lights should be turned off using a fade effect
* on each beat
Expand Down Expand Up @@ -43,14 +51,32 @@ export type BeatFadeOutCreateParams = BaseLightsEffectCreateParams & {
};

export default class BeatFadeOut extends LightsEffect<BeatFadeOutProps> {
private phase = 0;
private readonly nrSteps: number;

private lastBeat = new Date().getTime(); // in ms since epoch;

private beatLength: number = 1; // in ms;

constructor(lightsGroup: LightsGroup, props: BeatFadeOutProps, features?: TrackPropertiesEvent) {
super(lightsGroup, features);
const nrSteps = props.colors.length + (props.nrBlacks ?? 0);

const progressionMapperStrategy = new EffectProgressionMapFactory(lightsGroup).getMapper(
props.pattern,
nrSteps,
);

let progressionStrategy: EffectProgressionStrategy;
if (props.customCycleTime) {
progressionStrategy = new EffectProgressionTickStrategy(props.customCycleTime);
} else {
progressionStrategy = new EffectProgressionBeatStrategy(
progressionMapperStrategy.getNrFixtures(),
);
}

super(lightsGroup, progressionStrategy, progressionMapperStrategy, props.direction, features);

this.nrSteps = nrSteps;
this.props = props;

if (this.props.customCycleTime) {
Expand All @@ -72,21 +98,22 @@ export default class BeatFadeOut extends LightsEffect<BeatFadeOutProps> {
destroy(): void {}

beat(event: BeatEvent): void {
super.beat(event);

// If we use a custom cycle time, ignore all beats
if (this.props.customCycleTime) return;

this.lastBeat = new Date().getTime();
this.beatLength = event.beat.duration * 1000;
this.phase = (this.phase + 1) % (this.props.colors.length + (this.props.nrBlacks ?? 0));
}

getCurrentColor(i: number) {
const { colors, nrBlacks } = this.props;
const nrColors = colors.length + (nrBlacks || 0);
const index = (i + this.phase) % nrColors;
if (index === colors.length) {
return null;
}
getCurrentColor(fixture: LightsGroupFixture, i: number) {
const { colors } = this.props;
const progression = this.getProgression(new Date(), fixture);

const phase = progression * this.getEffectNrFixtures();
const index = Math.round(phase % this.nrSteps);

return colors[index];
}

Expand All @@ -99,7 +126,7 @@ export default class BeatFadeOut extends LightsEffect<BeatFadeOutProps> {
? Math.max(1 - (new Date().getTime() - this.lastBeat) / this.beatLength, 0)
: 1;

const color = this.getCurrentColor(i);
const color = this.getCurrentColor(p, i);
if (color == null) {
p.fixture.setMasterDimmer(0);
} else {
Expand All @@ -109,17 +136,15 @@ export default class BeatFadeOut extends LightsEffect<BeatFadeOutProps> {
}

tick(): LightsGroup {
if (this.props.customCycleTime) {
const now = new Date().getTime();
const msDiff = now - this.lastBeat;
if (msDiff >= this.props.customCycleTime) {
this.lastBeat = now;
this.phase = (this.phase + 1) % (this.props.colors.length + (this.props.nrBlacks ?? 0));
}
}

this.lightsGroup.pars.forEach(this.applyColorToFixture.bind(this));
this.lightsGroup.movingHeadRgbs.forEach(this.applyColorToFixture.bind(this));
super.tick();

[
...this.lightsGroup.pars,
...this.lightsGroup.movingHeadRgbs,
...this.lightsGroup.movingHeadWheels,
]
.sort((a, b) => a.positionX - b.positionX)
.forEach(this.applyColorToFixture.bind(this));

return this.lightsGroup;
}
Expand Down
2 changes: 1 addition & 1 deletion src/modules/lights/effects/color/fire.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export type FireCreateParams = BaseLightsEffectCreateParams & {

export default class Fire extends LightsEffect<FireProps> {
constructor(lightsGroup: LightsGroup, props?: FireProps, features?: TrackPropertiesEvent) {
super(lightsGroup, features);
super(lightsGroup, undefined, undefined, undefined, features);
}

public static build(props?: FireProps): LightsEffectBuilder<FireProps, Fire> {
Expand Down
2 changes: 1 addition & 1 deletion src/modules/lights/effects/color/single-flood.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default class SingleFlood extends LightsEffect<SingleFloodProps> {
return (lightsGroup) => new SingleFlood(lightsGroup, props);
}

private getProgression(currentTick: Date) {
protected getProgression(currentTick: Date) {
const dimMilliseconds = this.props.dimMilliseconds ?? DEFAULT_DIM_MILLISECONDS;
const diff = Math.max(0, currentTick.getTime() - this.effectStartTime.getTime());
if (diff < TURN_ON_TIME) return diff / TURN_ON_TIME;
Expand Down
21 changes: 10 additions & 11 deletions src/modules/lights/effects/color/sparkle.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import LightsEffect, { BaseLightsEffectCreateParams, LightsEffectBuilder } from '../lights-effect';
import LightsEffect, {
BaseLightsEffectCreateParams,
BaseLightsEffectProps,
LightsEffectBuilder,
} from '../lights-effect';
import { RgbColor } from '../../color-definitions';
import { LightsGroup } from '../../entities';
import { TrackPropertiesEvent } from '../../../events/music-emitter-events';
import { ColorEffects } from './color-effects';

export interface SparkleProps {
/**
* Colors of the lights
*/
colors: RgbColor[];

export interface SparkleProps extends BaseLightsEffectProps {
/**
* What percentage (on average) of the lights should be turned on
* @minimum 0
Expand Down Expand Up @@ -55,7 +54,7 @@ export default class Sparkle extends LightsEffect<SparkleProps> {
* @param features
*/
constructor(lightsGroup: LightsGroup, props: SparkleProps, features?: TrackPropertiesEvent) {
super(lightsGroup, features);
super(lightsGroup, undefined, undefined, undefined, features);

const nrFixtures = lightsGroup.pars.length + lightsGroup.movingHeadRgbs.length;
this.beats = new Array(nrFixtures).fill(new Date(0));
Expand Down Expand Up @@ -97,7 +96,7 @@ export default class Sparkle extends LightsEffect<SparkleProps> {
if (!this.props.cycleTime) this.enableLights();
}

private getProgression(beat: Date) {
private getDimProgression(beat: Date) {
const dimDuration = this.props.dimDuration ?? DEFAULT_DIM_DURATION;

return Math.max(1 - (new Date().getTime() - beat.getTime()) / dimDuration, 0);
Expand All @@ -115,15 +114,15 @@ export default class Sparkle extends LightsEffect<SparkleProps> {

this.lightsGroup.pars.forEach((p, i) => {
const index = i;
const progression = this.getProgression(this.beats[index]);
const progression = this.getDimProgression(this.beats[index]);
const colorIndex = this.colorIndices[index];
const color = colors[colorIndex % colors.length];
p.fixture.setColor(color);
p.fixture.setMasterDimmer(Math.round(255 * progression));
});
this.lightsGroup.movingHeadRgbs.forEach((p, i) => {
const index = i;
const progression = this.getProgression(this.beats[nrPars + index]);
const progression = this.getDimProgression(this.beats[nrPars + index]);
const colorIndex = this.colorIndices[nrPars + index];
const color = colors[colorIndex % colors.length];
p.fixture.setColor(color);
Expand Down
8 changes: 4 additions & 4 deletions src/modules/lights/effects/color/static-color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export default class StaticColor extends LightsEffect<StaticColorProps> {
private cycleStartTick: Date = new Date();

constructor(lightsGroup: LightsGroup, props: StaticColorProps, features?: TrackPropertiesEvent) {
super(lightsGroup, features);
super(lightsGroup, undefined, undefined, undefined, features);
this.props = props;

this.lightsGroup.fixtures.forEach((f) => {
Expand Down Expand Up @@ -87,14 +87,14 @@ export default class StaticColor extends LightsEffect<StaticColorProps> {

destroy(): void {}

private getProgression(durationMs: number) {
private getDimProgression(durationMs: number) {
return Math.min(1, (new Date().getTime() - this.cycleStartTick.getTime()) / durationMs);
}

tick(): LightsGroup {
let progression = 1;
if (this.props.brightenTimeMs) progression = this.getProgression(this.props.brightenTimeMs);
if (this.props.dimTimeMs) progression = 1 - this.getProgression(this.props.dimTimeMs);
if (this.props.brightenTimeMs) progression = this.getDimProgression(this.props.brightenTimeMs);
if (this.props.dimTimeMs) progression = 1 - this.getDimProgression(this.props.dimTimeMs);

this.lightsGroup.fixtures
.sort((f1, f2) => f1.firstChannel - f2.firstChannel)
Expand Down
2 changes: 1 addition & 1 deletion src/modules/lights/effects/color/strobe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export type StrobeCreateParams = BaseLightsEffectCreateParams & {

export default class Strobe extends LightsEffect<StrobeProps> {
constructor(lightsGroup: LightsGroup, props: StrobeProps, features?: TrackPropertiesEvent) {
super(lightsGroup, features);
super(lightsGroup, undefined, undefined, undefined, features);
this.props = props;

this.lightsGroup.pars.forEach((p) => {
Expand Down
Loading
Loading