Update. Partytime!

This commit is contained in:
Kai Vogelgesang 2021-11-13 14:10:03 +01:00
parent 13baa93aff
commit c5f8aba5e0
Signed by: kai
GPG Key ID: 0A95D3B6E62C0879
6 changed files with 170 additions and 56 deletions

View File

@ -2,12 +2,13 @@
import { ipcMain } from 'electron';
import { blackout } from '../patterns/blackout';
import { Pattern, PatternOutput, RenderUpdate } from '../patterns/proto';
import { TestPattern } from '../patterns/test';
// import { TestPattern } from '../patterns/test';
import rust, { BeatTrackerHandle, MovingHeadState, OutputHandle, TrackerConfig } from 'rust_native_module';
import { ChaserPattern } from '../patterns/chaser';
import { HSLRandomMovementPattern } from '../patterns/hslRandom';
import { HSLRandomMovementPairsPattern } from '../patterns/hslRandom2';
import { SnoopDoggPattern } from '../patterns/snoop';
import { StroboInsGesichtPattern } from '../patterns/stroboInsGesichtLol';
export type AppState = {
patterns: { [key: string]: PatternOutput },
@ -62,11 +63,12 @@ class Backend {
// patterns
this.patterns = new Map();
this.patterns.set("test", new TestPattern());
this.patterns.set("chaser", new ChaserPattern());
// this.patterns.set("test", new TestPattern());
this.patterns.set("hslRandom", new HSLRandomMovementPattern());
this.patterns.set("snoop", new SnoopDoggPattern());
this.patterns.set("hslRandomPairs", new HSLRandomMovementPairsPattern());
this.patterns.set("chaser", new ChaserPattern());
this.patterns.set("snoop", new SnoopDoggPattern());
this.patterns.set("stroboInsGesictLOL", new StroboInsGesichtPattern());
this.state = {
patterns: {},
@ -142,8 +144,19 @@ class Backend {
let patternOutput = pattern.render(update);
this.state.patterns[patternId] = patternOutput;
if (patternId === this.state.selectedPattern && patternOutput) {
output = patternOutput;
if (patternId === this.state.selectedPattern) {
if (patternOutput) {
output = patternOutput;
} else {
let choices = [
'hslRandom',
'hslRandomPairs',
];
this.state.selectedPattern = choices[Math.floor(Math.random() * choices.length)]
}
}
}

View File

@ -1,42 +1,77 @@
import { MovingHeadState } from 'rust_native_module';
import { Pattern, PatternOutput, RenderUpdate } from './proto';
import { TARGETS } from './stroboInsGesichtLol';
import { startAddresses } from './stage';
import { hsl2rgb, rescale } from './util';
import { Tuple4 } from './types';
const template: MovingHeadState = {
startAddress: 0,
pan: 0,
tilt: 0,
brightness: {
type: 'dimmer',
value: 0.2,
},
rgbw: [255, 0, 0, 0],
speed: 1,
reset: false,
}
export class ChaserPattern implements Pattern {
render(update: RenderUpdate): PatternOutput {
lastUpdate: number;
rgbw: Tuple4<number>;
if (update.beatRelative === null) {
return null;
}
constructor() {
this.lastUpdate = -8;
this.rgbw = [0, 0, 0, 255];
}
let t = update.beatRelative;
render(update: RenderUpdate): PatternOutput {
let head_number = Math.floor(t % 4);
let result: Tuple4<MovingHeadState> = [{ ...template }, { ...template }, { ...template }, { ...template }];
[1, 15, 29, 43].forEach((startAddress, i) => {
result[i].startAddress = startAddress;
if (i === head_number) {
result[i].brightness = { type: 'dimmer', value: 1 };
}
});
return result;
if (update.beatRelative === null) {
this.lastUpdate = -8;
return null;
}
let t = update.beatRelative;
if (t - this.lastUpdate > 4) {
this.lastUpdate = Math.floor(t);
if (Math.random() > 0.2) {
const h = rescale(Math.random(), { to: [0, 360] });
const s = 1;
const l = 0.5;
const [r, g, b] = hsl2rgb(h, s, l);
this.rgbw = [
Math.round(r * 255),
Math.round(g * 255),
Math.round(b * 255),
0
]
} else {
this.rgbw = [
0, 0, 0, 255
];
}
}
let head_number = Math.floor(t % 4);
return startAddresses.map((address, index) => {
const [pan, tilt] = TARGETS[index];
const state: MovingHeadState = {
startAddress: address,
pan: pan,
tilt: tilt,
brightness: {
type: 'dimmer',
value: 0.1,
},
rgbw: this.rgbw,
speed: 1,
reset: false
}
if (index === head_number) {
state.brightness = { type: 'dimmer', value: 0.6 };
}
return state;
}) as PatternOutput;
}
}

View File

@ -0,0 +1,35 @@
import { MovingHeadState } from "rust_native_module";
import { Pattern, PatternOutput, RenderUpdate } from "./proto";
import { left, right, panForward, startAddresses, tiltUp, down } from "./stage";
import { Tuple4 } from "./types";
export const TARGETS: Tuple4<[number, number]> = [
[panForward + Math.atan(1) * left, tiltUp + Math.atan(1 / 3) * down],
[panForward + Math.atan(1 / 3) * left, tiltUp + Math.atan(1 / 3) * down],
[panForward + Math.atan(1 / 3) * right, tiltUp + Math.atan(1 / 3) * down],
[panForward + Math.atan(1) * right, tiltUp + Math.atan(1 / 3) * down],
]
export class StroboInsGesichtPattern implements Pattern {
render(_update: RenderUpdate): PatternOutput {
return startAddresses.map(((address, index) => {
const [pan, tilt] = TARGETS[index];
const state: MovingHeadState = {
startAddress: address,
pan: pan,
tilt: tilt,
brightness: {
type: "strobe",
value: 1,
},
rgbw: [0, 0, 0, 255],
speed: 1,
reset: false
}
return state;
})) as PatternOutput
}
}

View File

@ -1,14 +1,14 @@
import { MemoryRouter as Router, Switch, Route } from 'react-router-dom';
import { IpcRenderer } from 'electron/renderer';
import './App.css';
import { useEffect, useState } from 'react';
import { MemoryRouter as Router, Route, Switch } from 'react-router-dom';
import { AppState } from '../main/backend';
import PatternPreview from './PatternPreview';
import GraphVisualization from './Graph';
import ConfigControls from './ConfigControls';
import './App.css';
import { BongoCat } from './BongoCat';
import ConfigControls from './ConfigControls';
import GraphVisualization from './Graph';
import PatternPreview from './PatternPreview';
const ipcRenderer = (window as any).electron.ipcRenderer as IpcRenderer;
@ -50,7 +50,7 @@ const Frontend: React.FC<{ state: AppState }> = ({ state }) => {
<div style={{ width: "80%", marginTop: 80 }} className="element-row">
{
Object.entries(state.patterns).map(([patternId, output]) => (
<PatternPreview selected={state.selectedPattern === patternId} key={patternId} patternId={patternId} output={output} />
<PatternPreview selectedPattern={state.selectedPattern ?? ""} key={patternId} patternId={patternId} output={output} />
))
}
</div>

View File

@ -1,28 +1,59 @@
import { IpcRenderer } from "electron/renderer";
import { useState } from "react";
import { PatternOutput } from "../patterns/proto";
import './patterns.css';
import _snoop from '../res/snoop.png';
import _strobo from '../res/strobo.jpg';
const ipcRenderer = (window as any).electron.ipcRenderer as IpcRenderer;
const PatternPreview: React.FC<{ patternId: string, output: PatternOutput , selected: boolean}> = ({ patternId, output, selected}) => {
const PatternPreview: React.FC<{ patternId: string, output: PatternOutput, selectedPattern: string }> = ({ patternId, output, selectedPattern }) => {
const [previousPattern, setPreviousPattern] = useState(patternId);
const selected = selectedPattern === patternId;
const selectedClass = selected ? " selected" : "";
const styleClass = "pattern-button" + selectedClass;
const styleObj: React.CSSProperties = {};
if (patternId === "snoop") {
styleObj.background = "none";
}
return <div style={{ margin: 5 }}>
<svg className={styleClass}/*style={{ padding: 15, backgroundColor: "#333333", borderColor: "white", borderRadius: 15 }}*/ onClick={() =>
ipcRenderer.send("pattern-select", patternId)
} width={260 + 2*70} height="150">
<svg className={styleClass}
style={styleObj}
onMouseUp={() => {
console.log("umu on", patternId);
if (patternId === "stroboInsGesictLOL") {
// restore after strobe
ipcRenderer.send("pattern-select", previousPattern);
}
}}
onMouseDown={() => {
console.log("omd on", patternId);
console.log("storing", selectedPattern);
// store before strobe
setPreviousPattern(selectedPattern);
ipcRenderer.send("pattern-select", patternId)
}}
//onClick={() =>
// ipcRenderer.send("pattern-select", patternId)
//}
width={260 + 2 * 70} height="150">
{output?.map((headState, index) => {
const [r, g, b, w] = headState.rgbw;
const brightness = "value" in headState.brightness ? headState.brightness.value : 0;
return <ColorCone tilt={headState.tilt} pan={headState.pan} white={w} color={`rgba(${r},${g},${b},${(0.3 + 0.7 * brightness)})`} translate={[70 + index * 65, 0]} />
return <ColorCone key={index} tilt={headState.tilt} pan={headState.pan} white={w} color={`rgba(${r},${g},${b},${(0.3 + 0.7 * brightness)})`} translate={[70 + index * 65, 0]} />
}
)}
</svg>
</div>
}
const ColorCone: React.FC<{ pan: number, tilt: number, color: string, translate: [number, number], white: number}> = ({ color, translate: [x, y], white, pan, tilt }) => {
const ColorCone: React.FC<{ pan: number, tilt: number, color: string, translate: [number, number], white: number }> = ({ color, translate: [x, y], white, pan, tilt }) => {
return <g transform={`translate(${x} ${y})`}>
{white === 0 ? null
: <Cone pan={pan} tilt={tilt} color={`rgba(1, 1, 1, ${white}`} translate={[x, y]} />
@ -32,14 +63,14 @@ const ColorCone: React.FC<{ pan: number, tilt: number, color: string, translate:
};
const Cone: React.FC<{ color: string, translate: [number, number] , pan : number, tilt: number}> = ({ color , tilt, pan}) => {
const Cone: React.FC<{ color: string, translate: [number, number], pan: number, tilt: number }> = ({ color, tilt, pan }) => {
// tilt is correct with -45 degree offset, but if lights are pointing down, they will be displayed to point down right
const degreesTilt = tilt / (2* Math.PI) * 360 - 45;
const degreesTilt = tilt / (2 * Math.PI) * 360 - 45;
// TODO: adjust if necessary
const degreesPan = pan / (2* Math.PI) * 360 - 45;
const degreesPan = pan / (2 * Math.PI) * 360 - 45;
// console.log(degreesTilt);
return <g className="cone" style={{transform: `rotate(${degreesTilt}deg) rotateX(${degreesPan}deg)`}} >
return <g className="cone" style={{ transform: `rotate(${degreesTilt}deg) rotateX(${degreesPan}deg)` }} >
<path stroke="black" fill="gray" d="M 15 0 L 45 0 L 60 30 L 0 30 L 15 0" />
<path fill={color} d="M 15 30 L 0 150 L 60 150 L 45 30 " />
</g>;

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB