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/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 5c3868a..33ce12c 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.setBrightness(0.5); + p.fixture.setCustomColor({ redChannel: 255, greenChannel: 32 + Math.round(Math.random() * 64), + blueChannel: 0, amberChannel: 128, }); }); 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/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..b031c4c --- /dev/null +++ b/src/modules/lights/entities/colors-rgb.ts @@ -0,0 +1,146 @@ +import { Column } from 'typeorm'; +import { RgbColor, rgbColorDefinitions } from '../color-definitions'; +import LightsFixtureShutterOptions, { ShutterOption } from './lights-fixture-shutter-options'; +import Colors from './colors'; +import { IColorsWheel } from './colors-wheel'; + +export type ColorChannel = keyof ColorsRgb; + +export interface IColorsRgb { + redChannel: number; + greenChannel: number; + blueChannel: number; + coldWhiteChannel?: number | null; + warmWhiteChannel?: number | null; + amberChannel?: number | null; + uvChannel?: number | null; +} + +export default class ColorsRgb extends Colors implements IColorsRgb { + @Column({ type: 'tinyint', unsigned: true, nullable: true }) + public masterDimChannel?: number; + + @Column({ type: 'tinyint', unsigned: true, nullable: true }) + public shutterChannel?: number; + + @Column({ type: 'tinyint', unsigned: true }) + public redChannel: number; + + @Column({ type: 'tinyint', unsigned: true }) + public greenChannel: number; + + @Column({ type: 'tinyint', unsigned: true }) + public blueChannel: number; + + @Column({ type: 'tinyint', nullable: true, unsigned: true }) + public coldWhiteChannel?: number | null; + + @Column({ type: 'tinyint', nullable: true, unsigned: true }) + public warmWhiteChannel?: number | null; + + @Column({ type: 'tinyint', nullable: true, unsigned: true }) + public amberChannel?: number | null; + + @Column({ type: 'tinyint', nullable: true, unsigned: true }) + public uvChannel?: number | null; + + private strobePing = false; + + private currentValues: Required = { + redChannel: 0, + greenChannel: 0, + blueChannel: 0, + coldWhiteChannel: 0, + warmWhiteChannel: 0, + amberChannel: 0, + uvChannel: 0, + }; + + public setColor(color: RgbColor): void { + this.currentValues = rgbColorDefinitions[color].definition; + } + + public setCustomColor(color: IColorsRgb): void { + const givenColors = Object.keys(color) as (keyof IColorsRgb)[]; + givenColors.forEach((key: keyof IColorsRgb) => { + this.currentValues[key] = color[key]!; + }); + } + + public reset(): void { + this.currentValues = { + redChannel: 0, + greenChannel: 0, + blueChannel: 0, + coldWhiteChannel: 0, + warmWhiteChannel: 0, + amberChannel: 0, + uvChannel: 0, + }; + } + + private getColor(color: keyof Required): number { + let value = this.currentValues[color]!; + if (this.masterDimChannel) return value; + value = Math.round(value * this.currentBrightness); + return value; + } + + public setStrobeInDmx(values: number[], shutterOptions: LightsFixtureShutterOptions[]): number[] { + if (this.masterDimChannel) values[this.masterDimChannel - 1] = 255; + if (this.shutterChannel) + values[this.shutterChannel - 1] = + shutterOptions.find((o) => o.shutterOption === ShutterOption.STROBE)?.channelValue ?? 0; + + if (this.shutterChannel || this.strobePing) { + // If we have a shutter channel or we should manually strobe + values[this.redChannel - 1] = 255; + values[this.blueChannel - 1] = 255; + values[this.greenChannel - 1] = 255; + if (this.warmWhiteChannel) values[this.warmWhiteChannel - 1] = 255; + if (this.coldWhiteChannel) values[this.coldWhiteChannel - 1] = 255; + if (this.amberChannel) values[this.amberChannel - 1] = 255; + } else if (!this.shutterChannel) { + // If we do not have a shutter channel and the ping is off, + // turn off all colors + values[this.redChannel - 1] = 0; + values[this.blueChannel - 1] = 0; + values[this.greenChannel - 1] = 0; + if (this.warmWhiteChannel) values[this.warmWhiteChannel - 1] = 0; + if (this.coldWhiteChannel) values[this.coldWhiteChannel - 1] = 0; + if (this.amberChannel) values[this.amberChannel - 1] = 0; + } + + // If we have no shutter channel, manually flip the strobe bit + if (!this.shutterChannel) { + this.strobePing = !this.strobePing; + } + + return values; + } + + public setColorsInDmx(values: number[], shutterOptions: LightsFixtureShutterOptions[]): number[] { + if (this.masterDimChannel) + values[this.masterDimChannel - 1] = Math.round(this.currentBrightness * 255); + if (this.shutterChannel) + values[this.shutterChannel - 1] = + shutterOptions.find((o) => o.shutterOption === ShutterOption.OPEN)?.channelValue ?? 0; + values[this.redChannel - 1] = this.getColor('redChannel'); + values[this.greenChannel - 1] = this.getColor('greenChannel'); + values[this.blueChannel - 1] = this.getColor('blueChannel'); + if (this.coldWhiteChannel != null) { + values[this.coldWhiteChannel - 1] = this.getColor('coldWhiteChannel'); + } + if (this.warmWhiteChannel != null) { + values[this.warmWhiteChannel - 1] = this.getColor('warmWhiteChannel'); + } + if (this.amberChannel != null) { + values[this.amberChannel - 1] = this.getColor('amberChannel'); + } + if (this.uvChannel != null) { + values[this.uvChannel - 1] = this.getColor('uvChannel'); + } + + return values; + } +} diff --git a/src/modules/lights/entities/colors-wheel.ts b/src/modules/lights/entities/colors-wheel.ts new file mode 100644 index 0000000..fe0d8c5 --- /dev/null +++ b/src/modules/lights/entities/colors-wheel.ts @@ -0,0 +1,123 @@ +import { Column, OneToMany } from 'typeorm'; +import LightsMovingHeadWheelShutterOptions from './lights-moving-head-wheel-shutter-options'; +import LightsWheelColorChannelValue from './lights-wheel-color-channel-value'; +import LightsWheelGoboChannelValue from './lights-wheel-gobo-channel-value'; +import LightsWheelRotateChannelValue from './lights-wheel-rotate-channel-value'; +import { RgbColor, rgbColorDefinitions, WheelColor } from '../color-definitions'; +import Colors from './colors'; +import LightsFixtureShutterOptions, { ShutterOption } from './lights-fixture-shutter-options'; + +export interface IColorsWheel { + colorChannel: number; + goboChannel: number; + goboRotateChannel?: number | null; +} + +export default class ColorsWheel extends Colors implements IColorsWheel { + @Column({ type: 'tinyint', unsigned: true }) + public masterDimChannel: number; + + @Column({ type: 'tinyint', unsigned: true, nullable: true }) + public shutterChannel?: number; + + @Column({ type: 'tinyint', unsigned: true }) + public colorChannel: number; + + @Column({ type: 'tinyint', unsigned: true }) + public goboChannel: number; + + @Column({ type: 'tinyint', nullable: true, unsigned: true }) + public goboRotateChannel: number | null; + + @OneToMany(() => LightsWheelColorChannelValue, (c) => c.movingHead, { eager: true }) + public colorChannelValues: LightsWheelColorChannelValue[]; + + @OneToMany(() => LightsWheelGoboChannelValue, (c) => c.movingHead, { eager: true }) + public goboChannelValues: LightsWheelGoboChannelValue[]; + + @OneToMany(() => LightsWheelRotateChannelValue, (c) => c.movingHead, { eager: true }) + public goboRotateChannelValues: LightsWheelRotateChannelValue[]; + + private strobePing = false; + + private currentValues: IColorsWheel = { + colorChannel: 0, + goboChannel: 0, + goboRotateChannel: 0, + }; + + public setColor(color: RgbColor): void { + const wheelColor = rgbColorDefinitions[color].alternative; + const channelValueObj = this.colorChannelValues.find((v) => v.name === wheelColor); + this.currentValues = { + ...this.currentValues, + colorChannel: channelValueObj?.value ?? 0, + }; + } + + public setGobo(gobo?: string) { + const channelValueObj = this.goboChannelValues.find((v) => v.name === gobo); + this.currentValues = { + ...this.currentValues, + goboChannel: channelValueObj?.value ?? 0, + }; + } + + public setGoboRotate(rotate?: string) { + const channelValueObj = this.goboRotateChannelValues.find((v) => v.name === rotate); + this.currentValues = { + ...this.currentValues, + goboRotateChannel: channelValueObj?.value ?? 0, + }; + } + + public reset(): void { + this.currentValues = { + colorChannel: 0, + goboChannel: 0, + goboRotateChannel: 0, + }; + } + + private get channelValues() { + return this.currentValues; + } + + public setStrobeInDmx(values: number[], shutterOptions: LightsFixtureShutterOptions[]): number[] { + if (this.shutterChannel) + values[this.shutterChannel - 1] = + shutterOptions.find((o) => o.shutterOption === ShutterOption.STROBE)?.channelValue ?? 0; + values[this.colorChannel - 1] = + this.colorChannelValues.find((o) => o.name === WheelColor.WHITE)?.value ?? 0; + + if (this.shutterChannel || this.strobePing) { + // If we have a shutter channel or we should manually strobe, + // turn on the light + values[this.masterDimChannel - 1] = 255; + } else if (!this.shutterChannel) { + // If we do not have a shutter channel and the ping is off, + // turn off the light + values[this.masterDimChannel - 1] = 0; + } + + // If we have no shutter channel, manually flip the strobe bit + if (!this.shutterChannel) { + this.strobePing = !this.strobePing; + } + return values; + } + + public setColorsInDmx(values: number[], shutterOptions: LightsFixtureShutterOptions[]): number[] { + values[this.masterDimChannel - 1] = Math.round(this.currentBrightness * 255); + if (this.shutterChannel) + values[this.shutterChannel - 1] = + shutterOptions.find((o) => o.shutterOption === ShutterOption.OPEN)?.channelValue ?? 0; + values[this.colorChannel - 1] = this.channelValues.colorChannel; + values[this.goboChannel - 1] = this.channelValues.goboChannel; + if (this.goboRotateChannel != null) { + values[this.goboRotateChannel - 1] = this.channelValues.goboRotateChannel || 0; + } + + return values; + } +} diff --git a/src/modules/lights/entities/colors.ts b/src/modules/lights/entities/colors.ts index 7ff1768..0c02730 100644 --- a/src/modules/lights/entities/colors.ts +++ b/src/modules/lights/entities/colors.ts @@ -1,26 +1,76 @@ -import { Column } from 'typeorm'; +import { RgbColor } from '../color-definitions'; +import LightsFixtureShutterOptions from './lights-fixture-shutter-options'; -export type ColorChannel = keyof Colors; +export default abstract class Colors { + protected currentBrightness: number = 1; -export default class Colors { - @Column({ type: 'tinyint', unsigned: true }) - public redChannel: number; + protected strobe: boolean = false; + private strobeDisableEvent: NodeJS.Timeout | undefined; - @Column({ type: 'tinyint', unsigned: true }) - public greenChannel: number; + public abstract setColor(color: RgbColor): void; + public abstract reset(): void; - @Column({ type: 'tinyint', unsigned: true }) - public blueChannel: number; + /** + * 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)); + } - @Column({ type: 'tinyint', nullable: true, unsigned: true }) - public coldWhiteChannel?: number | null; + /** + * Start strobing + * @param milliseconds After how many ms the strobe should automatically + * be disabled. + */ + public enableStrobe(milliseconds?: number): void { + this.strobe = true; - @Column({ type: 'tinyint', nullable: true, unsigned: true }) - public warmWhiteChannel?: number | null; + // Stop an existing stop strobe timeout if it exists + if (this.strobeDisableEvent) { + clearTimeout(this.strobeDisableEvent); + this.strobeDisableEvent = undefined; + } - @Column({ type: 'tinyint', nullable: true, unsigned: true }) - public amberChannel?: number | null; + // Create a stop strobe timeout if a time is given + if (milliseconds) { + this.strobeDisableEvent = setTimeout(this.disableStrobe.bind(this), milliseconds); + } + } - @Column({ type: 'tinyint', nullable: true, unsigned: true }) - public uvChannel?: number | null; + /** + * 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 a6c5cac..0bd1436 100644 --- a/src/modules/lights/entities/lights-fixture.ts +++ b/src/modules/lights/entities/lights-fixture.ts @@ -1,17 +1,13 @@ import { AfterLoad, Column } from 'typeorm'; import BaseEntity from '../../root/entities/base-entity'; - -export interface LightsFixtureCurrentValues extends Pick {} +import { RgbColor } from '../color-definitions'; 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; + public nrChannels: number; @Column({ type: 'varchar', @@ -31,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; @@ -44,9 +40,11 @@ export default abstract class LightsFixture extends BaseEntity { protected shouldReset: Date | undefined; - protected strobeEnabled = false; + constructor() { + super(); - private strobeDisableEvent: NodeJS.Timeout | undefined; + this.overrideDmx = this.getEmptyDmxSubPacket().map(() => null); + } /** * Reset the fixture if possible. @@ -58,33 +56,19 @@ 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; - } + public abstract setColor(color: RgbColor): void; + public abstract resetColor(): void; - // Create a stop strobe timeout if a time is given - if (milliseconds) { - this.strobeDisableEvent = setTimeout(this.disableStrobe.bind(this), milliseconds); - } - } + public abstract enableStrobe(milliseconds?: number): void; + protected abstract strobeEnabled(): boolean; + public abstract disableStrobe(): void; /** - * Disable the strobe if it is enabled + * Set the relative brightness of the fixture. + * Should be used by effects. + * @param brightness Value between [0, 1] */ - disableStrobe() { - this.strobeEnabled = false; - this.valuesUpdatedAt = new Date(); - } + public abstract setBrightness(brightness: number): void; /** * Override any set relative DMX channels with the given values. @@ -92,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(); } @@ -118,10 +103,65 @@ export default abstract class LightsFixture extends BaseEntity { }); } + /** + * Apply a blackout to this fixture, i.e. set all channels to zero + * @protected + */ + public blackout(): void { + 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 + */ + 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..3676556 100644 --- a/src/modules/lights/entities/lights-moving-head-rgb.ts +++ b/src/modules/lights/entities/lights-moving-head-rgb.ts @@ -1,95 +1,55 @@ 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'; @Entity() export default class LightsMovingHeadRgb extends LightsMovingHead { @OneToMany(() => LightsMovingHeadRgbShutterOptions, (opt) => opt.fixture, { eager: true }) public shutterOptions: LightsMovingHeadRgbShutterOptions[]; - @Column(() => Colors) - public color: Colors; - - 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, - }; + @Column(() => ColorsRgb) + public color: ColorsRgb; + + public setColor(color: RgbColor) { this.valuesUpdatedAt = new Date(); + this.color.setColor(color); } - setColor(color: RgbColor) { - this.setCurrentValues(rgbColorDefinitions[color].definition); + public setCustomColor(color: IColorsRgb) { + this.valuesUpdatedAt = new Date(); + this.color.setCustomColor(color); } - /** - * @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 resetColor() { + this.valuesUpdatedAt = new Date(); + this.color.reset(); + } + + public blackout() { + super.blackout(); + this.color.reset(); } - setMasterDimmer(masterDimChannel: number) { - if (this.currentValues.masterDimChannel === masterDimChannel) return; - this.setCurrentValues({ - masterDimChannel, - }); + public enableStrobe(milliseconds?: number): void { + this.valuesUpdatedAt = new Date(); + this.color.enableStrobe(milliseconds); } - 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 disableStrobe(): void { + this.valuesUpdatedAt = new Date(); + this.color.disableStrobe(); } - public get channelValues() { - return this.currentValues; + public setBrightness(brightness: number): void { + this.valuesUpdatedAt = new Date(); + this.color.setBrightness(brightness); + } + + protected strobeEnabled(): boolean { + return this.color.strobeEnabled(); } /** @@ -97,94 +57,26 @@ export default class LightsMovingHeadRgb extends LightsMovingHead { * @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; - } + let values = this.getEmptyDmxSubPacket(); - toDmx(): number[] { - if (this.frozenDmx != null && this.frozenDmx.length > 0) { - return this.frozenDmx; - } - - let values: number[] = new Array(16).fill(0); + values = this.color.setStrobeInDmx(values, this.shutterOptions); + values = this.setPositionInDmx(values); - values[this.masterDimChannel - 1] = this.channelValues.masterDimChannel; - 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 (!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); } - // 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, - ); - } + return values; + } - 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(); - } - } + public getDmxFromCurrentValues(): number[] { + let values = this.getEmptyDmxSubPacket(); - if (this.shouldFreezeDmx) { - this.frozenDmx = 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 bb44170..1af0121 100644 --- a/src/modules/lights/entities/lights-moving-head-wheel.ts +++ b/src/modules/lights/entities/lights-moving-head-wheel.ts @@ -1,129 +1,59 @@ 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 } 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, - }; - - setColor(color: RgbColor) { - const wheelColor = rgbColorDefinitions[color].alternative; - const channelValueObj = this.colorWheelChannelValues.find((v) => v.name === wheelColor); - this.setCurrentValues({ - colorWheelChannel: channelValueObj?.value ?? 0, - }); + @Column(() => ColorsWheel) + public wheel: ColorsWheel; + + public setColor(color: RgbColor) { + this.valuesUpdatedAt = new Date(); + this.wheel.setColor(color); } - setGobo(gobo?: string) { - const channelValueObj = this.goboWheelChannelValues.find((v) => v.name === gobo); - this.setCurrentValues({ - goboWheelChannel: channelValueObj?.value ?? 0, - }); + public resetColor(): void { + this.valuesUpdatedAt = new Date(); + this.wheel.reset(); } - setGoboRotate(rotate?: string) { - const channelValueObj = this.goboRotateChannelValues.find((v) => v.name === rotate); - this.setCurrentValues({ - goboRotateChannel: channelValueObj?.value ?? 0, - }); + public setGobo(gobo?: string) { + this.valuesUpdatedAt = new Date(); + this.wheel.setGobo(gobo); } - /** - * @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 setGoboRotate(rotate?: string) { + this.valuesUpdatedAt = new Date(); + this.wheel.setGoboRotate(rotate); + } + + public blackout() { + super.blackout(); + this.wheel.reset(); } - setMasterDimmer(masterDimChannel: number) { - if (this.currentValues.masterDimChannel === masterDimChannel) return; - this.setCurrentValues({ - masterDimChannel, - }); + public enableStrobe(milliseconds?: number): void { + this.valuesUpdatedAt = new Date(); + this.wheel.enableStrobe(milliseconds); } - 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 disableStrobe(): void { + this.valuesUpdatedAt = new Date(); + this.wheel.disableStrobe(); } - setCurrentValues(values: Partial) { - this.currentValues = { - ...this.currentValues, - ...values, - }; + public setBrightness(brightness: number): void { this.valuesUpdatedAt = new Date(); + this.wheel.setBrightness(brightness); } - public get channelValues() { - return this.currentValues; + protected strobeEnabled(): boolean { + return this.wheel.strobeEnabled(); } /** @@ -131,79 +61,26 @@ export default class LightsMovingHeadWheel extends LightsMovingHead { * @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.colorWheelChannel - 1] = - this.colorWheelChannelValues.find((o) => o.name === WheelColor.WHITE)?.value ?? 0; - return values; - } - - toDmx(): number[] { - if (this.frozenDmx != null && this.frozenDmx.length > 0) { - return this.frozenDmx; - } + let values = this.getEmptyDmxSubPacket(); - let values: number[] = new Array(16).fill(0); + values = this.wheel.setStrobeInDmx(values, this.shutterOptions); + values = this.setPositionInDmx(values); - values[this.masterDimChannel - 1] = this.channelValues.masterDimChannel; - 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.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); } - 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, - ); - } + return values; + } - 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(); - } - } + public getDmxFromCurrentValues(): number[] { + let values = this.getEmptyDmxSubPacket(); - if (this.shouldFreezeDmx) { - this.frozenDmx = values; - } + values = this.wheel.setColorsInDmx(values, this.shutterOptions); + 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..d0fc6e9 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 { + super.blackout(); + this.movement.reset(); + } } diff --git a/src/modules/lights/entities/lights-par.ts b/src/modules/lights/entities/lights-par.ts index 3b921ed..b6f042f 100644 --- a/src/modules/lights/entities/lights-par.ts +++ b/src/modules/lights/entities/lights-par.ts @@ -1,65 +1,55 @@ import { Column, Entity, OneToMany } from 'typeorm'; -import LightsFixture, { LightsFixtureCurrentValues } from './lights-fixture'; -import Colors from './colors'; -import { RgbColor, rgbColorDefinitions } from '../color-definitions'; +import LightsFixture from './lights-fixture'; +import ColorsRgb, { IColorsRgb } from './colors-rgb'; +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 { @OneToMany(() => LightsParShutterOptions, (opt) => opt.fixture, { eager: true }) public shutterOptions: LightsParShutterOptions[]; - @Column(() => Colors) - public color: Colors; - - 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, - }; + @Column(() => ColorsRgb) + public color: ColorsRgb; + + public setColor(color: RgbColor) { this.valuesUpdatedAt = new Date(); + this.color.setColor(color); } - setColor(color: RgbColor) { - this.setCurrentValues(rgbColorDefinitions[color].definition); + public setCustomColor(color: IColorsRgb) { + this.valuesUpdatedAt = new Date(); + this.color.setCustomColor(color); } - setMasterDimmer(masterDimChannel: number) { - if (this.currentValues.masterDimChannel === masterDimChannel) return; - this.setCurrentValues({ - masterDimChannel, - }); + public resetColor() { + this.valuesUpdatedAt = new Date(); + this.color.reset(); } - 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 blackout() { + super.blackout(); + this.color.reset(); } - public get channelValues() { - return this.currentValues; + public enableStrobe(milliseconds?: number): void { + this.valuesUpdatedAt = new Date(); + this.color.enableStrobe(milliseconds); + } + + public disableStrobe(): 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(); } /** @@ -67,63 +57,23 @@ export default class LightsPar extends LightsFixture { * @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; - } - - toDmx(): number[] { - if (this.strobeEnabled) return this.getStrobeDMX(); - - if (this.frozenDmx != null && this.frozenDmx.length > 0) { - return this.frozenDmx; + let values = this.getEmptyDmxSubPacket(); + 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); } - let values: number[] = new Array(16).fill(0); - - values[this.masterDimChannel - 1] = this.channelValues.masterDimChannel; - 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; - } + return values; + } - 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(); - } - } + public getDmxFromCurrentValues(): number[] { + let values = this.getEmptyDmxSubPacket(); - if (this.shouldFreezeDmx) { - this.frozenDmx = values; - } + values = this.color.setColorsInDmx(values, this.shutterOptions); 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..46b9c19 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 { IColorsWheel } from '../lights/entities/colors-wheel'; export interface LightsControllerResponse extends Pick {} @@ -32,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; @@ -47,13 +45,13 @@ export interface LightsFixtureResponse // prettier-ignore export interface ColorResponse extends Pick< - Colors, - 'redChannel' | 'blueChannel' | 'greenChannel' | 'coldWhiteChannel' | 'warmWhiteChannel' | 'amberChannel' | 'uvChannel' + ColorsRgb, + 'masterDimChannel' | 'shutterChannel' | '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 +60,15 @@ export interface MovingHeadWheelColorChannelValueResponse { channelValue: number; } -export interface MovingHeadWheelResponse - extends MovingHeadResponse, - Pick { +export interface MovingHeadWheelResponse extends MovingHeadResponse { + masterDimChannel: number; + shutterChannel?: number; + wheelColorChannel: number; + wheelColorChannelValues: MovingHeadWheelColorChannelValueResponse[]; + wheelGoboChannel: number; gobos: string[]; + wheelGoboRotateChannel: number | null; goboRotates: string[]; - colorChannelValues: MovingHeadWheelColorChannelValueResponse[]; } export interface FixtureInGroupResponse< @@ -93,6 +94,8 @@ export interface LightsGroupResponse extends BaseLightsGroupResponse { } export interface ColorParams { + masterDimChannel?: number; + shutterChannel?: number; colorRedChannel: number; colorGreenChannel: number; colorBlueChannel: number; @@ -107,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 {} @@ -125,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; @@ -198,8 +202,10 @@ 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 { + 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, @@ -210,7 +216,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, @@ -228,8 +234,7 @@ export default class RootLightsService { createdAt: f.createdAt, updatedAt: f.updatedAt, name: f.name, - masterDimChannel: f.masterDimChannel + firstChannel - 1, - shutterChannel: f.shutterChannel + firstChannel - 1, + nrChannels: f.nrChannels, shutterChannelValues: {}, canReset, resetChannel: canReset ? f.resetChannelAndValue![0] + firstChannel - 1 : undefined, @@ -284,15 +289,19 @@ export default class RootLightsService { ): MovingHeadWheelResponse { return { ...this.toMovingHeadResponse(m, firstChannel), - colorWheelChannel: m.colorWheelChannel + firstChannel - 1, - colorChannelValues: m.colorWheelChannelValues.map((x) => ({ + masterDimChannel: m.wheel.masterDimChannel, + shutterChannel: m.wheel.shutterChannel, + 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), }; } @@ -452,12 +461,11 @@ export default class RootLightsService { private toFixture(params: LightsFixtureParams): LightsFixture { return { name: params.name, - masterDimChannel: params.masterDimChannel, - shutterChannel: params.shutterChannel, + nrChannels: params.nrChannels, } as LightsFixture; } - private toColor(params: ColorParams): Colors { + private toColorRgb(params: ColorParams): IColorsRgb { return { redChannel: params.colorRedChannel, blueChannel: params.colorBlueChannel, @@ -479,6 +487,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(); } @@ -498,8 +514,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, @@ -518,7 +535,11 @@ 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), + masterDimChannel: params.masterDimChannel, + shutterChannel: params.shutterOptionValues ? params.shutterChannel : undefined, + }, }); par.shutterOptions = (await this.createFixtureShutterOptions( dataSource.getRepository(LightsParShutterOptions), @@ -535,7 +556,11 @@ export default class RootLightsService { const movingHead = await repository.save({ ...this.toFixture(params), movement: this.toMovement(params), - color: this.toColor(params), + color: { + ...this.toColorRgb(params), + masterDimChannel: params.masterDimChannel, + shutterChannel: params.shutterOptionValues ? params.shutterChannel : undefined, + }, }); movingHead.shutterOptions = (await this.createFixtureShutterOptions( dataSource.getRepository(LightsMovingHeadRgbShutterOptions), @@ -552,9 +577,11 @@ 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), + masterDimChannel: params.masterDimChannel, + shutterChannel: params.shutterOptionValues ? params.shutterChannel : undefined, + }, }); movingHead.shutterOptions = (await this.createFixtureShutterOptions( dataSource.getRepository(LightsMovingHeadWheelShutterOptions), 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, });