Skip to content

Commit

Permalink
feat(lights)!: remove individual fixture requirements and constraints (
Browse files Browse the repository at this point in the history
…#38)

* chore(lights)!: refactor fixtures to strategy design pattern

* chore(lights): refactor master dimmer to relative brightness

* chore(lights)!: move master dim and shutter channels to color strategies

* feat(lights): make master dimmer and shutter optional

* feat(lights): add specific nr of channels required for each fixture

BREAKING CHANGE: new database structure
  • Loading branch information
Yoronex committed Feb 5, 2025
1 parent 7be3f7e commit f9dfc7f
Show file tree
Hide file tree
Showing 19 changed files with 738 additions and 542 deletions.
8 changes: 2 additions & 6 deletions src/modules/lights/color-definitions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ColorChannel } from './entities/colors';
import { ColorChannel, IColorsRgb } from './entities/colors-rgb';

export enum WheelColor {
WHITE = 'white',
Expand Down Expand Up @@ -35,12 +35,8 @@ export const wheelColors = Object.values(WheelColor);

export const rgbColors = Object.values(RgbColor);

// TODO: implement wheel colors
export type RgbColorDefinition = { [k in ColorChannel]: number };
export type WheelColorDefinition = RgbColorDefinition;

export type RgbColorSpecification = {
definition: RgbColorDefinition;
definition: Required<IColorsRgb>;
alternative: WheelColor;
complementary: RgbColor[];
hex: string;
Expand Down
4 changes: 2 additions & 2 deletions src/modules/lights/effects/color/beat-fade-out.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ export default class BeatFadeOut extends LightsEffect<BeatFadeOutProps> {

const color = this.getCurrentColor(p, i);
if (color == null) {
p.fixture.setMasterDimmer(0);
p.fixture.resetColor();
} else {
p.fixture.setMasterDimmer(Math.round(255 * beatProgression));
p.fixture.setBrightness(beatProgression);
p.fixture.setColor(color);
}
}
Expand Down
7 changes: 4 additions & 3 deletions src/modules/lights/effects/color/fire.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ export default class Fire extends LightsEffect<FireProps> {
destroy(): void {}

tick(): LightsGroup {
this.lightsGroup.pars.forEach((p) => {
p.fixture.setCurrentValues({
masterDimChannel: 128,
[...this.lightsGroup.pars, ...this.lightsGroup.movingHeadRgbs].forEach((p) => {
p.fixture.setBrightness(0.5);
p.fixture.setCustomColor({
redChannel: 255,
greenChannel: 32 + Math.round(Math.random() * 64),
blueChannel: 0,
amberChannel: 128,
});
});
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 @@ -57,7 +57,7 @@ export default class SingleFlood extends LightsEffect<SingleFloodProps> {

this.lightsGroup.pars.forEach((p) => {
p.fixture.setColor(this.props.color ?? RgbColor.ORANGE);
p.fixture.setMasterDimmer(255 * progression);
p.fixture.setBrightness(progression);
});

this.lightsGroup.movingHeadWheels.forEach((m) => m.fixture.blackout());
Expand Down
4 changes: 2 additions & 2 deletions src/modules/lights/effects/color/sparkle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,15 @@ export default class Sparkle extends LightsEffect<SparkleProps> {
const colorIndex = this.colorIndices[index];
const color = colors[colorIndex % colors.length];
p.fixture.setColor(color);
p.fixture.setMasterDimmer(Math.round(255 * progression));
p.fixture.setBrightness(progression);
});
this.lightsGroup.movingHeadRgbs.forEach((p, i) => {
const index = i;
const progression = this.getDimProgression(this.beats[nrPars + index]);
const colorIndex = this.colorIndices[nrPars + index];
const color = colors[colorIndex % colors.length];
p.fixture.setColor(color);
p.fixture.setMasterDimmer(Math.round(255 * progression));
p.fixture.setBrightness(progression);
});

return this.lightsGroup;
Expand Down
8 changes: 3 additions & 5 deletions src/modules/lights/effects/color/static-color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export default class StaticColor extends LightsEffect<StaticColorProps> {
this.lightsGroup.fixtures.forEach((f) => {
f.fixture.setColor(this.props.color);
if (!this.props.beatToggle) {
f.fixture.setMasterDimmer(Math.round((this.props.relativeBrightness ?? 1) * 255));
f.fixture.setBrightness(this.props.relativeBrightness ?? 1);
}
});
this.lightsGroup.movingHeadWheels.forEach((f) => {
Expand Down Expand Up @@ -99,11 +99,9 @@ export default class StaticColor extends LightsEffect<StaticColorProps> {
.forEach((f, i) => {
// If beatToggle is disabled, or if it is enabled and the fixture should be turned on
if (!this.props.beatToggle || i % 2 === this.ping) {
f.fixture.setMasterDimmer(
Math.round(progression * (this.props.relativeBrightness ?? 1) * 255),
);
f.fixture.setBrightness(progression * (this.props.relativeBrightness ?? 1));
} else {
f.fixture.setMasterDimmer(0);
f.fixture.setBrightness(0);
}
});

Expand Down
2 changes: 1 addition & 1 deletion src/modules/lights/effects/color/wave.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export default class Wave extends LightsEffect<WaveProps> {
const apply = (p: LightsGroupPars | LightsGroupMovingHeadRgbs) => {
const progression = this.getProgression(currentTick, p);
const brightness = this.getBrightness(progression);
p.fixture.setMasterDimmer(Math.max(0, brightness * 255));
p.fixture.setBrightness(Math.max(0, brightness));
p.fixture.setColor(this.props.colors[0]);
};

Expand Down
10 changes: 2 additions & 8 deletions src/modules/lights/effects/movement/random-position.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,12 @@ export default class RandomPosition extends LightsEffect<RandomPositionProps> {
this.lightsGroup.movingHeadRgbs.forEach((m, i) => {
if (this.counters[i] > 0) return;

m.fixture.setCurrentValues({
panChannel: Math.round(Math.random() * (255 / 3)),
tiltChannel: Math.round(Math.random() * 255),
});
m.fixture.setPositionRel(Math.random() / 3, Math.random());
});
this.lightsGroup.movingHeadWheels.forEach((m, i) => {
if (this.counters[nrMHRgbs + i] > 0) return;

m.fixture.setCurrentValues({
panChannel: Math.round(Math.random() * (255 / 3)),
tiltChannel: Math.round(Math.random() * 255),
});
m.fixture.setPositionRel(Math.random() / 3, Math.random());
});

this.counters = this.counters.map(
Expand Down
146 changes: 146 additions & 0 deletions src/modules/lights/entities/colors-rgb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { Column } from 'typeorm';
import { RgbColor, rgbColorDefinitions } from '../color-definitions';
import LightsFixtureShutterOptions, { ShutterOption } from './lights-fixture-shutter-options';
import Colors from './colors';
import { IColorsWheel } from './colors-wheel';

export type ColorChannel = keyof ColorsRgb;

export interface IColorsRgb {
redChannel: number;
greenChannel: number;
blueChannel: number;
coldWhiteChannel?: number | null;
warmWhiteChannel?: number | null;
amberChannel?: number | null;
uvChannel?: number | null;
}

export default class ColorsRgb extends Colors implements IColorsRgb {
@Column({ type: 'tinyint', unsigned: true, nullable: true })
public masterDimChannel?: number;

@Column({ type: 'tinyint', unsigned: true, nullable: true })
public shutterChannel?: number;

@Column({ type: 'tinyint', unsigned: true })
public redChannel: number;

@Column({ type: 'tinyint', unsigned: true })
public greenChannel: number;

@Column({ type: 'tinyint', unsigned: true })
public blueChannel: number;

@Column({ type: 'tinyint', nullable: true, unsigned: true })
public coldWhiteChannel?: number | null;

@Column({ type: 'tinyint', nullable: true, unsigned: true })
public warmWhiteChannel?: number | null;

@Column({ type: 'tinyint', nullable: true, unsigned: true })
public amberChannel?: number | null;

@Column({ type: 'tinyint', nullable: true, unsigned: true })
public uvChannel?: number | null;

private strobePing = false;

private currentValues: Required<IColorsRgb> = {
redChannel: 0,
greenChannel: 0,
blueChannel: 0,
coldWhiteChannel: 0,
warmWhiteChannel: 0,
amberChannel: 0,
uvChannel: 0,
};

public setColor(color: RgbColor): void {
this.currentValues = rgbColorDefinitions[color].definition;
}

public setCustomColor(color: IColorsRgb): void {
const givenColors = Object.keys(color) as (keyof IColorsRgb)[];
givenColors.forEach((key: keyof IColorsRgb) => {
this.currentValues[key] = color[key]!;
});
}

public reset(): void {
this.currentValues = {
redChannel: 0,
greenChannel: 0,
blueChannel: 0,
coldWhiteChannel: 0,
warmWhiteChannel: 0,
amberChannel: 0,
uvChannel: 0,
};
}

private getColor(color: keyof Required<IColorsRgb>): number {
let value = this.currentValues[color]!;
if (this.masterDimChannel) return value;
value = Math.round(value * this.currentBrightness);
return value;
}

public setStrobeInDmx(values: number[], shutterOptions: LightsFixtureShutterOptions[]): number[] {
if (this.masterDimChannel) values[this.masterDimChannel - 1] = 255;
if (this.shutterChannel)
values[this.shutterChannel - 1] =
shutterOptions.find((o) => o.shutterOption === ShutterOption.STROBE)?.channelValue ?? 0;

if (this.shutterChannel || this.strobePing) {
// If we have a shutter channel or we should manually strobe
values[this.redChannel - 1] = 255;
values[this.blueChannel - 1] = 255;
values[this.greenChannel - 1] = 255;
if (this.warmWhiteChannel) values[this.warmWhiteChannel - 1] = 255;
if (this.coldWhiteChannel) values[this.coldWhiteChannel - 1] = 255;
if (this.amberChannel) values[this.amberChannel - 1] = 255;
} else if (!this.shutterChannel) {
// If we do not have a shutter channel and the ping is off,
// turn off all colors
values[this.redChannel - 1] = 0;
values[this.blueChannel - 1] = 0;
values[this.greenChannel - 1] = 0;
if (this.warmWhiteChannel) values[this.warmWhiteChannel - 1] = 0;
if (this.coldWhiteChannel) values[this.coldWhiteChannel - 1] = 0;
if (this.amberChannel) values[this.amberChannel - 1] = 0;
}

// If we have no shutter channel, manually flip the strobe bit
if (!this.shutterChannel) {
this.strobePing = !this.strobePing;
}

return values;
}

public setColorsInDmx(values: number[], shutterOptions: LightsFixtureShutterOptions[]): number[] {
if (this.masterDimChannel)
values[this.masterDimChannel - 1] = Math.round(this.currentBrightness * 255);
if (this.shutterChannel)
values[this.shutterChannel - 1] =
shutterOptions.find((o) => o.shutterOption === ShutterOption.OPEN)?.channelValue ?? 0;
values[this.redChannel - 1] = this.getColor('redChannel');
values[this.greenChannel - 1] = this.getColor('greenChannel');
values[this.blueChannel - 1] = this.getColor('blueChannel');
if (this.coldWhiteChannel != null) {
values[this.coldWhiteChannel - 1] = this.getColor('coldWhiteChannel');
}
if (this.warmWhiteChannel != null) {
values[this.warmWhiteChannel - 1] = this.getColor('warmWhiteChannel');
}
if (this.amberChannel != null) {
values[this.amberChannel - 1] = this.getColor('amberChannel');
}
if (this.uvChannel != null) {
values[this.uvChannel - 1] = this.getColor('uvChannel');
}

return values;
}
}
123 changes: 123 additions & 0 deletions src/modules/lights/entities/colors-wheel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { Column, OneToMany } from 'typeorm';
import LightsMovingHeadWheelShutterOptions from './lights-moving-head-wheel-shutter-options';
import LightsWheelColorChannelValue from './lights-wheel-color-channel-value';
import LightsWheelGoboChannelValue from './lights-wheel-gobo-channel-value';
import LightsWheelRotateChannelValue from './lights-wheel-rotate-channel-value';
import { RgbColor, rgbColorDefinitions, WheelColor } from '../color-definitions';
import Colors from './colors';
import LightsFixtureShutterOptions, { ShutterOption } from './lights-fixture-shutter-options';

export interface IColorsWheel {
colorChannel: number;
goboChannel: number;
goboRotateChannel?: number | null;
}

export default class ColorsWheel extends Colors implements IColorsWheel {
@Column({ type: 'tinyint', unsigned: true })
public masterDimChannel: number;

@Column({ type: 'tinyint', unsigned: true, nullable: true })
public shutterChannel?: number;

@Column({ type: 'tinyint', unsigned: true })
public colorChannel: number;

@Column({ type: 'tinyint', unsigned: true })
public goboChannel: number;

@Column({ type: 'tinyint', nullable: true, unsigned: true })
public goboRotateChannel: number | null;

@OneToMany(() => LightsWheelColorChannelValue, (c) => c.movingHead, { eager: true })
public colorChannelValues: LightsWheelColorChannelValue[];

@OneToMany(() => LightsWheelGoboChannelValue, (c) => c.movingHead, { eager: true })
public goboChannelValues: LightsWheelGoboChannelValue[];

@OneToMany(() => LightsWheelRotateChannelValue, (c) => c.movingHead, { eager: true })
public goboRotateChannelValues: LightsWheelRotateChannelValue[];

private strobePing = false;

private currentValues: IColorsWheel = {
colorChannel: 0,
goboChannel: 0,
goboRotateChannel: 0,
};

public setColor(color: RgbColor): void {
const wheelColor = rgbColorDefinitions[color].alternative;
const channelValueObj = this.colorChannelValues.find((v) => v.name === wheelColor);
this.currentValues = {
...this.currentValues,
colorChannel: channelValueObj?.value ?? 0,
};
}

public setGobo(gobo?: string) {
const channelValueObj = this.goboChannelValues.find((v) => v.name === gobo);
this.currentValues = {
...this.currentValues,
goboChannel: channelValueObj?.value ?? 0,
};
}

public setGoboRotate(rotate?: string) {
const channelValueObj = this.goboRotateChannelValues.find((v) => v.name === rotate);
this.currentValues = {
...this.currentValues,
goboRotateChannel: channelValueObj?.value ?? 0,
};
}

public reset(): void {
this.currentValues = {
colorChannel: 0,
goboChannel: 0,
goboRotateChannel: 0,
};
}

private get channelValues() {
return this.currentValues;
}

public setStrobeInDmx(values: number[], shutterOptions: LightsFixtureShutterOptions[]): number[] {
if (this.shutterChannel)
values[this.shutterChannel - 1] =
shutterOptions.find((o) => o.shutterOption === ShutterOption.STROBE)?.channelValue ?? 0;
values[this.colorChannel - 1] =
this.colorChannelValues.find((o) => o.name === WheelColor.WHITE)?.value ?? 0;

if (this.shutterChannel || this.strobePing) {
// If we have a shutter channel or we should manually strobe,
// turn on the light
values[this.masterDimChannel - 1] = 255;
} else if (!this.shutterChannel) {
// If we do not have a shutter channel and the ping is off,
// turn off the light
values[this.masterDimChannel - 1] = 0;
}

// If we have no shutter channel, manually flip the strobe bit
if (!this.shutterChannel) {
this.strobePing = !this.strobePing;
}
return values;
}

public setColorsInDmx(values: number[], shutterOptions: LightsFixtureShutterOptions[]): number[] {
values[this.masterDimChannel - 1] = Math.round(this.currentBrightness * 255);
if (this.shutterChannel)
values[this.shutterChannel - 1] =
shutterOptions.find((o) => o.shutterOption === ShutterOption.OPEN)?.channelValue ?? 0;
values[this.colorChannel - 1] = this.channelValues.colorChannel;
values[this.goboChannel - 1] = this.channelValues.goboChannel;
if (this.goboRotateChannel != null) {
values[this.goboRotateChannel - 1] = this.channelValues.goboRotateChannel || 0;
}

return values;
}
}
Loading

0 comments on commit f9dfc7f

Please sign in to comment.