From 3e3d3959ca7d7c52771cf2ed06a12bf520f3d895 Mon Sep 17 00:00:00 2001 From: Roy Kakkenberg Date: Sat, 25 Jan 2025 11:34:38 +0100 Subject: [PATCH 1/5] chore(lights): refactor fixtures to strategy design pattern --- src/modules/lights/color-definitions.ts | 8 +- src/modules/lights/effects/color/fire.ts | 7 +- .../effects/movement/random-position.ts | 10 +- src/modules/lights/entities/colors-rgb.ts | 91 ++++++++ src/modules/lights/entities/colors-wheel.ts | 88 ++++++++ src/modules/lights/entities/colors.ts | 26 --- src/modules/lights/entities/lights-fixture.ts | 57 ++++- .../lights/entities/lights-moving-head-rgb.ts | 168 +++------------ .../entities/lights-moving-head-wheel.ts | 197 +++--------------- .../lights/entities/lights-moving-head.ts | 28 +++ src/modules/lights/entities/lights-par.ts | 100 ++------- src/modules/lights/entities/movement.ts | 77 ++++++- src/modules/root/root-lights-service.ts | 54 +++-- 13 files changed, 450 insertions(+), 461 deletions(-) create mode 100644 src/modules/lights/entities/colors-rgb.ts create mode 100644 src/modules/lights/entities/colors-wheel.ts delete mode 100644 src/modules/lights/entities/colors.ts diff --git a/src/modules/lights/color-definitions.ts b/src/modules/lights/color-definitions.ts index 2406c54..97cfedc 100644 --- a/src/modules/lights/color-definitions.ts +++ b/src/modules/lights/color-definitions.ts @@ -1,4 +1,4 @@ -import { ColorChannel } from './entities/colors'; +import { ColorChannel, IColorsRgb } from './entities/colors-rgb'; export enum WheelColor { WHITE = 'white', @@ -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; alternative: WheelColor; complementary: RgbColor[]; hex: string; diff --git a/src/modules/lights/effects/color/fire.ts b/src/modules/lights/effects/color/fire.ts index 5c3868a..7d18db9 100644 --- a/src/modules/lights/effects/color/fire.ts +++ b/src/modules/lights/effects/color/fire.ts @@ -22,11 +22,12 @@ export default class Fire extends LightsEffect { destroy(): void {} tick(): LightsGroup { - this.lightsGroup.pars.forEach((p) => { - p.fixture.setCurrentValues({ - masterDimChannel: 128, + [...this.lightsGroup.pars, ...this.lightsGroup.movingHeadRgbs].forEach((p) => { + p.fixture.setMasterDimmer(128); + p.fixture.setCustomColor({ redChannel: 255, greenChannel: 32 + Math.round(Math.random() * 64), + blueChannel: 0, amberChannel: 128, }); }); diff --git a/src/modules/lights/effects/movement/random-position.ts b/src/modules/lights/effects/movement/random-position.ts index 880348e..d172ee9 100644 --- a/src/modules/lights/effects/movement/random-position.ts +++ b/src/modules/lights/effects/movement/random-position.ts @@ -42,18 +42,12 @@ export default class RandomPosition extends LightsEffect { 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( diff --git a/src/modules/lights/entities/colors-rgb.ts b/src/modules/lights/entities/colors-rgb.ts new file mode 100644 index 0000000..406fc48 --- /dev/null +++ b/src/modules/lights/entities/colors-rgb.ts @@ -0,0 +1,91 @@ +import { Column } from 'typeorm'; +import { RgbColor, rgbColorDefinitions } from '../color-definitions'; + +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 implements IColorsRgb { + @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 currentValues: 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 { + this.currentValues = color; + } + + public reset(): void { + this.currentValues = { + redChannel: 0, + greenChannel: 0, + blueChannel: 0, + coldWhiteChannel: 0, + warmWhiteChannel: 0, + amberChannel: 0, + uvChannel: 0, + }; + } + + private get channelValues() { + return this.currentValues; + } + + public setColorsInDmx(values: number[]): number[] { + values[this.redChannel - 1] = this.channelValues.redChannel; + values[this.greenChannel - 1] = this.channelValues.greenChannel; + values[this.blueChannel - 1] = this.channelValues.blueChannel; + if (this.coldWhiteChannel != null) { + values[this.coldWhiteChannel - 1] = this.channelValues.coldWhiteChannel || 0; + } + if (this.warmWhiteChannel != null) { + values[this.warmWhiteChannel - 1] = this.channelValues.warmWhiteChannel || 0; + } + if (this.amberChannel != null) { + values[this.amberChannel - 1] = this.channelValues.amberChannel || 0; + } + if (this.uvChannel != null) { + values[this.uvChannel - 1] = this.channelValues.uvChannel || 0; + } + + return values; + } +} diff --git a/src/modules/lights/entities/colors-wheel.ts b/src/modules/lights/entities/colors-wheel.ts new file mode 100644 index 0000000..f12ac8c --- /dev/null +++ b/src/modules/lights/entities/colors-wheel.ts @@ -0,0 +1,88 @@ +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'; + +export interface IColorsWheel { + colorChannel: number; + goboChannel: number; + goboRotateChannel?: number | null; +} + +export default class ColorsWheel implements IColorsWheel { + @OneToMany(() => LightsMovingHeadWheelShutterOptions, (opt) => opt.fixture, { eager: true }) + public shutterOptions: LightsMovingHeadWheelShutterOptions[]; + + @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 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 setColorsInDmx(values: number[]): number[] { + 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; + } +} diff --git a/src/modules/lights/entities/colors.ts b/src/modules/lights/entities/colors.ts deleted file mode 100644 index 7ff1768..0000000 --- a/src/modules/lights/entities/colors.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Column } from 'typeorm'; - -export type ColorChannel = keyof Colors; - -export default class Colors { - @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; -} diff --git a/src/modules/lights/entities/lights-fixture.ts b/src/modules/lights/entities/lights-fixture.ts index a6c5cac..93a674b 100644 --- a/src/modules/lights/entities/lights-fixture.ts +++ b/src/modules/lights/entities/lights-fixture.ts @@ -1,5 +1,6 @@ import { AfterLoad, Column } from 'typeorm'; import BaseEntity from '../../root/entities/base-entity'; +import { RgbColor } from '../color-definitions'; export interface LightsFixtureCurrentValues extends Pick {} @@ -29,6 +30,8 @@ export default abstract class LightsFixture extends BaseEntity { }) public resetChannelAndValue?: number[] | null; + public currentMasterDim: number | undefined; + public valuesUpdatedAt: Date; private overrideDmx: (number | null)[] = new Array(16).fill(null); @@ -86,6 +89,14 @@ export default abstract class LightsFixture extends BaseEntity { this.valuesUpdatedAt = new Date(); } + public abstract setColor(color: RgbColor): void; + public abstract resetColor(): void; + + public setMasterDimmer(value: number) { + this.currentMasterDim = value; + this.valuesUpdatedAt = new Date(); + } + /** * Override any set relative DMX channels with the given values. * Undefined if a channel should not be overriden @@ -118,10 +129,54 @@ export default abstract class LightsFixture extends BaseEntity { }); } + /** + * Apply a blackout to this fixture, i.e. set all channels to zero + * @protected + */ + public abstract blackout(): void; + + /** + * Get the DMX channels that should be used when the fixture should strobe + * @protected + */ + protected abstract getStrobeDMX(): number[]; + + /** + * Get the DMX channels that are created from the channel values + * @protected + */ + protected abstract getDmxFromCurrentValues(): number[]; + /** * Get the current DMX values as an 16-length array of integers. */ - abstract toDmx(): number[]; + toDmx(): number[] { + if (this.strobeEnabled) return this.getStrobeDMX(); + + if (this.frozenDmx != null && this.frozenDmx.length > 0) { + return this.frozenDmx; + } + + let values: number[] = this.getDmxFromCurrentValues(); + values = this.applyDmxOverride(values); + + if (this.shouldReset !== undefined) { + if (new Date().getTime() - this.shouldReset.getTime() > 5000) { + this.shouldReset = undefined; + } + if (this.resetChannelAndValue && this.resetChannelAndValue.length >= 2) { + const [channel, value] = this.resetChannelAndValue; + values[channel - 1] = value; + this.valuesUpdatedAt = new Date(); + } + } + + if (this.shouldFreezeDmx) { + this.frozenDmx = values; + } + + return values; + } /** * Store the next state of the fixture and do not change anymore diff --git a/src/modules/lights/entities/lights-moving-head-rgb.ts b/src/modules/lights/entities/lights-moving-head-rgb.ts index f4622f5..f89a253 100644 --- a/src/modules/lights/entities/lights-moving-head-rgb.ts +++ b/src/modules/lights/entities/lights-moving-head-rgb.ts @@ -1,9 +1,7 @@ import { Column, Entity, OneToMany } from 'typeorm'; import LightsMovingHead from './lights-moving-head'; -import Colors from './colors'; -import Movement from './movement'; -import { LightsFixtureCurrentValues } from './lights-fixture'; -import { RgbColor, rgbColorDefinitions } from '../color-definitions'; +import ColorsRgb, { IColorsRgb } from './colors-rgb'; +import { RgbColor } from '../color-definitions'; // eslint-disable-next-line import/no-cycle import LightsMovingHeadRgbShutterOptions from './lights-moving-head-rgb-shutter-options'; import { ShutterOption } from './lights-fixture-shutter-options'; @@ -13,83 +11,28 @@ export default class LightsMovingHeadRgb extends LightsMovingHead { @OneToMany(() => LightsMovingHeadRgbShutterOptions, (opt) => opt.fixture, { eager: true }) public shutterOptions: LightsMovingHeadRgbShutterOptions[]; - @Column(() => Colors) - public color: Colors; + @Column(() => ColorsRgb) + public color: ColorsRgb; - public currentValues: Colors & Movement & LightsFixtureCurrentValues = { - masterDimChannel: 0, - redChannel: 0, - greenChannel: 0, - blueChannel: 0, - coldWhiteChannel: 0, - warmWhiteChannel: 0, - amberChannel: 0, - uvChannel: 0, - panChannel: 0, - finePanChannel: 0, - tiltChannel: 0, - fineTiltChannel: 0, - movingSpeedChannel: 0, - }; - - setCurrentValues(values: Partial) { - this.currentValues = { - ...this.currentValues, - ...values, - }; + public setColor(color: RgbColor) { this.valuesUpdatedAt = new Date(); + this.color.setColor(color); } - setColor(color: RgbColor) { - this.setCurrentValues(rgbColorDefinitions[color].definition); - } - - /** - * @param pan value between [0, 255). Any decimals are applied to the finePan - * @param tilt value between [0, 255). Any decimals are applied to the finePan - */ - setPosition(pan: number, tilt: number) { - const panChannel = Math.floor(pan); - const tiltChannel = Math.floor(tilt); - const finePanChannel = Math.floor((pan - panChannel) * 255); - const fineTiltChannel = Math.floor((tilt - tiltChannel) * 255); - - this.setCurrentValues({ - panChannel, - finePanChannel, - tiltChannel, - fineTiltChannel, - }); - } - - setMasterDimmer(masterDimChannel: number) { - if (this.currentValues.masterDimChannel === masterDimChannel) return; - this.setCurrentValues({ - masterDimChannel, - }); + public setCustomColor(color: IColorsRgb) { + this.valuesUpdatedAt = new Date(); + this.color.setCustomColor(color); } - blackout() { - if (Object.values(this.currentValues).every((v) => v === 0)) return; - this.setCurrentValues({ - masterDimChannel: 0, - redChannel: 0, - greenChannel: 0, - blueChannel: 0, - coldWhiteChannel: 0, - warmWhiteChannel: 0, - amberChannel: 0, - uvChannel: 0, - panChannel: 0, - finePanChannel: 0, - tiltChannel: 0, - fineTiltChannel: 0, - movingSpeedChannel: 0, - }); + public resetColor() { + this.valuesUpdatedAt = new Date(); + this.color.reset(); } - public get channelValues() { - return this.currentValues; + public blackout() { + this.valuesUpdatedAt = new Date(); + super.blackout(); + this.color.reset(); } /** @@ -97,7 +40,7 @@ export default class LightsMovingHeadRgb extends LightsMovingHead { * @protected */ protected getStrobeDMX(): number[] { - const values: number[] = new Array(16).fill(0); + let values: number[] = new Array(16).fill(0); values[this.masterDimChannel - 1] = 255; values[this.shutterChannel - 1] = this.shutterOptions.find((o) => o.shutterOption === ShutterOption.STROBE)?.channelValue ?? 0; @@ -107,84 +50,21 @@ export default class LightsMovingHeadRgb extends LightsMovingHead { if (this.color.warmWhiteChannel) values[this.color.warmWhiteChannel - 1] = 255; if (this.color.coldWhiteChannel) values[this.color.coldWhiteChannel - 1] = 255; if (this.color.amberChannel) values[this.color.amberChannel - 1] = 255; + + values = this.setPositionInDmx(values); + return values; } - toDmx(): number[] { - if (this.frozenDmx != null && this.frozenDmx.length > 0) { - return this.frozenDmx; - } - + public getDmxFromCurrentValues(): number[] { let values: number[] = new Array(16).fill(0); - values[this.masterDimChannel - 1] = this.channelValues.masterDimChannel; + values[this.masterDimChannel - 1] = this.currentMasterDim ?? 0; values[this.shutterChannel - 1] = this.shutterOptions.find((o) => o.shutterOption === ShutterOption.OPEN)?.channelValue ?? 0; - values[this.color.redChannel - 1] = this.channelValues.redChannel; - values[this.color.greenChannel - 1] = this.channelValues.greenChannel; - values[this.color.blueChannel - 1] = this.channelValues.blueChannel; - if (this.color.coldWhiteChannel != null) { - values[this.color.coldWhiteChannel - 1] = this.channelValues.coldWhiteChannel || 0; - } - if (this.color.warmWhiteChannel != null) { - values[this.color.warmWhiteChannel - 1] = this.channelValues.warmWhiteChannel || 0; - } - if (this.color.amberChannel != null) { - values[this.color.amberChannel - 1] = this.channelValues.amberChannel || 0; - } - if (this.color.uvChannel != null) { - values[this.color.uvChannel - 1] = this.channelValues.uvChannel || 0; - } - values[this.movement.panChannel - 1] = this.channelValues.panChannel; - if (this.movement.finePanChannel != null) { - values[this.movement.finePanChannel] = this.channelValues.finePanChannel || 0; - } - values[this.movement.tiltChannel - 1] = this.channelValues.tiltChannel; - if (this.movement.fineTiltChannel != null) { - values[this.movement.fineTiltChannel - 1] = this.channelValues.fineTiltChannel || 0; - } - if (this.movement.movingSpeedChannel != null) { - values[this.movement.movingSpeedChannel - 1] = this.channelValues.movingSpeedChannel || 0; - } - - // If the strobe is enabled, override all color channels with a strobe - if (this.strobeEnabled) { - const strobeDmxValues = this.getStrobeDMX(); - - // Remove starting zeroes, so we don't override the position of the moving head. - // Assumes that all color-related channels are near each other; - let nrStartingZeroes = 0; - while (strobeDmxValues.length > 0) { - if (strobeDmxValues[0] === 0) { - strobeDmxValues.shift(); - nrStartingZeroes += 1; - } else { - break; - } - } - values.splice( - nrStartingZeroes, - nrStartingZeroes + strobeDmxValues.length, - ...strobeDmxValues, - ); - } - - values = this.applyDmxOverride(values); - - if (this.shouldReset !== undefined) { - if (new Date().getTime() - this.shouldReset.getTime() > 5000) { - this.shouldReset = undefined; - } - if (this.resetChannelAndValue && this.resetChannelAndValue.length >= 2) { - const [channel, value] = this.resetChannelAndValue; - values[channel - 1] = value; - this.valuesUpdatedAt = new Date(); - } - } - if (this.shouldFreezeDmx) { - this.frozenDmx = values; - } + values = this.color.setColorsInDmx(values); + values = this.setPositionInDmx(values); return values; } diff --git a/src/modules/lights/entities/lights-moving-head-wheel.ts b/src/modules/lights/entities/lights-moving-head-wheel.ts index bb44170..af08041 100644 --- a/src/modules/lights/entities/lights-moving-head-wheel.ts +++ b/src/modules/lights/entities/lights-moving-head-wheel.ts @@ -1,129 +1,42 @@ import { Column, Entity, OneToMany } from 'typeorm'; import LightsMovingHead from './lights-moving-head'; -import Movement from './movement'; -import { LightsFixtureCurrentValues } from './lights-fixture'; -import { RgbColor, rgbColorDefinitions, WheelColor } from '../color-definitions'; -// eslint-disable-next-line import/no-cycle +import { RgbColor, WheelColor } from '../color-definitions'; import LightsMovingHeadWheelShutterOptions from './lights-moving-head-wheel-shutter-options'; import { ShutterOption } from './lights-fixture-shutter-options'; -// eslint-disable-next-line import/no-cycle -import LightsWheelColorChannelValue from './lights-wheel-color-channel-value'; -import LightsWheelGoboChannelValue from './lights-wheel-gobo-channel-value'; -import LightsWheelRotateChannelValue from './lights-wheel-rotate-channel-value'; - -interface LightsMovingHeadWheelCurrentValues extends Movement, LightsFixtureCurrentValues { - colorWheelChannel: number; - goboWheelChannel: number; - goboRotateChannel?: number; -} +import ColorsWheel from './colors-wheel'; @Entity() export default class LightsMovingHeadWheel extends LightsMovingHead { @OneToMany(() => LightsMovingHeadWheelShutterOptions, (opt) => opt.fixture, { eager: true }) public shutterOptions: LightsMovingHeadWheelShutterOptions[]; - @Column({ type: 'tinyint', unsigned: true }) - public colorWheelChannel: number; - - @Column({ type: 'tinyint', unsigned: true }) - public goboWheelChannel: number; - - @Column({ type: 'tinyint', nullable: true, unsigned: true }) - public goboRotateChannel: number | null; - - @OneToMany(() => LightsWheelColorChannelValue, (c) => c.movingHead, { eager: true }) - public colorWheelChannelValues: LightsWheelColorChannelValue[]; - - @OneToMany(() => LightsWheelGoboChannelValue, (c) => c.movingHead, { eager: true }) - public goboWheelChannelValues: LightsWheelGoboChannelValue[]; - - @OneToMany(() => LightsWheelRotateChannelValue, (c) => c.movingHead, { eager: true }) - public goboRotateChannelValues: LightsWheelRotateChannelValue[]; - - private currentValues: LightsMovingHeadWheelCurrentValues = { - masterDimChannel: 0, - panChannel: 0, - finePanChannel: 0, - tiltChannel: 0, - fineTiltChannel: 0, - movingSpeedChannel: 0, - colorWheelChannel: 0, - goboWheelChannel: 0, - goboRotateChannel: 0, - }; + @Column(() => ColorsWheel) + public wheel: ColorsWheel; - setColor(color: RgbColor) { - const wheelColor = rgbColorDefinitions[color].alternative; - const channelValueObj = this.colorWheelChannelValues.find((v) => v.name === wheelColor); - this.setCurrentValues({ - colorWheelChannel: channelValueObj?.value ?? 0, - }); - } - - setGobo(gobo?: string) { - const channelValueObj = this.goboWheelChannelValues.find((v) => v.name === gobo); - this.setCurrentValues({ - goboWheelChannel: channelValueObj?.value ?? 0, - }); - } - - setGoboRotate(rotate?: string) { - const channelValueObj = this.goboRotateChannelValues.find((v) => v.name === rotate); - this.setCurrentValues({ - goboRotateChannel: channelValueObj?.value ?? 0, - }); - } - - /** - * @param pan value between [0, 255). Any decimals are applied to the finePan - * @param tilt value between [0, 255). Any decimals are applied to the finePan - */ - setPosition(pan: number, tilt: number) { - const panChannel = Math.floor(pan); - const tiltChannel = Math.floor(tilt); - const finePanChannel = Math.floor((pan - panChannel) * 255); - const fineTiltChannel = Math.floor((tilt - tiltChannel) * 255); - - this.setCurrentValues({ - panChannel, - finePanChannel, - tiltChannel, - fineTiltChannel, - }); + public setColor(color: RgbColor) { + this.valuesUpdatedAt = new Date(); + this.wheel.setColor(color); } - setMasterDimmer(masterDimChannel: number) { - if (this.currentValues.masterDimChannel === masterDimChannel) return; - this.setCurrentValues({ - masterDimChannel, - }); + public resetColor(): void { + this.valuesUpdatedAt = new Date(); + this.wheel.reset(); } - blackout() { - if (Object.values(this.currentValues).every((v) => v === 0)) return; - this.setCurrentValues({ - masterDimChannel: 0, - panChannel: 0, - finePanChannel: 0, - tiltChannel: 0, - fineTiltChannel: 0, - movingSpeedChannel: 0, - colorWheelChannel: 0, - goboWheelChannel: 0, - goboRotateChannel: 0, - }); + public setGobo(gobo?: string) { + this.valuesUpdatedAt = new Date(); + this.wheel.setGobo(gobo); } - setCurrentValues(values: Partial) { - this.currentValues = { - ...this.currentValues, - ...values, - }; + public setGoboRotate(rotate?: string) { this.valuesUpdatedAt = new Date(); + this.wheel.setGoboRotate(rotate); } - public get channelValues() { - return this.currentValues; + public blackout() { + this.valuesUpdatedAt = new Date(); + super.blackout(); + this.wheel.reset(); } /** @@ -131,79 +44,27 @@ export default class LightsMovingHeadWheel extends LightsMovingHead { * @protected */ protected getStrobeDMX(): number[] { - const values: number[] = new Array(16).fill(0); + let values: number[] = new Array(16).fill(0); values[this.masterDimChannel - 1] = 255; values[this.shutterChannel - 1] = this.shutterOptions.find((o) => o.shutterOption === ShutterOption.STROBE)?.channelValue ?? 0; - values[this.colorWheelChannel - 1] = - this.colorWheelChannelValues.find((o) => o.name === WheelColor.WHITE)?.value ?? 0; + values[this.wheel.colorChannel - 1] = + this.wheel.colorChannelValues.find((o) => o.name === WheelColor.WHITE)?.value ?? 0; + + values = this.setPositionInDmx(values); + return values; } - toDmx(): number[] { - if (this.frozenDmx != null && this.frozenDmx.length > 0) { - return this.frozenDmx; - } - + public getDmxFromCurrentValues(): number[] { let values: number[] = new Array(16).fill(0); - values[this.masterDimChannel - 1] = this.channelValues.masterDimChannel; + values[this.masterDimChannel - 1] = this.currentMasterDim ?? 0; values[this.shutterChannel - 1] = this.shutterOptions.find((o) => o.shutterOption === ShutterOption.OPEN)?.channelValue ?? 0; - values[this.movement.panChannel - 1] = this.channelValues.panChannel; - values[this.colorWheelChannel - 1] = this.channelValues.colorWheelChannel; - values[this.goboWheelChannel - 1] = this.channelValues.goboWheelChannel; - if (this.goboRotateChannel != null) { - values[this.goboRotateChannel - 1] = this.channelValues.goboRotateChannel || 0; - } - if (this.movement.finePanChannel != null) { - values[this.movement.finePanChannel - 1] = this.channelValues.finePanChannel || 0; - } - values[this.movement.tiltChannel - 1] = this.channelValues.tiltChannel; - if (this.movement.fineTiltChannel != null) { - values[this.movement.fineTiltChannel - 1] = this.channelValues.fineTiltChannel || 0; - } - if (this.movement.movingSpeedChannel != null) { - values[this.movement.movingSpeedChannel - 1] = this.channelValues.movingSpeedChannel || 0; - } - - if (this.strobeEnabled) { - const strobeDmxValues = this.getStrobeDMX(); - - // Remove starting zeroes, so we don't override the position of the moving head. - // Assumes that all color-related channels are near each other; - let nrStartingZeroes = 0; - while (strobeDmxValues.length > 0) { - if (strobeDmxValues[0] === 0) { - strobeDmxValues.shift(); - nrStartingZeroes += 1; - } else { - break; - } - } - values.splice( - nrStartingZeroes, - nrStartingZeroes + strobeDmxValues.length, - ...strobeDmxValues, - ); - } - - values = this.applyDmxOverride(values); - - if (this.shouldReset !== undefined) { - if (new Date().getTime() - this.shouldReset.getTime() > 5000) { - this.shouldReset = undefined; - } - if (this.resetChannelAndValue && this.resetChannelAndValue.length >= 2) { - const [channel, value] = this.resetChannelAndValue; - values[channel - 1] = value; - this.valuesUpdatedAt = new Date(); - } - } - if (this.shouldFreezeDmx) { - this.frozenDmx = values; - } + values = this.wheel.setColorsInDmx(values); + values = this.setPositionInDmx(values); return values; } diff --git a/src/modules/lights/entities/lights-moving-head.ts b/src/modules/lights/entities/lights-moving-head.ts index 2e45862..05210d6 100644 --- a/src/modules/lights/entities/lights-moving-head.ts +++ b/src/modules/lights/entities/lights-moving-head.ts @@ -5,4 +5,32 @@ import Movement from './movement'; export default abstract class LightsMovingHead extends LightsFixture { @Column(() => Movement) public movement: Movement; + + /** + * @param pan value between [0, 1] + * @param tilt value between [0, 1] + */ + public setPositionRel(pan: number, tilt: number) { + this.valuesUpdatedAt = new Date(); + this.movement.setPositionRel(pan, tilt); + } + + /** + * @param pan value between [0, 255). Any decimals are applied to the finePan + * @param tilt value between [0, 255). Any decimals are applied to the fineTilt + */ + public setPosition(pan: number, tilt: number) { + this.valuesUpdatedAt = new Date(); + this.movement.setPositionAbs(pan, tilt); + } + + protected setPositionInDmx(values: number[]): number[] { + this.valuesUpdatedAt = new Date(); + return this.movement.setPositionInDmx(values); + } + + public blackout(): void { + this.valuesUpdatedAt = new Date(); + this.movement.reset(); + } } diff --git a/src/modules/lights/entities/lights-par.ts b/src/modules/lights/entities/lights-par.ts index 3b921ed..d493bc0 100644 --- a/src/modules/lights/entities/lights-par.ts +++ b/src/modules/lights/entities/lights-par.ts @@ -1,6 +1,6 @@ import { Column, Entity, OneToMany } from 'typeorm'; -import LightsFixture, { LightsFixtureCurrentValues } from './lights-fixture'; -import Colors from './colors'; +import LightsFixture from './lights-fixture'; +import ColorsRgb, { IColorsRgb } from './colors-rgb'; import { RgbColor, rgbColorDefinitions } from '../color-definitions'; // eslint-disable-next-line import/no-cycle import LightsParShutterOptions from './lights-par-shutter-options'; @@ -11,55 +11,27 @@ export default class LightsPar extends LightsFixture { @OneToMany(() => LightsParShutterOptions, (opt) => opt.fixture, { eager: true }) public shutterOptions: LightsParShutterOptions[]; - @Column(() => Colors) - public color: Colors; + @Column(() => ColorsRgb) + public color: ColorsRgb; - private currentValues: Required = { - masterDimChannel: 0, - redChannel: 0, - greenChannel: 0, - blueChannel: 0, - coldWhiteChannel: 0, - warmWhiteChannel: 0, - amberChannel: 0, - uvChannel: 0, - }; - - setCurrentValues(values: Partial) { - this.currentValues = { - ...this.currentValues, - ...values, - }; + public setColor(color: RgbColor) { this.valuesUpdatedAt = new Date(); + this.color.setColor(color); } - setColor(color: RgbColor) { - this.setCurrentValues(rgbColorDefinitions[color].definition); - } - - setMasterDimmer(masterDimChannel: number) { - if (this.currentValues.masterDimChannel === masterDimChannel) return; - this.setCurrentValues({ - masterDimChannel, - }); + public setCustomColor(color: IColorsRgb) { + this.valuesUpdatedAt = new Date(); + this.color.setCustomColor(color); } - blackout() { - if (Object.values(this.currentValues).every((v) => v === 0)) return; - this.setCurrentValues({ - masterDimChannel: 0, - redChannel: 0, - greenChannel: 0, - blueChannel: 0, - coldWhiteChannel: 0, - warmWhiteChannel: 0, - amberChannel: 0, - uvChannel: 0, - }); + public resetColor() { + this.valuesUpdatedAt = new Date(); + this.color.reset(); } - public get channelValues() { - return this.currentValues; + public blackout() { + this.valuesUpdatedAt = new Date(); + this.color.reset(); } /** @@ -80,50 +52,14 @@ export default class LightsPar extends LightsFixture { return values; } - toDmx(): number[] { - if (this.strobeEnabled) return this.getStrobeDMX(); - - if (this.frozenDmx != null && this.frozenDmx.length > 0) { - return this.frozenDmx; - } - + public getDmxFromCurrentValues(): number[] { let values: number[] = new Array(16).fill(0); - values[this.masterDimChannel - 1] = this.channelValues.masterDimChannel; + values[this.masterDimChannel - 1] = this.currentMasterDim ?? 0; values[this.shutterChannel - 1] = this.shutterOptions.find((o) => o.shutterOption === ShutterOption.OPEN)?.channelValue ?? 0; - values[this.color.redChannel - 1] = this.channelValues.redChannel; - values[this.color.greenChannel - 1] = this.channelValues.greenChannel; - values[this.color.blueChannel - 1] = this.channelValues.blueChannel; - if (this.color.coldWhiteChannel != null) { - values[this.color.coldWhiteChannel - 1] = this.channelValues.coldWhiteChannel || 0; - } - if (this.color.warmWhiteChannel != null) { - values[this.color.warmWhiteChannel - 1] = this.channelValues.warmWhiteChannel || 0; - } - if (this.color.amberChannel != null) { - values[this.color.amberChannel - 1] = this.channelValues.amberChannel || 0; - } - if (this.color.uvChannel != null) { - values[this.color.uvChannel - 1] = this.channelValues.uvChannel || 0; - } - - values = this.applyDmxOverride(values); - - if (this.shouldReset !== undefined) { - if (new Date().getTime() - this.shouldReset.getTime() > 5000) { - this.shouldReset = undefined; - } - if (this.resetChannelAndValue && this.resetChannelAndValue.length >= 2) { - const [channel, value] = this.resetChannelAndValue; - values[channel - 1] = value; - this.valuesUpdatedAt = new Date(); - } - } - if (this.shouldFreezeDmx) { - this.frozenDmx = values; - } + values = this.color.setColorsInDmx(values); return values; } diff --git a/src/modules/lights/entities/movement.ts b/src/modules/lights/entities/movement.ts index 30e0c62..fddd0ae 100644 --- a/src/modules/lights/entities/movement.ts +++ b/src/modules/lights/entities/movement.ts @@ -1,6 +1,14 @@ import { Column } from 'typeorm'; -export default class Movement { +export interface IMovement { + panChannel: number; + finePanChannel?: number | null; + tiltChannel: number; + fineTiltChannel?: number | null; + movingSpeedChannel?: number | null; +} + +export default class Movement implements IMovement { @Column({ type: 'tinyint', unsigned: true }) public panChannel: number; @@ -15,4 +23,71 @@ export default class Movement { @Column({ type: 'tinyint', nullable: true, unsigned: true }) public movingSpeedChannel?: number | null; + + private currentValues: Required = { + panChannel: 0, + finePanChannel: 0, + tiltChannel: 0, + fineTiltChannel: 0, + movingSpeedChannel: 0, + }; + + /** + * @param pan value between [0, 1] + * @param tilt value between [0, 1] + */ + public setPositionRel(pan: number, tilt: number) { + this.setPositionAbs(pan * 255, tilt * 255); + } + + /** + * @param pan value between [0, 255). Any decimals are applied to the finePan + * @param tilt value between [0, 255). Any decimals are applied to the fineTilt + */ + public setPositionAbs(pan: number, tilt: number) { + const panChannel = Math.floor(pan); + const tiltChannel = Math.floor(tilt); + const finePanChannel = Math.floor((pan - panChannel) * 255); + const fineTiltChannel = Math.floor((tilt - tiltChannel) * 255); + + this.currentValues = { + panChannel, + finePanChannel, + tiltChannel, + fineTiltChannel, + movingSpeedChannel: 0, + }; + } + + /** + * Reset the moving head to its initial position + */ + public reset() { + this.currentValues = { + panChannel: 0, + finePanChannel: 0, + tiltChannel: 0, + fineTiltChannel: 0, + movingSpeedChannel: 0, + }; + } + + private get channelValues() { + return this.currentValues; + } + + public setPositionInDmx(values: number[]): number[] { + values[this.panChannel - 1] = this.channelValues.panChannel; + if (this.finePanChannel != null) { + values[this.finePanChannel] = this.channelValues.finePanChannel || 0; + } + values[this.tiltChannel - 1] = this.channelValues.tiltChannel; + if (this.fineTiltChannel != null) { + values[this.fineTiltChannel - 1] = this.channelValues.fineTiltChannel || 0; + } + if (this.movingSpeedChannel != null) { + values[this.movingSpeedChannel - 1] = this.channelValues.movingSpeedChannel || 0; + } + return values; + } } diff --git a/src/modules/root/root-lights-service.ts b/src/modules/root/root-lights-service.ts index 0e3d8fa..6ac12af 100644 --- a/src/modules/root/root-lights-service.ts +++ b/src/modules/root/root-lights-service.ts @@ -8,12 +8,12 @@ import { } from '../lights/entities'; import dataSource from '../../database'; import LightsFixture from '../lights/entities/lights-fixture'; -import Colors from '../lights/entities/colors'; +import ColorsRgb, { IColorsRgb } from '../lights/entities/colors-rgb'; import LightsMovingHead from '../lights/entities/lights-moving-head'; import LightsGroupPars from '../lights/entities/lights-group-pars'; import LightsGroupMovingHeadRgbs from '../lights/entities/lights-group-moving-head-rgbs'; import LightsGroupMovingHeadWheels from '../lights/entities/lights-group-moving-head-wheels'; -import Movement from '../lights/entities/movement'; +import Movement, { IMovement } from '../lights/entities/movement'; import AuthService from '../auth/auth-service'; import LightsParShutterOptions from '../lights/entities/lights-par-shutter-options'; import LightsMovingHeadRgbShutterOptions from '../lights/entities/lights-moving-head-rgb-shutter-options'; @@ -22,6 +22,7 @@ import LightsFixtureShutterOptions, { ShutterOption, } from '../lights/entities/lights-fixture-shutter-options'; import { WheelColor } from '../lights/color-definitions'; +import ColorsWheel, { IColorsWheel } from '../lights/entities/colors-wheel'; export interface LightsControllerResponse extends Pick {} @@ -47,13 +48,13 @@ export interface LightsFixtureResponse // prettier-ignore export interface ColorResponse extends Pick< - Colors, + ColorsRgb, 'redChannel' | 'blueChannel' | 'greenChannel' | 'coldWhiteChannel' | 'warmWhiteChannel' | 'amberChannel' | 'uvChannel' > {} export interface ParResponse extends LightsFixtureResponse, ColorResponse {} -export interface MovingHeadResponse extends LightsFixtureResponse, Movement {} +export interface MovingHeadResponse extends LightsFixtureResponse, IMovement {} export interface MovingHeadRgbResponse extends MovingHeadResponse, ColorResponse {} @@ -62,12 +63,13 @@ export interface MovingHeadWheelColorChannelValueResponse { channelValue: number; } -export interface MovingHeadWheelResponse - extends MovingHeadResponse, - Pick { +export interface MovingHeadWheelResponse extends MovingHeadResponse { + wheelColorChannel: number; + wheelColorChannelValues: MovingHeadWheelColorChannelValueResponse[]; + wheelGoboChannel: number; gobos: string[]; + wheelGoboRotateChannel: number | null; goboRotates: string[]; - colorChannelValues: MovingHeadWheelColorChannelValueResponse[]; } export interface FixtureInGroupResponse< @@ -198,7 +200,7 @@ export default class RootLightsService { this.groupRepository = dataSource.getRepository(LightsGroup); } - private static toColorResponse(c: Colors, firstChannel: number): ColorResponse { + private static toColorResponse(c: ColorsRgb, firstChannel: number): ColorResponse { return { redChannel: c.redChannel + firstChannel - 1, blueChannel: c.blueChannel + firstChannel - 1, @@ -210,7 +212,7 @@ export default class RootLightsService { }; } - private static toMovementResponse(m: Movement, firstChannel: number): Movement { + private static toMovementResponse(m: Movement, firstChannel: number): IMovement { return { tiltChannel: m.tiltChannel + firstChannel - 1, fineTiltChannel: m.fineTiltChannel ? m.fineTiltChannel + firstChannel - 1 : null, @@ -284,15 +286,17 @@ export default class RootLightsService { ): MovingHeadWheelResponse { return { ...this.toMovingHeadResponse(m, firstChannel), - colorWheelChannel: m.colorWheelChannel + firstChannel - 1, - colorChannelValues: m.colorWheelChannelValues.map((x) => ({ + wheelColorChannel: m.wheel.colorChannel + firstChannel - 1, + wheelColorChannelValues: m.wheel.colorChannelValues.map((x) => ({ color: x.name, channelValue: x.value, })), - goboWheelChannel: m.goboWheelChannel + firstChannel - 1, - goboRotateChannel: m.goboRotateChannel ? m.goboRotateChannel + firstChannel - 1 : null, - gobos: m.goboWheelChannelValues.map((v) => v.name), - goboRotates: m.goboRotateChannelValues.map((v) => v.name), + wheelGoboChannel: m.wheel.goboChannel + firstChannel - 1, + wheelGoboRotateChannel: m.wheel.goboRotateChannel + ? m.wheel.goboRotateChannel + firstChannel - 1 + : null, + gobos: m.wheel.goboChannelValues.map((v) => v.name), + goboRotates: m.wheel.goboRotateChannelValues.map((v) => v.name), shutterChannelValues: this.getShutterChannelsResponse(m.shutterOptions), }; } @@ -457,7 +461,7 @@ export default class RootLightsService { } as LightsFixture; } - private toColor(params: ColorParams): Colors { + private toColorRgb(params: ColorParams): IColorsRgb { return { redChannel: params.colorRedChannel, blueChannel: params.colorBlueChannel, @@ -479,6 +483,14 @@ export default class RootLightsService { } as Movement; } + private toColorWheel(params: LightsMovingHeadWheelCreateParams): IColorsWheel { + return { + colorChannel: params.colorWheelChannel, + goboChannel: params.goboWheelChannel, + goboRotateChannel: params.goboRotateChannel, + }; + } + public async getAllLightsPars(): Promise { return dataSource.getRepository(LightsPar).find(); } @@ -518,7 +530,7 @@ export default class RootLightsService { const repository = dataSource.getRepository(LightsPar); const par = await repository.save({ ...this.toFixture(params), - color: this.toColor(params), + color: this.toColorRgb(params), }); par.shutterOptions = (await this.createFixtureShutterOptions( dataSource.getRepository(LightsParShutterOptions), @@ -535,7 +547,7 @@ export default class RootLightsService { const movingHead = await repository.save({ ...this.toFixture(params), movement: this.toMovement(params), - color: this.toColor(params), + color: this.toColorRgb(params), }); movingHead.shutterOptions = (await this.createFixtureShutterOptions( dataSource.getRepository(LightsMovingHeadRgbShutterOptions), @@ -552,9 +564,7 @@ export default class RootLightsService { const movingHead = await repository.save({ ...this.toFixture(params), movement: this.toMovement(params), - colorWheelChannel: params.colorWheelChannel, - goboWheelChannel: params.goboWheelChannel, - goboRotateChannel: params.goboRotateChannel, + wheel: this.toColorWheel(params), }); movingHead.shutterOptions = (await this.createFixtureShutterOptions( dataSource.getRepository(LightsMovingHeadWheelShutterOptions), From 9dcdff4c8c5c3a3ee18ffe6316d1e35a9fa36d29 Mon Sep 17 00:00:00 2001 From: Roy Kakkenberg Date: Sat, 25 Jan 2025 11:55:20 +0100 Subject: [PATCH 2/5] chore(lights): refactor master dimmer to relative brightness --- .../lights/effects/color/beat-fade-out.ts | 4 ++-- src/modules/lights/effects/color/fire.ts | 2 +- .../lights/effects/color/single-flood.ts | 2 +- src/modules/lights/effects/color/sparkle.ts | 4 ++-- .../lights/effects/color/static-color.ts | 8 +++----- src/modules/lights/effects/color/wave.ts | 2 +- src/modules/lights/entities/lights-fixture.ts | 17 +++++++++++++---- .../lights/entities/lights-moving-head-rgb.ts | 3 +-- .../lights/entities/lights-moving-head-wheel.ts | 3 +-- .../lights/entities/lights-moving-head.ts | 2 +- src/modules/lights/entities/lights-par.ts | 4 ++-- 11 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/modules/lights/effects/color/beat-fade-out.ts b/src/modules/lights/effects/color/beat-fade-out.ts index 16c12af..515dddd 100644 --- a/src/modules/lights/effects/color/beat-fade-out.ts +++ b/src/modules/lights/effects/color/beat-fade-out.ts @@ -125,9 +125,9 @@ export default class BeatFadeOut extends LightsEffect { 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); } } diff --git a/src/modules/lights/effects/color/fire.ts b/src/modules/lights/effects/color/fire.ts index 7d18db9..33ce12c 100644 --- a/src/modules/lights/effects/color/fire.ts +++ b/src/modules/lights/effects/color/fire.ts @@ -23,7 +23,7 @@ export default class Fire extends LightsEffect { tick(): LightsGroup { [...this.lightsGroup.pars, ...this.lightsGroup.movingHeadRgbs].forEach((p) => { - p.fixture.setMasterDimmer(128); + p.fixture.setBrightness(0.5); p.fixture.setCustomColor({ redChannel: 255, greenChannel: 32 + Math.round(Math.random() * 64), diff --git a/src/modules/lights/effects/color/single-flood.ts b/src/modules/lights/effects/color/single-flood.ts index 9e8ec36..ab19e30 100644 --- a/src/modules/lights/effects/color/single-flood.ts +++ b/src/modules/lights/effects/color/single-flood.ts @@ -57,7 +57,7 @@ export default class SingleFlood extends LightsEffect { 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()); diff --git a/src/modules/lights/effects/color/sparkle.ts b/src/modules/lights/effects/color/sparkle.ts index dc0548e..5cfc5ed 100644 --- a/src/modules/lights/effects/color/sparkle.ts +++ b/src/modules/lights/effects/color/sparkle.ts @@ -114,7 +114,7 @@ export default class Sparkle extends LightsEffect { 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; @@ -122,7 +122,7 @@ export default class Sparkle extends LightsEffect { 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; diff --git a/src/modules/lights/effects/color/static-color.ts b/src/modules/lights/effects/color/static-color.ts index 0b5bd80..04e867f 100644 --- a/src/modules/lights/effects/color/static-color.ts +++ b/src/modules/lights/effects/color/static-color.ts @@ -62,7 +62,7 @@ export default class StaticColor extends LightsEffect { 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) => { @@ -99,11 +99,9 @@ export default class StaticColor extends LightsEffect { .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); } }); diff --git a/src/modules/lights/effects/color/wave.ts b/src/modules/lights/effects/color/wave.ts index 1d218c3..c72901c 100644 --- a/src/modules/lights/effects/color/wave.ts +++ b/src/modules/lights/effects/color/wave.ts @@ -86,7 +86,7 @@ export default class Wave extends LightsEffect { 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]); }; diff --git a/src/modules/lights/entities/lights-fixture.ts b/src/modules/lights/entities/lights-fixture.ts index 93a674b..7674f2e 100644 --- a/src/modules/lights/entities/lights-fixture.ts +++ b/src/modules/lights/entities/lights-fixture.ts @@ -30,7 +30,7 @@ export default abstract class LightsFixture extends BaseEntity { }) public resetChannelAndValue?: number[] | null; - public currentMasterDim: number | undefined; + public currentBrightness: number = 1; public valuesUpdatedAt: Date; @@ -92,8 +92,14 @@ export default abstract class LightsFixture extends BaseEntity { public abstract setColor(color: RgbColor): void; public abstract resetColor(): void; - public setMasterDimmer(value: number) { - this.currentMasterDim = value; + /** + * Set the relative brightness of the fixture. + * Should be used by effects. + * @param brightness Value between [0, 1] + */ + public setBrightness(brightness: number) { + // Set upper and lower bounds to 1 and 0 respectively + this.currentBrightness = Math.max(0, Math.min(1, brightness)); this.valuesUpdatedAt = new Date(); } @@ -133,7 +139,10 @@ export default abstract class LightsFixture extends BaseEntity { * Apply a blackout to this fixture, i.e. set all channels to zero * @protected */ - public abstract blackout(): void; + public blackout(): void { + this.valuesUpdatedAt = new Date(); + this.currentBrightness = 1; + } /** * Get the DMX channels that should be used when the fixture should strobe diff --git a/src/modules/lights/entities/lights-moving-head-rgb.ts b/src/modules/lights/entities/lights-moving-head-rgb.ts index f89a253..cbf64d0 100644 --- a/src/modules/lights/entities/lights-moving-head-rgb.ts +++ b/src/modules/lights/entities/lights-moving-head-rgb.ts @@ -30,7 +30,6 @@ export default class LightsMovingHeadRgb extends LightsMovingHead { } public blackout() { - this.valuesUpdatedAt = new Date(); super.blackout(); this.color.reset(); } @@ -59,7 +58,7 @@ export default class LightsMovingHeadRgb extends LightsMovingHead { public getDmxFromCurrentValues(): number[] { let values: number[] = new Array(16).fill(0); - values[this.masterDimChannel - 1] = this.currentMasterDim ?? 0; + values[this.masterDimChannel - 1] = Math.round(this.currentBrightness * 255); values[this.shutterChannel - 1] = this.shutterOptions.find((o) => o.shutterOption === ShutterOption.OPEN)?.channelValue ?? 0; diff --git a/src/modules/lights/entities/lights-moving-head-wheel.ts b/src/modules/lights/entities/lights-moving-head-wheel.ts index af08041..16a599b 100644 --- a/src/modules/lights/entities/lights-moving-head-wheel.ts +++ b/src/modules/lights/entities/lights-moving-head-wheel.ts @@ -34,7 +34,6 @@ export default class LightsMovingHeadWheel extends LightsMovingHead { } public blackout() { - this.valuesUpdatedAt = new Date(); super.blackout(); this.wheel.reset(); } @@ -59,7 +58,7 @@ export default class LightsMovingHeadWheel extends LightsMovingHead { public getDmxFromCurrentValues(): number[] { let values: number[] = new Array(16).fill(0); - values[this.masterDimChannel - 1] = this.currentMasterDim ?? 0; + values[this.masterDimChannel - 1] = Math.round(this.currentBrightness * 255); values[this.shutterChannel - 1] = this.shutterOptions.find((o) => o.shutterOption === ShutterOption.OPEN)?.channelValue ?? 0; diff --git a/src/modules/lights/entities/lights-moving-head.ts b/src/modules/lights/entities/lights-moving-head.ts index 05210d6..d0fc6e9 100644 --- a/src/modules/lights/entities/lights-moving-head.ts +++ b/src/modules/lights/entities/lights-moving-head.ts @@ -30,7 +30,7 @@ export default abstract class LightsMovingHead extends LightsFixture { } public blackout(): void { - this.valuesUpdatedAt = new Date(); + super.blackout(); this.movement.reset(); } } diff --git a/src/modules/lights/entities/lights-par.ts b/src/modules/lights/entities/lights-par.ts index d493bc0..101686f 100644 --- a/src/modules/lights/entities/lights-par.ts +++ b/src/modules/lights/entities/lights-par.ts @@ -30,7 +30,7 @@ export default class LightsPar extends LightsFixture { } public blackout() { - this.valuesUpdatedAt = new Date(); + super.blackout(); this.color.reset(); } @@ -55,7 +55,7 @@ export default class LightsPar extends LightsFixture { public getDmxFromCurrentValues(): number[] { let values: number[] = new Array(16).fill(0); - values[this.masterDimChannel - 1] = this.currentMasterDim ?? 0; + values[this.masterDimChannel - 1] = Math.round(this.currentBrightness * 255); values[this.shutterChannel - 1] = this.shutterOptions.find((o) => o.shutterOption === ShutterOption.OPEN)?.channelValue ?? 0; From 638cfa4136a9db304227284f5b80b19c352529b0 Mon Sep 17 00:00:00 2001 From: Roy Kakkenberg Date: Sat, 25 Jan 2025 18:00:44 +0100 Subject: [PATCH 3/5] chore(lights)!: move master dim and shutter channels to color strategies --- src/modules/lights/entities/colors-rgb.ts | 28 ++++++- src/modules/lights/entities/colors-wheel.ts | 26 ++++++- src/modules/lights/entities/colors.ts | 76 +++++++++++++++++++ src/modules/lights/entities/lights-fixture.ts | 56 ++------------ .../lights/entities/lights-moving-head-rgb.ts | 44 +++++++---- .../entities/lights-moving-head-wheel.ts | 42 +++++++--- src/modules/lights/entities/lights-par.ts | 47 +++++++----- src/modules/root/root-lights-service.ts | 20 +++-- 8 files changed, 236 insertions(+), 103 deletions(-) create mode 100644 src/modules/lights/entities/colors.ts diff --git a/src/modules/lights/entities/colors-rgb.ts b/src/modules/lights/entities/colors-rgb.ts index 406fc48..aac10c3 100644 --- a/src/modules/lights/entities/colors-rgb.ts +++ b/src/modules/lights/entities/colors-rgb.ts @@ -1,5 +1,7 @@ import { Column } from 'typeorm'; import { RgbColor, rgbColorDefinitions } from '../color-definitions'; +import LightsFixtureShutterOptions, { ShutterOption } from './lights-fixture-shutter-options'; +import Colors from './colors'; export type ColorChannel = keyof ColorsRgb; @@ -13,7 +15,13 @@ export interface IColorsRgb { uvChannel?: number | null; } -export default class ColorsRgb implements IColorsRgb { +export default class ColorsRgb extends Colors implements IColorsRgb { + @Column({ type: 'tinyint', unsigned: true }) + public masterDimChannel: number; + + @Column({ type: 'tinyint', unsigned: true }) + public shutterChannel: number; + @Column({ type: 'tinyint', unsigned: true }) public redChannel: number; @@ -69,7 +77,23 @@ export default class ColorsRgb implements IColorsRgb { return this.currentValues; } - public setColorsInDmx(values: number[]): number[] { + public setStrobeInDmx(values: number[], shutterOptions: LightsFixtureShutterOptions[]): number[] { + values[this.masterDimChannel - 1] = 255; + values[this.shutterChannel - 1] = + shutterOptions.find((o) => o.shutterOption === ShutterOption.STROBE)?.channelValue ?? 0; + 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; + return values; + } + + public setColorsInDmx(values: number[], shutterOptions: LightsFixtureShutterOptions[]): number[] { + values[this.masterDimChannel - 1] = Math.round(this.currentBrightness * 255); + values[this.shutterChannel - 1] = + shutterOptions.find((o) => o.shutterOption === ShutterOption.OPEN)?.channelValue ?? 0; values[this.redChannel - 1] = this.channelValues.redChannel; values[this.greenChannel - 1] = this.channelValues.greenChannel; values[this.blueChannel - 1] = this.channelValues.blueChannel; diff --git a/src/modules/lights/entities/colors-wheel.ts b/src/modules/lights/entities/colors-wheel.ts index f12ac8c..3b53004 100644 --- a/src/modules/lights/entities/colors-wheel.ts +++ b/src/modules/lights/entities/colors-wheel.ts @@ -4,6 +4,8 @@ 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; @@ -11,9 +13,12 @@ export interface IColorsWheel { goboRotateChannel?: number | null; } -export default class ColorsWheel implements IColorsWheel { - @OneToMany(() => LightsMovingHeadWheelShutterOptions, (opt) => opt.fixture, { eager: true }) - public shutterOptions: LightsMovingHeadWheelShutterOptions[]; +export default class ColorsWheel extends Colors implements IColorsWheel { + @Column({ type: 'tinyint', unsigned: true }) + public masterDimChannel: number; + + @Column({ type: 'tinyint', unsigned: true }) + public shutterChannel: number; @Column({ type: 'tinyint', unsigned: true }) public colorChannel: number; @@ -76,7 +81,20 @@ export default class ColorsWheel implements IColorsWheel { return this.currentValues; } - public setColorsInDmx(values: number[]): number[] { + public setStrobeInDmx(values: number[], shutterOptions: LightsFixtureShutterOptions[]): number[] { + values[this.masterDimChannel - 1] = 255; + 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; + + return values; + } + + public setColorsInDmx(values: number[], shutterOptions: LightsFixtureShutterOptions[]): number[] { + values[this.masterDimChannel - 1] = Math.round(this.currentBrightness * 255); + 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) { diff --git a/src/modules/lights/entities/colors.ts b/src/modules/lights/entities/colors.ts new file mode 100644 index 0000000..0c02730 --- /dev/null +++ b/src/modules/lights/entities/colors.ts @@ -0,0 +1,76 @@ +import { RgbColor } from '../color-definitions'; +import LightsFixtureShutterOptions from './lights-fixture-shutter-options'; + +export default abstract class Colors { + protected currentBrightness: number = 1; + + protected strobe: boolean = false; + private strobeDisableEvent: NodeJS.Timeout | undefined; + + public abstract setColor(color: RgbColor): void; + public abstract reset(): void; + + /** + * Set the relative brightness of the fixture. + * Should be used by effects. + * @param brightness Value between [0, 1] + */ + public setBrightness(brightness: number) { + // Set upper and lower bounds to 1 and 0 respectively + this.currentBrightness = Math.max(0, Math.min(1, brightness)); + } + + /** + * Start strobing + * @param milliseconds After how many ms the strobe should automatically + * be disabled. + */ + public enableStrobe(milliseconds?: number): void { + this.strobe = true; + + // Stop an existing stop strobe timeout if it exists + if (this.strobeDisableEvent) { + clearTimeout(this.strobeDisableEvent); + this.strobeDisableEvent = undefined; + } + + // Create a stop strobe timeout if a time is given + if (milliseconds) { + this.strobeDisableEvent = setTimeout(this.disableStrobe.bind(this), milliseconds); + } + } + + /** + * Stop strobe if strobing + */ + public disableStrobe(): void { + this.strobe = false; + + if (this.strobeDisableEvent) { + clearTimeout(this.strobeDisableEvent); + this.strobeDisableEvent = undefined; + } + } + + /** + * Whether strobe is enabled + */ + public strobeEnabled(): boolean { + return this.strobe; + } + + public abstract setStrobeInDmx( + values: number[], + shutterOptions: LightsFixtureShutterOptions[], + ): number[]; + + /** + * Apply colors to the given DMX subpacket (in-place) + * @param values + * @param shutterOptions + */ + public abstract setColorsInDmx( + values: number[], + shutterOptions: LightsFixtureShutterOptions[], + ): number[]; +} diff --git a/src/modules/lights/entities/lights-fixture.ts b/src/modules/lights/entities/lights-fixture.ts index 7674f2e..b2d2b43 100644 --- a/src/modules/lights/entities/lights-fixture.ts +++ b/src/modules/lights/entities/lights-fixture.ts @@ -2,18 +2,10 @@ import { AfterLoad, Column } from 'typeorm'; import BaseEntity from '../../root/entities/base-entity'; import { RgbColor } from '../color-definitions'; -export interface LightsFixtureCurrentValues extends Pick {} - export default abstract class LightsFixture extends BaseEntity { @Column() public name: string; - @Column({ type: 'tinyint', unsigned: true }) - public masterDimChannel: number; - - @Column({ type: 'tinyint', unsigned: true }) - public shutterChannel: number; - @Column({ type: 'varchar', transformer: { @@ -30,8 +22,6 @@ export default abstract class LightsFixture extends BaseEntity { }) public resetChannelAndValue?: number[] | null; - public currentBrightness: number = 1; - public valuesUpdatedAt: Date; private overrideDmx: (number | null)[] = new Array(16).fill(null); @@ -47,10 +37,6 @@ export default abstract class LightsFixture extends BaseEntity { protected shouldReset: Date | undefined; - protected strobeEnabled = false; - - private strobeDisableEvent: NodeJS.Timeout | undefined; - /** * Reset the fixture if possible. * @return true if reset command can be sent. False otherwise @@ -61,47 +47,22 @@ export default abstract class LightsFixture extends BaseEntity { return true; } - /** - * How long the strobe needs to be enabled - * @param milliseconds - */ - enableStrobe(milliseconds?: number) { - this.strobeEnabled = true; - this.valuesUpdatedAt = new Date(); - - // Stop an existing stop strobe timeout if it exists - if (this.strobeDisableEvent) { - clearTimeout(this.strobeDisableEvent); - this.strobeDisableEvent = undefined; - } - - // Create a stop strobe timeout if a time is given - if (milliseconds) { - this.strobeDisableEvent = setTimeout(this.disableStrobe.bind(this), milliseconds); - } - } - - /** - * Disable the strobe if it is enabled - */ - disableStrobe() { - this.strobeEnabled = false; - this.valuesUpdatedAt = new Date(); - } + public abstract get masterDimChannel(): number; + public abstract get shutterChannel(): number; public abstract setColor(color: RgbColor): void; public abstract resetColor(): void; + public abstract enableStrobe(milliseconds?: number): void; + protected abstract strobeEnabled(): boolean; + public abstract disableStrobe(): void; + /** * Set the relative brightness of the fixture. * Should be used by effects. * @param brightness Value between [0, 1] */ - public setBrightness(brightness: number) { - // Set upper and lower bounds to 1 and 0 respectively - this.currentBrightness = Math.max(0, Math.min(1, brightness)); - this.valuesUpdatedAt = new Date(); - } + public abstract setBrightness(brightness: number): void; /** * Override any set relative DMX channels with the given values. @@ -141,7 +102,6 @@ export default abstract class LightsFixture extends BaseEntity { */ public blackout(): void { this.valuesUpdatedAt = new Date(); - this.currentBrightness = 1; } /** @@ -160,7 +120,7 @@ export default abstract class LightsFixture extends BaseEntity { * Get the current DMX values as an 16-length array of integers. */ toDmx(): number[] { - if (this.strobeEnabled) return this.getStrobeDMX(); + if (this.strobeEnabled()) return this.getStrobeDMX(); if (this.frozenDmx != null && this.frozenDmx.length > 0) { return this.frozenDmx; diff --git a/src/modules/lights/entities/lights-moving-head-rgb.ts b/src/modules/lights/entities/lights-moving-head-rgb.ts index cbf64d0..13e1ab6 100644 --- a/src/modules/lights/entities/lights-moving-head-rgb.ts +++ b/src/modules/lights/entities/lights-moving-head-rgb.ts @@ -4,7 +4,6 @@ import ColorsRgb, { IColorsRgb } from './colors-rgb'; import { RgbColor } from '../color-definitions'; // eslint-disable-next-line import/no-cycle import LightsMovingHeadRgbShutterOptions from './lights-moving-head-rgb-shutter-options'; -import { ShutterOption } from './lights-fixture-shutter-options'; @Entity() export default class LightsMovingHeadRgb extends LightsMovingHead { @@ -14,6 +13,14 @@ export default class LightsMovingHeadRgb extends LightsMovingHead { @Column(() => ColorsRgb) public color: ColorsRgb; + public get masterDimChannel(): number { + return this.color.masterDimChannel; + } + + public get shutterChannel(): number { + return this.color.shutterChannel; + } + public setColor(color: RgbColor) { this.valuesUpdatedAt = new Date(); this.color.setColor(color); @@ -34,22 +41,33 @@ export default class LightsMovingHeadRgb extends LightsMovingHead { this.color.reset(); } + public disableStrobe(): void { + this.valuesUpdatedAt = new Date(); + this.color.disableStrobe(); + } + + public enableStrobe(milliseconds?: number): void { + this.valuesUpdatedAt = new Date(); + this.color.disableStrobe(); + } + + public setBrightness(brightness: number): void { + this.valuesUpdatedAt = new Date(); + this.color.setBrightness(brightness); + } + + protected strobeEnabled(): boolean { + return this.color.strobeEnabled(); + } + /** * Get the DMX packet for a strobing light * @protected */ protected getStrobeDMX(): number[] { let values: number[] = new Array(16).fill(0); - values[this.masterDimChannel - 1] = 255; - values[this.shutterChannel - 1] = - this.shutterOptions.find((o) => o.shutterOption === ShutterOption.STROBE)?.channelValue ?? 0; - values[this.color.redChannel - 1] = 255; - values[this.color.blueChannel - 1] = 255; - values[this.color.greenChannel - 1] = 255; - if (this.color.warmWhiteChannel) values[this.color.warmWhiteChannel - 1] = 255; - if (this.color.coldWhiteChannel) values[this.color.coldWhiteChannel - 1] = 255; - if (this.color.amberChannel) values[this.color.amberChannel - 1] = 255; + values = this.color.setStrobeInDmx(values, this.shutterOptions); values = this.setPositionInDmx(values); return values; @@ -58,11 +76,7 @@ export default class LightsMovingHeadRgb extends LightsMovingHead { public getDmxFromCurrentValues(): number[] { let values: number[] = new Array(16).fill(0); - values[this.masterDimChannel - 1] = Math.round(this.currentBrightness * 255); - values[this.shutterChannel - 1] = - this.shutterOptions.find((o) => o.shutterOption === ShutterOption.OPEN)?.channelValue ?? 0; - - values = this.color.setColorsInDmx(values); + values = this.color.setColorsInDmx(values, this.shutterOptions); values = this.setPositionInDmx(values); return values; diff --git a/src/modules/lights/entities/lights-moving-head-wheel.ts b/src/modules/lights/entities/lights-moving-head-wheel.ts index 16a599b..77bda6b 100644 --- a/src/modules/lights/entities/lights-moving-head-wheel.ts +++ b/src/modules/lights/entities/lights-moving-head-wheel.ts @@ -1,8 +1,7 @@ import { Column, Entity, OneToMany } from 'typeorm'; import LightsMovingHead from './lights-moving-head'; -import { RgbColor, WheelColor } from '../color-definitions'; +import { RgbColor } from '../color-definitions'; import LightsMovingHeadWheelShutterOptions from './lights-moving-head-wheel-shutter-options'; -import { ShutterOption } from './lights-fixture-shutter-options'; import ColorsWheel from './colors-wheel'; @Entity() @@ -13,6 +12,14 @@ export default class LightsMovingHeadWheel extends LightsMovingHead { @Column(() => ColorsWheel) public wheel: ColorsWheel; + public get masterDimChannel(): number { + return this.wheel.masterDimChannel; + } + + public get shutterChannel(): number { + return this.wheel.shutterChannel; + } + public setColor(color: RgbColor) { this.valuesUpdatedAt = new Date(); this.wheel.setColor(color); @@ -38,18 +45,33 @@ export default class LightsMovingHeadWheel extends LightsMovingHead { this.wheel.reset(); } + public disableStrobe(): void { + this.valuesUpdatedAt = new Date(); + this.wheel.disableStrobe(); + } + + public enableStrobe(milliseconds?: number): void { + this.valuesUpdatedAt = new Date(); + this.wheel.disableStrobe(); + } + + public setBrightness(brightness: number): void { + this.valuesUpdatedAt = new Date(); + this.wheel.setBrightness(brightness); + } + + protected strobeEnabled(): boolean { + return this.wheel.strobeEnabled(); + } + /** * Get the DMX packet for a strobing light * @protected */ protected getStrobeDMX(): number[] { let values: number[] = new Array(16).fill(0); - values[this.masterDimChannel - 1] = 255; - values[this.shutterChannel - 1] = - this.shutterOptions.find((o) => o.shutterOption === ShutterOption.STROBE)?.channelValue ?? 0; - values[this.wheel.colorChannel - 1] = - this.wheel.colorChannelValues.find((o) => o.name === WheelColor.WHITE)?.value ?? 0; + values = this.wheel.setStrobeInDmx(values, this.shutterOptions); values = this.setPositionInDmx(values); return values; @@ -58,11 +80,7 @@ export default class LightsMovingHeadWheel extends LightsMovingHead { public getDmxFromCurrentValues(): number[] { let values: number[] = new Array(16).fill(0); - values[this.masterDimChannel - 1] = Math.round(this.currentBrightness * 255); - values[this.shutterChannel - 1] = - this.shutterOptions.find((o) => o.shutterOption === ShutterOption.OPEN)?.channelValue ?? 0; - - values = this.wheel.setColorsInDmx(values); + values = this.wheel.setColorsInDmx(values, this.shutterOptions); values = this.setPositionInDmx(values); return values; diff --git a/src/modules/lights/entities/lights-par.ts b/src/modules/lights/entities/lights-par.ts index 101686f..48c49c3 100644 --- a/src/modules/lights/entities/lights-par.ts +++ b/src/modules/lights/entities/lights-par.ts @@ -1,10 +1,9 @@ import { Column, Entity, OneToMany } from 'typeorm'; import LightsFixture from './lights-fixture'; import ColorsRgb, { IColorsRgb } from './colors-rgb'; -import { RgbColor, rgbColorDefinitions } from '../color-definitions'; +import { RgbColor } from '../color-definitions'; // eslint-disable-next-line import/no-cycle import LightsParShutterOptions from './lights-par-shutter-options'; -import { ShutterOption } from './lights-fixture-shutter-options'; @Entity() export default class LightsPar extends LightsFixture { @@ -14,6 +13,14 @@ export default class LightsPar extends LightsFixture { @Column(() => ColorsRgb) public color: ColorsRgb; + public get masterDimChannel(): number { + return this.color.masterDimChannel; + } + + public get shutterChannel(): number { + return this.color.shutterChannel; + } + public setColor(color: RgbColor) { this.valuesUpdatedAt = new Date(); this.color.setColor(color); @@ -34,32 +41,38 @@ export default class LightsPar extends LightsFixture { this.color.reset(); } + public disableStrobe(): void { + this.valuesUpdatedAt = new Date(); + this.color.disableStrobe(); + } + + public enableStrobe(milliseconds?: number): void { + this.valuesUpdatedAt = new Date(); + this.color.disableStrobe(); + } + + public setBrightness(brightness: number): void { + this.valuesUpdatedAt = new Date(); + this.color.setBrightness(brightness); + } + + protected strobeEnabled(): boolean { + return this.color.strobeEnabled(); + } + /** * Get the DMX packet for a strobing light (16 channels) * @protected */ protected getStrobeDMX(): number[] { const values: number[] = new Array(16).fill(0); - values[this.masterDimChannel - 1] = 255; - values[this.shutterChannel - 1] = - this.shutterOptions.find((o) => o.shutterOption === ShutterOption.STROBE)?.channelValue ?? 0; - values[this.color.redChannel - 1] = 255; - values[this.color.blueChannel - 1] = 255; - values[this.color.greenChannel - 1] = 255; - if (this.color.warmWhiteChannel) values[this.color.warmWhiteChannel - 1] = 255; - if (this.color.coldWhiteChannel) values[this.color.coldWhiteChannel - 1] = 255; - if (this.color.amberChannel) values[this.color.amberChannel - 1] = 255; - return values; + return this.color.setStrobeInDmx(values, this.shutterOptions); } public getDmxFromCurrentValues(): number[] { let values: number[] = new Array(16).fill(0); - values[this.masterDimChannel - 1] = Math.round(this.currentBrightness * 255); - values[this.shutterChannel - 1] = - this.shutterOptions.find((o) => o.shutterOption === ShutterOption.OPEN)?.channelValue ?? 0; - - values = this.color.setColorsInDmx(values); + values = this.color.setColorsInDmx(values, this.shutterOptions); return values; } diff --git a/src/modules/root/root-lights-service.ts b/src/modules/root/root-lights-service.ts index 6ac12af..d0e0693 100644 --- a/src/modules/root/root-lights-service.ts +++ b/src/modules/root/root-lights-service.ts @@ -456,8 +456,6 @@ export default class RootLightsService { private toFixture(params: LightsFixtureParams): LightsFixture { return { name: params.name, - masterDimChannel: params.masterDimChannel, - shutterChannel: params.shutterChannel, } as LightsFixture; } @@ -530,7 +528,11 @@ export default class RootLightsService { const repository = dataSource.getRepository(LightsPar); const par = await repository.save({ ...this.toFixture(params), - color: this.toColorRgb(params), + color: { + ...this.toColorRgb(params), + masterDimChannel: params.masterDimChannel, + shutterChannel: params.shutterChannel, + }, }); par.shutterOptions = (await this.createFixtureShutterOptions( dataSource.getRepository(LightsParShutterOptions), @@ -547,7 +549,11 @@ export default class RootLightsService { const movingHead = await repository.save({ ...this.toFixture(params), movement: this.toMovement(params), - color: this.toColorRgb(params), + color: { + ...this.toColorRgb(params), + masterDimChannel: params.masterDimChannel, + shutterChannel: params.shutterChannel, + }, }); movingHead.shutterOptions = (await this.createFixtureShutterOptions( dataSource.getRepository(LightsMovingHeadRgbShutterOptions), @@ -564,7 +570,11 @@ export default class RootLightsService { const movingHead = await repository.save({ ...this.toFixture(params), movement: this.toMovement(params), - wheel: this.toColorWheel(params), + wheel: { + ...this.toColorWheel(params), + masterDimChannel: params.masterDimChannel, + shutterChannel: params.shutterChannel, + }, }); movingHead.shutterOptions = (await this.createFixtureShutterOptions( dataSource.getRepository(LightsMovingHeadWheelShutterOptions), From cc6194ead79bdb91ccc1e3f22a4e346cb7e34ada Mon Sep 17 00:00:00 2001 From: Roy Kakkenberg Date: Sat, 25 Jan 2025 18:59:44 +0100 Subject: [PATCH 4/5] feat(lights): make master dimmer and shutter optional --- src/modules/lights/entities/colors-rgb.ts | 85 +++++++++++++------ src/modules/lights/entities/colors-wheel.ts | 31 +++++-- src/modules/lights/entities/lights-fixture.ts | 3 - .../lights/entities/lights-moving-head-rgb.ts | 21 +++-- .../entities/lights-moving-head-wheel.ts | 21 +++-- src/modules/lights/entities/lights-par.ts | 27 +++--- src/modules/root/root-lights-service.ts | 35 ++++---- 7 files changed, 136 insertions(+), 87 deletions(-) diff --git a/src/modules/lights/entities/colors-rgb.ts b/src/modules/lights/entities/colors-rgb.ts index aac10c3..b031c4c 100644 --- a/src/modules/lights/entities/colors-rgb.ts +++ b/src/modules/lights/entities/colors-rgb.ts @@ -2,6 +2,7 @@ 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; @@ -16,11 +17,11 @@ export interface IColorsRgb { } export default class ColorsRgb extends Colors implements IColorsRgb { - @Column({ type: 'tinyint', unsigned: true }) - public masterDimChannel: number; + @Column({ type: 'tinyint', unsigned: true, nullable: true }) + public masterDimChannel?: number; - @Column({ type: 'tinyint', unsigned: true }) - public shutterChannel: number; + @Column({ type: 'tinyint', unsigned: true, nullable: true }) + public shutterChannel?: number; @Column({ type: 'tinyint', unsigned: true }) public redChannel: number; @@ -43,7 +44,9 @@ export default class ColorsRgb extends Colors implements IColorsRgb { @Column({ type: 'tinyint', nullable: true, unsigned: true }) public uvChannel?: number | null; - private currentValues: IColorsRgb = { + private strobePing = false; + + private currentValues: Required = { redChannel: 0, greenChannel: 0, blueChannel: 0, @@ -58,7 +61,10 @@ export default class ColorsRgb extends Colors implements IColorsRgb { } public setCustomColor(color: IColorsRgb): void { - this.currentValues = color; + const givenColors = Object.keys(color) as (keyof IColorsRgb)[]; + givenColors.forEach((key: keyof IColorsRgb) => { + this.currentValues[key] = color[key]!; + }); } public reset(): void { @@ -73,41 +79,66 @@ export default class ColorsRgb extends Colors implements IColorsRgb { }; } - private get channelValues() { - return this.currentValues; + private getColor(color: keyof Required): 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[] { - values[this.masterDimChannel - 1] = 255; - values[this.shutterChannel - 1] = - shutterOptions.find((o) => o.shutterOption === ShutterOption.STROBE)?.channelValue ?? 0; - 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; + 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[] { - values[this.masterDimChannel - 1] = Math.round(this.currentBrightness * 255); - values[this.shutterChannel - 1] = - shutterOptions.find((o) => o.shutterOption === ShutterOption.OPEN)?.channelValue ?? 0; - values[this.redChannel - 1] = this.channelValues.redChannel; - values[this.greenChannel - 1] = this.channelValues.greenChannel; - values[this.blueChannel - 1] = this.channelValues.blueChannel; + 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.channelValues.coldWhiteChannel || 0; + values[this.coldWhiteChannel - 1] = this.getColor('coldWhiteChannel'); } if (this.warmWhiteChannel != null) { - values[this.warmWhiteChannel - 1] = this.channelValues.warmWhiteChannel || 0; + values[this.warmWhiteChannel - 1] = this.getColor('warmWhiteChannel'); } if (this.amberChannel != null) { - values[this.amberChannel - 1] = this.channelValues.amberChannel || 0; + values[this.amberChannel - 1] = this.getColor('amberChannel'); } if (this.uvChannel != null) { - values[this.uvChannel - 1] = this.channelValues.uvChannel || 0; + values[this.uvChannel - 1] = this.getColor('uvChannel'); } return values; diff --git a/src/modules/lights/entities/colors-wheel.ts b/src/modules/lights/entities/colors-wheel.ts index 3b53004..fe0d8c5 100644 --- a/src/modules/lights/entities/colors-wheel.ts +++ b/src/modules/lights/entities/colors-wheel.ts @@ -17,8 +17,8 @@ export default class ColorsWheel extends Colors implements IColorsWheel { @Column({ type: 'tinyint', unsigned: true }) public masterDimChannel: number; - @Column({ type: 'tinyint', unsigned: true }) - public shutterChannel: number; + @Column({ type: 'tinyint', unsigned: true, nullable: true }) + public shutterChannel?: number; @Column({ type: 'tinyint', unsigned: true }) public colorChannel: number; @@ -38,6 +38,8 @@ export default class ColorsWheel extends Colors implements IColorsWheel { @OneToMany(() => LightsWheelRotateChannelValue, (c) => c.movingHead, { eager: true }) public goboRotateChannelValues: LightsWheelRotateChannelValue[]; + private strobePing = false; + private currentValues: IColorsWheel = { colorChannel: 0, goboChannel: 0, @@ -82,19 +84,34 @@ export default class ColorsWheel extends Colors implements IColorsWheel { } public setStrobeInDmx(values: number[], shutterOptions: LightsFixtureShutterOptions[]): number[] { - values[this.masterDimChannel - 1] = 255; - values[this.shutterChannel - 1] = - shutterOptions.find((o) => o.shutterOption === ShutterOption.STROBE)?.channelValue ?? 0; + 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); - values[this.shutterChannel - 1] = - shutterOptions.find((o) => o.shutterOption === ShutterOption.OPEN)?.channelValue ?? 0; + 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) { diff --git a/src/modules/lights/entities/lights-fixture.ts b/src/modules/lights/entities/lights-fixture.ts index b2d2b43..6786bdd 100644 --- a/src/modules/lights/entities/lights-fixture.ts +++ b/src/modules/lights/entities/lights-fixture.ts @@ -47,9 +47,6 @@ export default abstract class LightsFixture extends BaseEntity { return true; } - public abstract get masterDimChannel(): number; - public abstract get shutterChannel(): number; - public abstract setColor(color: RgbColor): void; public abstract resetColor(): void; diff --git a/src/modules/lights/entities/lights-moving-head-rgb.ts b/src/modules/lights/entities/lights-moving-head-rgb.ts index 13e1ab6..e7a659c 100644 --- a/src/modules/lights/entities/lights-moving-head-rgb.ts +++ b/src/modules/lights/entities/lights-moving-head-rgb.ts @@ -13,14 +13,6 @@ export default class LightsMovingHeadRgb extends LightsMovingHead { @Column(() => ColorsRgb) public color: ColorsRgb; - public get masterDimChannel(): number { - return this.color.masterDimChannel; - } - - public get shutterChannel(): number { - return this.color.shutterChannel; - } - public setColor(color: RgbColor) { this.valuesUpdatedAt = new Date(); this.color.setColor(color); @@ -41,12 +33,12 @@ export default class LightsMovingHeadRgb extends LightsMovingHead { this.color.reset(); } - public disableStrobe(): void { + public enableStrobe(milliseconds?: number): void { this.valuesUpdatedAt = new Date(); - this.color.disableStrobe(); + this.color.enableStrobe(milliseconds); } - public enableStrobe(milliseconds?: number): void { + public disableStrobe(): void { this.valuesUpdatedAt = new Date(); this.color.disableStrobe(); } @@ -70,6 +62,13 @@ export default class LightsMovingHeadRgb extends LightsMovingHead { values = this.color.setStrobeInDmx(values, this.shutterOptions); values = this.setPositionInDmx(values); + if (!this.color.shutterChannel) { + // The getStrobeInDmx() value changes its state if we need to + // strobe manually. Because of optimizations, we need to also + // indicate the state has changed. + this.valuesUpdatedAt = new Date(new Date().getTime() + 1000); + } + return values; } diff --git a/src/modules/lights/entities/lights-moving-head-wheel.ts b/src/modules/lights/entities/lights-moving-head-wheel.ts index 77bda6b..0fb9653 100644 --- a/src/modules/lights/entities/lights-moving-head-wheel.ts +++ b/src/modules/lights/entities/lights-moving-head-wheel.ts @@ -12,14 +12,6 @@ export default class LightsMovingHeadWheel extends LightsMovingHead { @Column(() => ColorsWheel) public wheel: ColorsWheel; - public get masterDimChannel(): number { - return this.wheel.masterDimChannel; - } - - public get shutterChannel(): number { - return this.wheel.shutterChannel; - } - public setColor(color: RgbColor) { this.valuesUpdatedAt = new Date(); this.wheel.setColor(color); @@ -45,12 +37,12 @@ export default class LightsMovingHeadWheel extends LightsMovingHead { this.wheel.reset(); } - public disableStrobe(): void { + public enableStrobe(milliseconds?: number): void { this.valuesUpdatedAt = new Date(); - this.wheel.disableStrobe(); + this.wheel.enableStrobe(milliseconds); } - public enableStrobe(milliseconds?: number): void { + public disableStrobe(): void { this.valuesUpdatedAt = new Date(); this.wheel.disableStrobe(); } @@ -74,6 +66,13 @@ export default class LightsMovingHeadWheel extends LightsMovingHead { values = this.wheel.setStrobeInDmx(values, this.shutterOptions); values = this.setPositionInDmx(values); + if (!this.wheel.shutterChannel) { + // The getStrobeInDmx() value changes its state if we need to + // strobe manually. Because of optimizations, we need to also + // indicate the state has changed. + this.valuesUpdatedAt = new Date(new Date().getTime() + 1000); + } + return values; } diff --git a/src/modules/lights/entities/lights-par.ts b/src/modules/lights/entities/lights-par.ts index 48c49c3..c262fb5 100644 --- a/src/modules/lights/entities/lights-par.ts +++ b/src/modules/lights/entities/lights-par.ts @@ -13,14 +13,6 @@ export default class LightsPar extends LightsFixture { @Column(() => ColorsRgb) public color: ColorsRgb; - public get masterDimChannel(): number { - return this.color.masterDimChannel; - } - - public get shutterChannel(): number { - return this.color.shutterChannel; - } - public setColor(color: RgbColor) { this.valuesUpdatedAt = new Date(); this.color.setColor(color); @@ -41,12 +33,12 @@ export default class LightsPar extends LightsFixture { this.color.reset(); } - public disableStrobe(): void { + public enableStrobe(milliseconds?: number): void { this.valuesUpdatedAt = new Date(); - this.color.disableStrobe(); + this.color.enableStrobe(milliseconds); } - public enableStrobe(milliseconds?: number): void { + public disableStrobe(): void { this.valuesUpdatedAt = new Date(); this.color.disableStrobe(); } @@ -65,8 +57,17 @@ export default class LightsPar extends LightsFixture { * @protected */ protected getStrobeDMX(): number[] { - const values: number[] = new Array(16).fill(0); - return this.color.setStrobeInDmx(values, this.shutterOptions); + let values: number[] = new Array(16).fill(0); + values = this.color.setStrobeInDmx(values, this.shutterOptions); + + if (!this.color.shutterChannel) { + // The getStrobeInDmx() value changes its state if we need to + // strobe manually. Because of optimizations, we need to also + // indicate the state has changed. + this.valuesUpdatedAt = new Date(new Date().getTime() + 1000); + } + + return values; } public getDmxFromCurrentValues(): number[] { diff --git a/src/modules/root/root-lights-service.ts b/src/modules/root/root-lights-service.ts index d0e0693..4559c30 100644 --- a/src/modules/root/root-lights-service.ts +++ b/src/modules/root/root-lights-service.ts @@ -22,7 +22,7 @@ import LightsFixtureShutterOptions, { ShutterOption, } from '../lights/entities/lights-fixture-shutter-options'; import { WheelColor } from '../lights/color-definitions'; -import ColorsWheel, { IColorsWheel } from '../lights/entities/colors-wheel'; +import { IColorsWheel } from '../lights/entities/colors-wheel'; export interface LightsControllerResponse extends Pick {} @@ -33,10 +33,7 @@ export interface ShutterChannelValuesResponse { } export interface LightsFixtureResponse - extends Pick< - LightsFixture, - 'id' | 'createdAt' | 'updatedAt' | 'name' | 'masterDimChannel' | 'shutterChannel' - > { + extends Pick { canReset: boolean; resetChannel?: number; resetChannelValue?: number; @@ -49,7 +46,7 @@ export interface LightsFixtureResponse export interface ColorResponse extends Pick< ColorsRgb, - 'redChannel' | 'blueChannel' | 'greenChannel' | 'coldWhiteChannel' | 'warmWhiteChannel' | 'amberChannel' | 'uvChannel' + 'masterDimChannel' | 'shutterChannel' | 'redChannel' | 'blueChannel' | 'greenChannel' | 'coldWhiteChannel' | 'warmWhiteChannel' | 'amberChannel' | 'uvChannel' > {} export interface ParResponse extends LightsFixtureResponse, ColorResponse {} @@ -64,6 +61,8 @@ export interface MovingHeadWheelColorChannelValueResponse { } export interface MovingHeadWheelResponse extends MovingHeadResponse { + masterDimChannel: number; + shutterChannel?: number; wheelColorChannel: number; wheelColorChannelValues: MovingHeadWheelColorChannelValueResponse[]; wheelGoboChannel: number; @@ -95,6 +94,8 @@ export interface LightsGroupResponse extends BaseLightsGroupResponse { } export interface ColorParams { + masterDimChannel?: number; + shutterChannel?: number; colorRedChannel: number; colorGreenChannel: number; colorBlueChannel: number; @@ -109,9 +110,8 @@ export interface ShutterOptionValues { strobe: number; } -export interface LightsFixtureParams - extends Pick { - shutterOptionValues: ShutterOptionValues; +export interface LightsFixtureParams extends Pick { + shutterOptionValues?: ShutterOptionValues; } export interface LightsParCreateParams extends LightsFixtureParams, ColorParams {} @@ -127,6 +127,8 @@ export interface LightsMovingHeadParams extends LightsFixtureParams { export interface LightsMovingHeadRgbCreateParams extends LightsMovingHeadParams, ColorParams {} export interface LightsMovingHeadWheelCreateParams extends LightsMovingHeadParams { + masterDimChannel: number; + shutterChannel?: number; colorWheelChannel: number; colorWheelChannelValues: { name: string; @@ -202,6 +204,8 @@ export default class RootLightsService { private static toColorResponse(c: ColorsRgb, firstChannel: number): ColorResponse { return { + masterDimChannel: c.masterDimChannel ? c.masterDimChannel + firstChannel - 1 : undefined, + shutterChannel: c.shutterChannel ? c.shutterChannel + firstChannel - 1 : undefined, redChannel: c.redChannel + firstChannel - 1, blueChannel: c.blueChannel + firstChannel - 1, greenChannel: c.greenChannel + firstChannel - 1, @@ -230,8 +234,6 @@ export default class RootLightsService { createdAt: f.createdAt, updatedAt: f.updatedAt, name: f.name, - masterDimChannel: f.masterDimChannel + firstChannel - 1, - shutterChannel: f.shutterChannel + firstChannel - 1, shutterChannelValues: {}, canReset, resetChannel: canReset ? f.resetChannelAndValue![0] + firstChannel - 1 : undefined, @@ -286,6 +288,8 @@ export default class RootLightsService { ): MovingHeadWheelResponse { return { ...this.toMovingHeadResponse(m, firstChannel), + masterDimChannel: m.wheel.masterDimChannel, + shutterChannel: m.wheel.shutterChannel, wheelColorChannel: m.wheel.colorChannel + firstChannel - 1, wheelColorChannelValues: m.wheel.colorChannelValues.map((x) => ({ color: x.name, @@ -508,8 +512,9 @@ export default class RootLightsService { | LightsMovingHeadWheelShutterOptions >, fixture: LightsFixture, - params: ShutterOptionValues, + params: ShutterOptionValues | undefined, ): Promise { + if (!params) return []; return Promise.all([ repo.save({ fixtureId: fixture.id, @@ -531,7 +536,7 @@ export default class RootLightsService { color: { ...this.toColorRgb(params), masterDimChannel: params.masterDimChannel, - shutterChannel: params.shutterChannel, + shutterChannel: params.shutterOptionValues ? params.shutterChannel : undefined, }, }); par.shutterOptions = (await this.createFixtureShutterOptions( @@ -552,7 +557,7 @@ export default class RootLightsService { color: { ...this.toColorRgb(params), masterDimChannel: params.masterDimChannel, - shutterChannel: params.shutterChannel, + shutterChannel: params.shutterOptionValues ? params.shutterChannel : undefined, }, }); movingHead.shutterOptions = (await this.createFixtureShutterOptions( @@ -573,7 +578,7 @@ export default class RootLightsService { wheel: { ...this.toColorWheel(params), masterDimChannel: params.masterDimChannel, - shutterChannel: params.shutterChannel, + shutterChannel: params.shutterOptionValues ? params.shutterChannel : undefined, }, }); movingHead.shutterOptions = (await this.createFixtureShutterOptions( From ed1185ae6996766091370774b43b713bd451a08c Mon Sep 17 00:00:00 2001 From: Roy Kakkenberg Date: Sat, 25 Jan 2025 19:19:25 +0100 Subject: [PATCH 5/5] feat(lights): add specific nr of channels required for each fixture --- src/modules/lights/entities/lights-fixture.ts | 23 +++++++++++++++++-- .../lights/entities/lights-moving-head-rgb.ts | 4 ++-- .../entities/lights-moving-head-wheel.ts | 4 ++-- src/modules/lights/entities/lights-par.ts | 4 ++-- src/modules/root/root-lights-service.ts | 6 +++-- src/seed/seed.ts | 15 ++++++------ 6 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/modules/lights/entities/lights-fixture.ts b/src/modules/lights/entities/lights-fixture.ts index 6786bdd..0bd1436 100644 --- a/src/modules/lights/entities/lights-fixture.ts +++ b/src/modules/lights/entities/lights-fixture.ts @@ -6,6 +6,9 @@ export default abstract class LightsFixture extends BaseEntity { @Column() public name: string; + @Column({ type: 'tinyint', unsigned: true }) + public nrChannels: number; + @Column({ type: 'varchar', transformer: { @@ -24,7 +27,7 @@ export default abstract class LightsFixture extends BaseEntity { public valuesUpdatedAt: Date; - private overrideDmx: (number | null)[] = new Array(16).fill(null); + private overrideDmx: (number | null)[]; protected shouldFreezeDmx: boolean; @@ -37,6 +40,12 @@ export default abstract class LightsFixture extends BaseEntity { protected shouldReset: Date | undefined; + constructor() { + super(); + + this.overrideDmx = this.getEmptyDmxSubPacket().map(() => null); + } + /** * Reset the fixture if possible. * @return true if reset command can be sent. False otherwise @@ -67,7 +76,8 @@ export default abstract class LightsFixture extends BaseEntity { * @param relativeChannels */ public setOverrideDmx(relativeChannels: (number | null)[]) { - this.overrideDmx = relativeChannels.concat(new Array(16).fill(null)).slice(0, 16); + const safetyMargin = this.getEmptyDmxSubPacket().map(() => null); + this.overrideDmx = relativeChannels.concat(safetyMargin).slice(0, this.nrChannels); this.valuesUpdatedAt = new Date(); } @@ -101,6 +111,15 @@ export default abstract class LightsFixture extends BaseEntity { this.valuesUpdatedAt = new Date(); } + /** + * Get an array of zeroes with length equaling the number of channels + * this fixture requires + * @protected + */ + protected getEmptyDmxSubPacket(): number[] { + return new Array(this.nrChannels).fill(0); + } + /** * Get the DMX channels that should be used when the fixture should strobe * @protected diff --git a/src/modules/lights/entities/lights-moving-head-rgb.ts b/src/modules/lights/entities/lights-moving-head-rgb.ts index e7a659c..3676556 100644 --- a/src/modules/lights/entities/lights-moving-head-rgb.ts +++ b/src/modules/lights/entities/lights-moving-head-rgb.ts @@ -57,7 +57,7 @@ export default class LightsMovingHeadRgb extends LightsMovingHead { * @protected */ protected getStrobeDMX(): number[] { - let values: number[] = new Array(16).fill(0); + let values = this.getEmptyDmxSubPacket(); values = this.color.setStrobeInDmx(values, this.shutterOptions); values = this.setPositionInDmx(values); @@ -73,7 +73,7 @@ export default class LightsMovingHeadRgb extends LightsMovingHead { } public getDmxFromCurrentValues(): number[] { - let values: number[] = new Array(16).fill(0); + let values = this.getEmptyDmxSubPacket(); values = this.color.setColorsInDmx(values, this.shutterOptions); values = this.setPositionInDmx(values); diff --git a/src/modules/lights/entities/lights-moving-head-wheel.ts b/src/modules/lights/entities/lights-moving-head-wheel.ts index 0fb9653..1af0121 100644 --- a/src/modules/lights/entities/lights-moving-head-wheel.ts +++ b/src/modules/lights/entities/lights-moving-head-wheel.ts @@ -61,7 +61,7 @@ export default class LightsMovingHeadWheel extends LightsMovingHead { * @protected */ protected getStrobeDMX(): number[] { - let values: number[] = new Array(16).fill(0); + let values = this.getEmptyDmxSubPacket(); values = this.wheel.setStrobeInDmx(values, this.shutterOptions); values = this.setPositionInDmx(values); @@ -77,7 +77,7 @@ export default class LightsMovingHeadWheel extends LightsMovingHead { } public getDmxFromCurrentValues(): number[] { - let values: number[] = new Array(16).fill(0); + let values = this.getEmptyDmxSubPacket(); values = this.wheel.setColorsInDmx(values, this.shutterOptions); values = this.setPositionInDmx(values); diff --git a/src/modules/lights/entities/lights-par.ts b/src/modules/lights/entities/lights-par.ts index c262fb5..b6f042f 100644 --- a/src/modules/lights/entities/lights-par.ts +++ b/src/modules/lights/entities/lights-par.ts @@ -57,7 +57,7 @@ export default class LightsPar extends LightsFixture { * @protected */ protected getStrobeDMX(): number[] { - let values: number[] = new Array(16).fill(0); + let values = this.getEmptyDmxSubPacket(); values = this.color.setStrobeInDmx(values, this.shutterOptions); if (!this.color.shutterChannel) { @@ -71,7 +71,7 @@ export default class LightsPar extends LightsFixture { } public getDmxFromCurrentValues(): number[] { - let values: number[] = new Array(16).fill(0); + let values = this.getEmptyDmxSubPacket(); values = this.color.setColorsInDmx(values, this.shutterOptions); diff --git a/src/modules/root/root-lights-service.ts b/src/modules/root/root-lights-service.ts index 4559c30..46b9c19 100644 --- a/src/modules/root/root-lights-service.ts +++ b/src/modules/root/root-lights-service.ts @@ -33,7 +33,7 @@ export interface ShutterChannelValuesResponse { } export interface LightsFixtureResponse - extends Pick { + extends Pick { canReset: boolean; resetChannel?: number; resetChannelValue?: number; @@ -110,7 +110,7 @@ export interface ShutterOptionValues { strobe: number; } -export interface LightsFixtureParams extends Pick { +export interface LightsFixtureParams extends Pick { shutterOptionValues?: ShutterOptionValues; } @@ -234,6 +234,7 @@ export default class RootLightsService { createdAt: f.createdAt, updatedAt: f.updatedAt, name: f.name, + nrChannels: f.nrChannels, shutterChannelValues: {}, canReset, resetChannel: canReset ? f.resetChannelAndValue![0] + firstChannel - 1 : undefined, @@ -460,6 +461,7 @@ export default class RootLightsService { private toFixture(params: LightsFixtureParams): LightsFixture { return { name: params.name, + nrChannels: params.nrChannels, } as LightsFixture; } diff --git a/src/seed/seed.ts b/src/seed/seed.ts index 46e0792..2f9a2fa 100644 --- a/src/seed/seed.ts +++ b/src/seed/seed.ts @@ -2,7 +2,7 @@ import RootAudioService from '../modules/root/root-audio-service'; import RootScreenService from '../modules/root/root-screen-service'; import RootLightsService, { LightsInGroup } from '../modules/root/root-lights-service'; import dataSource from '../database'; -import { LightsGroup, LightsMovingHeadWheel, LightsPar } from '../modules/lights/entities'; +import { LightsGroup, LightsMovingHeadWheel } from '../modules/lights/entities'; import { RgbColor, WheelColor } from '../modules/lights/color-definitions'; import { SparkleCreateParams } from '../modules/lights/effects/color/sparkle'; import { StaticColorCreateParams } from '../modules/lights/effects/color/static-color'; @@ -62,6 +62,7 @@ export default async function seedDatabase() { // eslint-disable-next-line @typescript-eslint/naming-convention const eurolite_LED_7C_7 = await rootLightsService.createLightsPar({ name: 'Eurolite LED 7C-7', + nrChannels: 12, masterDimChannel: 1, shutterChannel: 2, shutterOptionValues: { @@ -79,6 +80,7 @@ export default async function seedDatabase() { // eslint-disable-next-line @typescript-eslint/naming-convention const eurolite_LED_TMH_S30 = await rootLightsService.createMovingHeadWheel({ name: 'Eurolite LED TMH-S30', + nrChannels: 12, masterDimChannel: 10, shutterChannel: 9, shutterOptionValues: { @@ -103,6 +105,7 @@ export default async function seedDatabase() { // eslint-disable-next-line @typescript-eslint/naming-convention const ayra_ERO_506 = await rootLightsService.createMovingHeadRgb({ name: 'Ayra ERO 506', + nrChannels: 15, masterDimChannel: 6, shutterChannel: 7, shutterOptionValues: { @@ -123,6 +126,7 @@ export default async function seedDatabase() { const showtec_Kanjo_Spot10 = await rootLightsService.createMovingHeadWheel({ name: 'Showtec Kanjo Spot 10', + nrChannels: 10, masterDimChannel: 8, shutterChannel: 9, shutterOptionValues: { @@ -745,15 +749,10 @@ export async function seedDiscoFloor(width: number, height: number) { const controller = await service.createController({ name: 'GEWIS-DISCO-FLOOR' }); const fixture = await service.createLightsPar({ name: 'Disco floor panel', + nrChannels: 3, colorRedChannel: 1, colorGreenChannel: 2, colorBlueChannel: 3, - masterDimChannel: 4, - shutterChannel: 5, - shutterOptionValues: { - open: 0, - strobe: 220, - }, }); const pars: LightsInGroup[] = []; @@ -761,7 +760,7 @@ export async function seedDiscoFloor(width: number, height: number) { for (let positionX = 0; positionX < width; positionX++) { pars.push({ fixtureId: fixture.id, - firstChannel: pars.length * 16 + 1, + firstChannel: pars.length * fixture.nrChannels + 1, positionX, positionY, });