From 3189a6153920dc65efea419e48cc91342de2be94 Mon Sep 17 00:00:00 2001 From: Roy Kakkenberg <38660000+Yoronex@users.noreply.github.com> Date: Wed, 5 Feb 2025 23:03:57 +0100 Subject: [PATCH] fix(lights): several small fixes and improvements (#47) * fix(moving-heads): not working synchronous in a line * fix(moving-heads): light always on * feat(lights-effects): use hex colors if pars have only RGB LEDs * fix(lights-effects): clean up when effect is destroyed --- src/modules/lights/color-definitions.ts | 17 ++- .../lights/effects/color/background-pulse.ts | 103 ++++++++++++++---- .../lights/effects/color/beat-fade-out.ts | 6 +- src/modules/lights/effects/color/fire.ts | 6 +- .../lights/effects/color/random-color.ts | 6 +- .../lights/effects/color/single-flood.ts | 6 +- src/modules/lights/effects/color/sparkle.ts | 6 +- .../lights/effects/color/static-color.ts | 6 +- src/modules/lights/effects/color/wave.ts | 6 +- src/modules/lights/effects/lights-effect.ts | 26 ++--- .../lights/effects/movement/base-rotate.ts | 12 +- .../lights/effects/movement/classic-rotate.ts | 6 +- .../lights/effects/movement/fixed-position.ts | 57 ++++++++-- .../effects/movement/random-position.ts | 4 +- .../lights/effects/movement/search-light.ts | 6 +- .../lights/effects/movement/table-rotate.ts | 6 +- .../lights/effects/movement/zig-zag.ts | 10 +- src/modules/lights/entities/colors-rgb.ts | 30 ++++- src/modules/lights/entities/colors-wheel.ts | 22 ++-- src/modules/lights/entities/lights-fixture.ts | 2 +- .../lights/entities/lights-moving-head.ts | 1 + src/modules/lights/entities/movement.ts | 25 ++++- .../root/root-lights-operations-controller.ts | 6 +- src/modules/root/root-lights-service.ts | 6 +- src/seed/seed.ts | 6 +- 25 files changed, 285 insertions(+), 102 deletions(-) diff --git a/src/modules/lights/color-definitions.ts b/src/modules/lights/color-definitions.ts index 97cfedc..3ed95a3 100644 --- a/src/modules/lights/color-definitions.ts +++ b/src/modules/lights/color-definitions.ts @@ -35,11 +35,13 @@ export const wheelColors = Object.values(WheelColor); export const rgbColors = Object.values(RgbColor); +export type HexColor = string; + export type RgbColorSpecification = { definition: Required; alternative: WheelColor; complementary: RgbColor[]; - hex: string; + hex: HexColor; }; export type RgbColorSet = { @@ -50,6 +52,17 @@ export type RgbColorAlternatives = { [color in RgbColor]: WheelColor; }; +export function hexToRgb(hex: HexColor): { r: number; g: number; b: number } { + const parts = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return parts + ? { + r: parseInt(parts[1], 16), + g: parseInt(parts[2], 16), + b: parseInt(parts[3], 16), + } + : { r: 0, g: 0, b: 0 }; +} + export const rgbColorDefinitions: RgbColorSet = { [RgbColor.WHITE]: { definition: { @@ -63,7 +76,7 @@ export const rgbColorDefinitions: RgbColorSet = { }, alternative: WheelColor.WHITE, complementary: [], - hex: '#fff', + hex: '#ffffff', }, [RgbColor.RED]: { definition: { diff --git a/src/modules/lights/effects/color/background-pulse.ts b/src/modules/lights/effects/color/background-pulse.ts index 19f290b..5ed6c97 100644 --- a/src/modules/lights/effects/color/background-pulse.ts +++ b/src/modules/lights/effects/color/background-pulse.ts @@ -4,7 +4,12 @@ import LightsEffect, { LightsEffectBuilder, } from '../lights-effect'; import { LightsGroup } from '../../entities'; -import { RgbColor, rgbColorDefinitions } from '../../color-definitions'; +import { + hexToRgb, + RgbColor, + rgbColorDefinitions, + RgbColorSpecification, +} from '../../color-definitions'; import { EffectProgressionTickStrategy } from '../progression-strategies'; import { IColorsRgb } from '../../entities/colors-rgb'; import { ColorEffects } from './color-effects'; @@ -71,7 +76,12 @@ export default class BackgroundPulse extends LightsEffect this.props.colors = colors; } - destroy(): void {} + destroy(): void { + this.lightsGroup.fixtures.forEach((f) => { + f.fixture.setBrightness(1); + f.fixture.resetColor(); + }); + } /** * Whether we should start a new tick @@ -129,31 +139,82 @@ export default class BackgroundPulse extends LightsEffect return (1 - fixtureAbsoluteProgression) * 2; } + private rgbToIColorsRgb({ r, g, b }: { r: number; b: number; g: number }): Required { + return { + redChannel: r, + greenChannel: g, + blueChannel: b, + coldWhiteChannel: 0, + warmWhiteChannel: 0, + amberChannel: 0, + uvChannel: 0, + }; + } + + /** + * Mix two colors together + * @param colorA + * @param colorB + * @param p factor B present in the new color + * @param rgbOnly Whether the output should be an RGB-only color + * @private + */ + private mixColors( + colorA: RgbColorSpecification, + colorB: RgbColorSpecification, + p: number, + rgbOnly: boolean, + ): Required { + if (!rgbOnly) { + return { + redChannel: colorA.definition.redChannel * (1 - p) + colorB.definition.redChannel * p, + greenChannel: colorA.definition.greenChannel * (1 - p) + colorB.definition.greenChannel * p, + blueChannel: colorA.definition.blueChannel * (1 - p) + colorB.definition.blueChannel * p, + warmWhiteChannel: + colorA.definition.warmWhiteChannel! * (1 - p) + colorB.definition.warmWhiteChannel! * p, + coldWhiteChannel: + colorA.definition.coldWhiteChannel! * (1 - p) + colorB.definition.coldWhiteChannel! * p, + amberChannel: + colorA.definition.amberChannel! * (1 - p) + colorB.definition.amberChannel! * p, + uvChannel: colorA.definition.uvChannel! * (1 - p) + colorB.definition.uvChannel! * p, + }; + } + + const rgbA = hexToRgb(colorA.hex); + const rgbB = hexToRgb(colorB.hex); + + return this.rgbToIColorsRgb({ + r: rgbA.r * (1 - p) + rgbB.r * p, + g: rgbA.g * (1 - p) + rgbB.g * p, + b: rgbA.b * (1 - p) + rgbB.b * p, + }); + } + /** * Get the mixed color for the given progression and the given color index * @param p progression, in range [0, 1] * @param colorIndex + * @param rgbOnly Whether only an RGB color should be returned, instead of an extensive palette * @private */ - private getColor(p: number, colorIndex: number): Required | undefined { - const baseColor = rgbColorDefinitions[this.props.colors[0]]?.definition; - if (p <= 0 || this.props.colors.length < 2) { - return baseColor; + private getColor( + p: number, + colorIndex: number, + rgbOnly: boolean, + ): Required | undefined { + const baseColor = rgbColorDefinitions[this.props.colors[0]]; + if (!baseColor) return undefined; + + let compositeColor: RgbColorSpecification | undefined; + if (this.props.colors.length >= 2) { + compositeColor = rgbColorDefinitions[this.props.colors[colorIndex]]; } - const compositeColor = rgbColorDefinitions[this.props.colors[colorIndex]]?.definition; + if (p <= 0 || !compositeColor) { + return rgbOnly ? this.rgbToIColorsRgb(hexToRgb(baseColor.hex)) : baseColor.definition; + } - return { - redChannel: baseColor.redChannel * (1 - p) + compositeColor.redChannel * p, - greenChannel: baseColor.greenChannel * (1 - p) + compositeColor.greenChannel * p, - blueChannel: baseColor.blueChannel * (1 - p) + compositeColor.blueChannel * p, - warmWhiteChannel: - baseColor.warmWhiteChannel! * (1 - p) + compositeColor.warmWhiteChannel! * p, - coldWhiteChannel: - baseColor.coldWhiteChannel! * (1 - p) + compositeColor.coldWhiteChannel! * p, - amberChannel: baseColor.amberChannel! * (1 - p) + compositeColor.amberChannel! * p, - uvChannel: baseColor.uvChannel! * (1 - p) + compositeColor.uvChannel! * p, - }; + return this.mixColors(baseColor, compositeColor, p, rgbOnly); } tick(): LightsGroup { @@ -171,7 +232,11 @@ export default class BackgroundPulse extends LightsEffect p = this.getRelativeProgression(progressionStrategy.getProgression(tick)); } - const color = this.getColor(p, this.colorIndices[i] + 1); + const color = this.getColor( + p, + this.colorIndices[i] + 1, + !f.fixture.color.hasExtendedColorPalette(), + ); if (color) { f.fixture.setCustomColor(color); } else { diff --git a/src/modules/lights/effects/color/beat-fade-out.ts b/src/modules/lights/effects/color/beat-fade-out.ts index 1415310..61ce506 100644 --- a/src/modules/lights/effects/color/beat-fade-out.ts +++ b/src/modules/lights/effects/color/beat-fade-out.ts @@ -97,7 +97,11 @@ export default class BeatFadeOut extends LightsEffect { this.props.colors = colors; } - destroy(): void {} + destroy(): void { + this.lightsGroup.fixtures.forEach((f) => { + f.fixture.resetColor(); + }); + } beat(event: BeatEvent): void { super.beat(event); diff --git a/src/modules/lights/effects/color/fire.ts b/src/modules/lights/effects/color/fire.ts index 33ce12c..c50c4ef 100644 --- a/src/modules/lights/effects/color/fire.ts +++ b/src/modules/lights/effects/color/fire.ts @@ -19,7 +19,11 @@ export default class Fire extends LightsEffect { beat(): void {} - destroy(): void {} + destroy(): void { + this.lightsGroup.fixtures.forEach((f) => { + f.fixture.resetColor(); + }); + } tick(): LightsGroup { [...this.lightsGroup.pars, ...this.lightsGroup.movingHeadRgbs].forEach((p) => { diff --git a/src/modules/lights/effects/color/random-color.ts b/src/modules/lights/effects/color/random-color.ts index a925ec7..62bb632 100644 --- a/src/modules/lights/effects/color/random-color.ts +++ b/src/modules/lights/effects/color/random-color.ts @@ -70,7 +70,11 @@ export default class RandomColor extends LightsEffect { this.props.colors = colors; } - destroy() {} + destroy() { + this.lightsGroup.fixtures.forEach((f) => { + f.fixture.resetColor(); + }); + } beat(event: BeatEvent) { super.beat(event); diff --git a/src/modules/lights/effects/color/single-flood.ts b/src/modules/lights/effects/color/single-flood.ts index ab19e30..16e4b30 100644 --- a/src/modules/lights/effects/color/single-flood.ts +++ b/src/modules/lights/effects/color/single-flood.ts @@ -34,7 +34,11 @@ export default class SingleFlood extends LightsEffect { this.props = props; } - destroy(): void {} + destroy(): void { + this.lightsGroup.fixtures.forEach((f) => { + f.fixture.resetColor(); + }); + } beat(): void {} diff --git a/src/modules/lights/effects/color/sparkle.ts b/src/modules/lights/effects/color/sparkle.ts index c9fd7fd..1bf4afc 100644 --- a/src/modules/lights/effects/color/sparkle.ts +++ b/src/modules/lights/effects/color/sparkle.ts @@ -91,7 +91,11 @@ export default class Sparkle extends LightsEffect { this.previousTick = new Date(); } - destroy(): void {} + destroy(): void { + this.lightsGroup.fixtures.forEach((f) => { + f.fixture.resetColor(); + }); + } beat(): void { if (!this.props.cycleTime) this.enableLights(); diff --git a/src/modules/lights/effects/color/static-color.ts b/src/modules/lights/effects/color/static-color.ts index c287be6..b101d5f 100644 --- a/src/modules/lights/effects/color/static-color.ts +++ b/src/modules/lights/effects/color/static-color.ts @@ -87,7 +87,11 @@ export default class StaticColor extends LightsEffect { this.ping = (this.ping + 1) % 2; } - destroy(): void {} + destroy(): void { + this.lightsGroup.fixtures.forEach((f) => { + f.fixture.resetColor(); + }); + } private getDimProgression(durationMs: number) { return Math.min(1, (new Date().getTime() - this.cycleStartTick.getTime()) / durationMs); diff --git a/src/modules/lights/effects/color/wave.ts b/src/modules/lights/effects/color/wave.ts index c7752ec..1d1ec92 100644 --- a/src/modules/lights/effects/color/wave.ts +++ b/src/modules/lights/effects/color/wave.ts @@ -61,7 +61,11 @@ export default class Wave extends LightsEffect { this.props.colors = colors; } - destroy(): void {} + destroy(): void { + this.lightsGroup.fixtures.forEach((f) => { + f.fixture.resetColor(); + }); + } beat(): void {} diff --git a/src/modules/lights/effects/lights-effect.ts b/src/modules/lights/effects/lights-effect.ts index 9ae7d85..cc07a0c 100644 --- a/src/modules/lights/effects/lights-effect.ts +++ b/src/modules/lights/effects/lights-effect.ts @@ -35,29 +35,24 @@ export type BaseLightsEffectCreateParams = {}; export default abstract class LightsEffect

{ protected props: P; - private readonly progressionMapperStrategy: EffectProgressionMapStrategy; - protected constructor( public readonly lightsGroup: LightsGroup, private readonly progressionStrategy?: EffectProgressionStrategy, - progressionMapperStrategy?: EffectProgressionMapStrategy, + private readonly progressionMapperStrategy?: EffectProgressionMapStrategy, private patternDirection = LightsEffectDirection.FORWARDS, - ) { - if (!progressionMapperStrategy) { - this.progressionMapperStrategy = new EffectProgressionMapFactory(this.lightsGroup).getMapper( - LightsEffectPattern.HORIZONTAL, - ); - } else { - this.progressionMapperStrategy = progressionMapperStrategy; - } - } + ) {} public setNewProps(props: P) { this.props = props; } protected getEffectNrFixtures(): number { - return this.progressionMapperStrategy.getNrFixtures(); + if (this.progressionMapperStrategy) return this.progressionMapperStrategy.getNrFixtures(); + return ( + this.lightsGroup.pars.length + + this.lightsGroup.movingHeadWheels.length + + this.lightsGroup.movingHeadRgbs.length + ); } protected getProgression(currentTick: Date, fixture: LightsGroupFixture): number { @@ -68,7 +63,10 @@ export default abstract class LightsEffect

{ progression = 1 - progression; } - return this.progressionMapperStrategy.getProgression(progression, fixture); + if (this.progressionMapperStrategy) { + return this.progressionMapperStrategy.getProgression(progression, fixture); + } + return this.progressionStrategy.getProgression(currentTick); } /** diff --git a/src/modules/lights/effects/movement/base-rotate.ts b/src/modules/lights/effects/movement/base-rotate.ts index d9c1fcc..2c354d9 100644 --- a/src/modules/lights/effects/movement/base-rotate.ts +++ b/src/modules/lights/effects/movement/base-rotate.ts @@ -30,11 +30,7 @@ export default abstract class BaseRotate extends Ligh progressionProps: BaseLightsEffectProgressionProps, cycleTime?: number, ) { - super( - lightsGroup, - new EffectProgressionTickStrategy(cycleTime ?? defaults.cycleTime), - new EffectProgressionMapFactory(lightsGroup).getMapper(progressionProps.pattern), - ); + super(lightsGroup, new EffectProgressionTickStrategy(cycleTime ?? defaults.cycleTime)); } /** @@ -50,7 +46,11 @@ export default abstract class BaseRotate extends Ligh offset: number, ): void; - destroy(): void {} + destroy(): void { + [...this.lightsGroup.movingHeadRgbs, ...this.lightsGroup.movingHeadWheels].forEach((f) => { + f.fixture.movement.reset(); + }); + } beat(event: BeatEvent): void { super.beat(event); diff --git a/src/modules/lights/effects/movement/classic-rotate.ts b/src/modules/lights/effects/movement/classic-rotate.ts index db48fa7..5deaf59 100644 --- a/src/modules/lights/effects/movement/classic-rotate.ts +++ b/src/modules/lights/effects/movement/classic-rotate.ts @@ -56,8 +56,8 @@ export default class ClassicRotate extends BaseRotate { progression: number, offset: number = 0, ) { - const pan = this.triangleFunction(progression + offset) * 128 + 128; - const tilt = this.triangleFunction(progression * 4 + offset) * 128 + 128; - movingHead.setPosition(pan, tilt); + const pan = this.triangleFunction(progression + offset) / 2 + 0.5; + const tilt = this.triangleFunction(progression * 4 + offset) / 2 + 0.5; + movingHead.setPositionRel(pan, tilt); } } diff --git a/src/modules/lights/effects/movement/fixed-position.ts b/src/modules/lights/effects/movement/fixed-position.ts index a49e712..a8cd73a 100644 --- a/src/modules/lights/effects/movement/fixed-position.ts +++ b/src/modules/lights/effects/movement/fixed-position.ts @@ -2,21 +2,43 @@ import LightsEffect, { BaseLightsEffectCreateParams, LightsEffectBuilder } from import { LightsGroup } from '../../entities'; import { MovementEffects } from './movement-effetcs'; -export interface FixedPositionProps { +export type FixedPositionPropsAbs = { + variant: 'Absolute'; + /** - * Pan value of the moving heads. Any decimals are applied to the finePan if it exists. + * Absolute pan value of the moving heads. Any decimals are applied to the finePan if it exists. * @minimum 0 * @maximum 256 */ pan: number; /** - * Tilt value of the moving heads. Any decimals are applied to the fineTilt if it exists. + * Relative tilt value of the moving heads. Any decimals are applied to the fineTilt if it exists. * @minimum 0 * @maximum 256 */ tilt: number; -} +}; + +export type FixedPositionPropsRel = { + variant: 'Relative'; + + /** + * Relative pan value of the moving heads. Any decimals are applied to the finePan if it exists. + * @minimum 0 + * @maximum 1 + */ + pan: number; + + /** + * Relative tilt value of the moving heads. Any decimals are applied to the fineTilt if it exists. + * @minimum 0 + * @maximum 1 + */ + tilt: number; +}; + +export type FixedPositionProps = FixedPositionPropsAbs | FixedPositionPropsRel; export type FixedPositionCreateParams = BaseLightsEffectCreateParams & { type: MovementEffects.FixedPosition; @@ -28,12 +50,21 @@ export default class FixedPosition extends LightsEffect { super(lightsGroup); this.props = props; - lightsGroup.movingHeadRgbs.forEach(({ fixture }) => { - fixture.setPosition(props.pan, props.tilt); - }); - lightsGroup.movingHeadWheels.forEach(({ fixture }) => { - fixture.setPosition(props.pan, props.tilt); - }); + if (props.variant === 'Absolute') { + lightsGroup.movingHeadRgbs.forEach(({ fixture }) => { + fixture.setPosition(props.pan, props.tilt); + }); + lightsGroup.movingHeadWheels.forEach(({ fixture }) => { + fixture.setPosition(props.pan, props.tilt); + }); + } else if (props.variant === 'Relative') { + lightsGroup.movingHeadRgbs.forEach(({ fixture }) => { + fixture.setPositionRel(props.pan, props.tilt); + }); + lightsGroup.movingHeadWheels.forEach(({ fixture }) => { + fixture.setPositionRel(props.pan, props.tilt); + }); + } } public static build( @@ -44,7 +75,11 @@ export default class FixedPosition extends LightsEffect { beat(): void {} - destroy(): void {} + destroy(): void { + [...this.lightsGroup.movingHeadRgbs, ...this.lightsGroup.movingHeadWheels].forEach((f) => { + f.fixture.movement.reset(); + }); + } tick(): LightsGroup { return this.lightsGroup; diff --git a/src/modules/lights/effects/movement/random-position.ts b/src/modules/lights/effects/movement/random-position.ts index d172ee9..51d42cc 100644 --- a/src/modules/lights/effects/movement/random-position.ts +++ b/src/modules/lights/effects/movement/random-position.ts @@ -42,12 +42,12 @@ export default class RandomPosition extends LightsEffect { this.lightsGroup.movingHeadRgbs.forEach((m, i) => { if (this.counters[i] > 0) return; - m.fixture.setPositionRel(Math.random() / 3, Math.random()); + m.fixture.setPositionRel(Math.random() / 2, Math.random()); }); this.lightsGroup.movingHeadWheels.forEach((m, i) => { if (this.counters[nrMHRgbs + i] > 0) return; - m.fixture.setPositionRel(Math.random() / 3, Math.random()); + m.fixture.setPositionRel(Math.random() / 2, Math.random()); }); this.counters = this.counters.map( diff --git a/src/modules/lights/effects/movement/search-light.ts b/src/modules/lights/effects/movement/search-light.ts index 77975fc..a63e70c 100644 --- a/src/modules/lights/effects/movement/search-light.ts +++ b/src/modules/lights/effects/movement/search-light.ts @@ -56,8 +56,8 @@ export default class SearchLight extends BaseRotate { offset: number = 0, ) { const radiusFactor = this.props.radiusFactor ?? DEFAULT_RADIUS_FACTOR; - const pan = Math.cos(progression * 2 * Math.PI + offset) * 42 + 42; - const tilt = Math.sin(progression * 2 * Math.PI + offset) * 64 * radiusFactor + 128; - movingHead.setPosition(pan, tilt); + const pan = Math.cos(progression * 2 * Math.PI + offset) * 0.15 + 0.15; + const tilt = Math.sin(progression * 2 * Math.PI + offset) * 0.25 * radiusFactor + 0.5; + movingHead.setPositionRel(pan, tilt); } } diff --git a/src/modules/lights/effects/movement/table-rotate.ts b/src/modules/lights/effects/movement/table-rotate.ts index dac55b7..91c944d 100644 --- a/src/modules/lights/effects/movement/table-rotate.ts +++ b/src/modules/lights/effects/movement/table-rotate.ts @@ -45,9 +45,9 @@ export default class TableRotate extends BaseRotate { progression: number, offset: number = 0, ) { - const panChannel = Math.cos(progression * 2 * Math.PI + offset) * (255 / 6) + 255 / 6; - const tiltChannel = Math.sin(progression * 6 * Math.PI + offset) * 48 + 96; + const panChannel = Math.cos(progression * 2 * Math.PI + offset) / 4 + 0.25; + const tiltChannel = Math.sin(progression * 6 * Math.PI + offset) * 0.2 + 0.4; - movingHead.setPosition(panChannel, tiltChannel); + movingHead.setPositionRel(panChannel, tiltChannel); } } diff --git a/src/modules/lights/effects/movement/zig-zag.ts b/src/modules/lights/effects/movement/zig-zag.ts index b2fffe6..43abff3 100644 --- a/src/modules/lights/effects/movement/zig-zag.ts +++ b/src/modules/lights/effects/movement/zig-zag.ts @@ -56,14 +56,12 @@ export default class ZigZag extends BaseRotate { offset: number, ) { const hozRadiusFactor = this.props.horizontalRadius || DEFAULT_HOZ_RADIUS_FACTOR; - const horizontalRadius = hozRadiusFactor / 6; + const horizontalRadius = hozRadiusFactor / 4; const vertRadiusFactor = this.props.verticalRadius || DEFAULT_VERT_RADIUS_FACTOR; - const verticalRadius = 0.5 * vertRadiusFactor; + const verticalRadius = vertRadiusFactor / 2; - const panChannel = - Math.sin(progression * 2 * Math.PI + offset) * horizontalRadius + (1 - horizontalRadius / 6); - const tiltChannel = - Math.sin(progression * 24 * Math.PI + offset) * verticalRadius + (1 - vertRadiusFactor / 2); + const panChannel = Math.sin(progression * 2 * Math.PI + offset) * horizontalRadius + 0.5; + const tiltChannel = Math.sin(progression * 24 * Math.PI + offset) * verticalRadius + 0.5; movingHead.setPositionRel(panChannel, tiltChannel); } } diff --git a/src/modules/lights/entities/colors-rgb.ts b/src/modules/lights/entities/colors-rgb.ts index b664d09..bcb4342 100644 --- a/src/modules/lights/entities/colors-rgb.ts +++ b/src/modules/lights/entities/colors-rgb.ts @@ -1,8 +1,7 @@ import { Column } from 'typeorm'; -import { RgbColor, rgbColorDefinitions } from '../color-definitions'; +import { hexToRgb, RgbColor, rgbColorDefinitions } from '../color-definitions'; import LightsFixtureShutterOptions, { ShutterOption } from './lights-fixture-shutter-options'; import Colors from './colors'; -import { IColorsWheel } from './colors-wheel'; import logger from '../../../logger'; export type ColorChannel = keyof ColorsRgb; @@ -57,6 +56,16 @@ export default class ColorsRgb extends Colors implements IColorsRgb { uvChannel: 0, }; + /** + * Returns whether multiple LED colors can be used to mix colors, or whether only + * RGB can be used. + */ + public hasExtendedColorPalette(): boolean { + return ( + this.coldWhiteChannel != null || this.warmWhiteChannel != null || this.amberChannel != null + ); + } + public setColor(color?: RgbColor): void { if (!color) { this.reset(); @@ -68,7 +77,21 @@ export default class ColorsRgb extends Colors implements IColorsRgb { this.reset(); return; } - this.currentValues = spec.definition; + + if (this.hasExtendedColorPalette()) { + this.currentValues = spec.definition; + } else { + const { r, g, b } = hexToRgb(spec.hex); + this.currentValues = { + redChannel: r, + greenChannel: g, + blueChannel: b, + coldWhiteChannel: 0, + warmWhiteChannel: 0, + amberChannel: 0, + uvChannel: 0, + }; + } } public setCustomColor(color: IColorsRgb): void { @@ -79,6 +102,7 @@ export default class ColorsRgb extends Colors implements IColorsRgb { } public reset(): void { + this.setBrightness(1); this.currentValues = { redChannel: 0, greenChannel: 0, diff --git a/src/modules/lights/entities/colors-wheel.ts b/src/modules/lights/entities/colors-wheel.ts index a15f8c1..7ea66c5 100644 --- a/src/modules/lights/entities/colors-wheel.ts +++ b/src/modules/lights/entities/colors-wheel.ts @@ -9,7 +9,6 @@ import LightsFixtureShutterOptions, { ShutterOption } from './lights-fixture-shu import logger from '../../../logger'; export interface IColorsWheel { - colorChannel: number; goboChannel: number; goboRotateChannel?: number | null; } @@ -41,8 +40,9 @@ export default class ColorsWheel extends Colors implements IColorsWheel { private strobePing = false; + private currentColor: LightsWheelColorChannelValue | undefined; + private currentValues: IColorsWheel = { - colorChannel: 0, goboChannel: 0, goboRotateChannel: 0, }; @@ -60,11 +60,7 @@ export default class ColorsWheel extends Colors implements IColorsWheel { } const wheelColor = spec.alternative; - const channelValueObj = this.colorChannelValues.find((v) => v.name === wheelColor); - this.currentValues = { - ...this.currentValues, - colorChannel: channelValueObj?.value ?? 0, - }; + this.currentColor = this.colorChannelValues.find((v) => v.name === wheelColor); } public setGobo(gobo?: string) { @@ -84,8 +80,9 @@ export default class ColorsWheel extends Colors implements IColorsWheel { } public reset(): void { + this.setBrightness(1); + this.currentColor = undefined; this.currentValues = { - colorChannel: 0, goboChannel: 0, goboRotateChannel: 0, }; @@ -128,13 +125,14 @@ export default class ColorsWheel extends Colors implements IColorsWheel { values: number[], shutterOptions: LightsFixtureShutterOptions[], ): number[] { - values[this.masterDimChannel - 1] = Math.round( - masterRelativeBrightness * this.currentBrightness * 255, - ); + values[this.masterDimChannel - 1] = + this.currentColor !== undefined + ? Math.round(masterRelativeBrightness * this.currentBrightness * 255) + : 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.colorChannel - 1] = this.currentColor?.value ?? 0; values[this.goboChannel - 1] = this.channelValues.goboChannel; if (this.goboRotateChannel != null) { values[this.goboRotateChannel - 1] = this.channelValues.goboRotateChannel || 0; diff --git a/src/modules/lights/entities/lights-fixture.ts b/src/modules/lights/entities/lights-fixture.ts index b0b16dd..4145335 100644 --- a/src/modules/lights/entities/lights-fixture.ts +++ b/src/modules/lights/entities/lights-fixture.ts @@ -51,7 +51,7 @@ export default abstract class LightsFixture extends BaseEntity { * Reset the fixture if possible. * @return true if reset command can be sent. False otherwise */ - reset(): boolean { + hardwareReset(): boolean { if (this.resetChannelAndValue == null || this.resetChannelAndValue.length < 2) return false; this.shouldReset = new Date(); return true; diff --git a/src/modules/lights/entities/lights-moving-head.ts b/src/modules/lights/entities/lights-moving-head.ts index d0fc6e9..b0f500e 100644 --- a/src/modules/lights/entities/lights-moving-head.ts +++ b/src/modules/lights/entities/lights-moving-head.ts @@ -18,6 +18,7 @@ export default abstract class LightsMovingHead extends LightsFixture { /** * @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 + * @deprecated */ public setPosition(pan: number, tilt: number) { this.valuesUpdatedAt = new Date(); diff --git a/src/modules/lights/entities/movement.ts b/src/modules/lights/entities/movement.ts index fddd0ae..363614a 100644 --- a/src/modules/lights/entities/movement.ts +++ b/src/modules/lights/entities/movement.ts @@ -12,6 +12,9 @@ export default class Movement implements IMovement { @Column({ type: 'tinyint', unsigned: true }) public panChannel: number; + @Column({ type: 'tinyint', unsigned: true, default: 0 }) + public basePanValue: number; + @Column({ type: 'tinyint', nullable: true, unsigned: true }) public finePanChannel?: number | null; @@ -33,16 +36,30 @@ export default class Movement implements IMovement { }; /** - * @param pan value between [0, 1] - * @param tilt value between [0, 1] + * @param panFactor value between [0, 1] + * @param tiltFactor value between [0, 1] */ - public setPositionRel(pan: number, tilt: number) { - this.setPositionAbs(pan * 255, tilt * 255); + public setPositionRel(panFactor: number, tiltFactor: number) { + const pan = panFactor * 170; + const tilt = tiltFactor * 255; + const panChannel = Math.floor(pan) + this.basePanValue; + 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, + }; } /** * @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 + * @deprecated */ public setPositionAbs(pan: number, tilt: number) { const panChannel = Math.floor(pan); diff --git a/src/modules/root/root-lights-operations-controller.ts b/src/modules/root/root-lights-operations-controller.ts index 0d2a0ae..c87722a 100644 --- a/src/modules/root/root-lights-operations-controller.ts +++ b/src/modules/root/root-lights-operations-controller.ts @@ -211,7 +211,7 @@ export class RootLightsOperationsController extends Controller { logger.audit(req.user, `Reset lights par "${chosenPar.fixture.name}" (id: ${id}).`); - const success = chosenPar.fixture.reset(); + const success = chosenPar.fixture.hardwareReset(); if (!success) { this.setStatus(422); } @@ -299,7 +299,7 @@ export class RootLightsOperationsController extends Controller { logger.audit(req.user, `Reset moving head RGB "${chosenMovingHead.fixture.name}" (id: ${id}).`); - const success = chosenMovingHead.fixture.reset(); + const success = chosenMovingHead.fixture.hardwareReset(); if (!success) { this.setStatus(422); } @@ -396,7 +396,7 @@ export class RootLightsOperationsController extends Controller { `Reset moving head wheel "${chosenMovingHead.fixture.name}" (id: ${id}).`, ); - const success = chosenMovingHead.fixture.reset(); + const success = chosenMovingHead.fixture.hardwareReset(); if (!success) { this.setStatus(422); } diff --git a/src/modules/root/root-lights-service.ts b/src/modules/root/root-lights-service.ts index f5bfac4..4e62012 100644 --- a/src/modules/root/root-lights-service.ts +++ b/src/modules/root/root-lights-service.ts @@ -131,6 +131,7 @@ export interface LightsMovingHeadParams extends LightsFixtureParams { tiltChannel: number; fineTiltChannel?: number; movingSpeedChannel?: number; + basePanValue?: number; } export interface LightsMovingHeadRgbCreateParams extends LightsMovingHeadParams, ColorParams {} @@ -510,10 +511,13 @@ export default class RootLightsService { tiltChannel: params.tiltChannel, fineTiltChannel: params.fineTiltChannel, movingSpeedChannel: params.movingSpeedChannel, + basePanValue: params.basePanValue ?? 0, } as Movement; } - private toColorWheel(params: LightsMovingHeadWheelCreateParams): IColorsWheel { + private toColorWheel( + params: LightsMovingHeadWheelCreateParams, + ): IColorsWheel & { colorChannel: number } { return { colorChannel: params.colorWheelChannel, goboChannel: params.goboWheelChannel, diff --git a/src/seed/seed.ts b/src/seed/seed.ts index 12ba8d0..49f3bbf 100644 --- a/src/seed/seed.ts +++ b/src/seed/seed.ts @@ -118,6 +118,7 @@ export default async function seedDatabase() { finePanChannel: 2, tiltChannel: 3, fineTiltChannel: 4, + basePanValue: 42, colorRedChannel: 8, colorGreenChannel: 9, colorBlueChannel: 10, @@ -397,6 +398,7 @@ export async function seedBorrelLights( const movingHeadStatic: FixedPositionCreateParams = { type: MovementEffects.FixedPosition, props: { + variant: 'Absolute', pan: 111, tilt: 18, }, @@ -794,8 +796,8 @@ export async function seedDiscoFloor(width: number, height: number) { return await service.createLightGroup(controller.id, { name: 'Disco floor', defaultHandler: '', - gridSizeX: width, - gridSizeY: height, + gridSizeX: width * 2, + gridSizeY: height * 2, pars, movingHeadWheels: [], movingHeadRgbs: [],