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 { ipcMain } from 'electron';
import { blackout } from '../patterns/blackout'; import { blackout } from '../patterns/blackout';
import { Pattern, PatternOutput, RenderUpdate } from '../patterns/proto'; 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 rust, { BeatTrackerHandle, MovingHeadState, OutputHandle, TrackerConfig } from 'rust_native_module';
import { ChaserPattern } from '../patterns/chaser'; import { ChaserPattern } from '../patterns/chaser';
import { HSLRandomMovementPattern } from '../patterns/hslRandom'; import { HSLRandomMovementPattern } from '../patterns/hslRandom';
import { HSLRandomMovementPairsPattern } from '../patterns/hslRandom2'; import { HSLRandomMovementPairsPattern } from '../patterns/hslRandom2';
import { SnoopDoggPattern } from '../patterns/snoop'; import { SnoopDoggPattern } from '../patterns/snoop';
import { StroboInsGesichtPattern } from '../patterns/stroboInsGesichtLol';
export type AppState = { export type AppState = {
patterns: { [key: string]: PatternOutput }, patterns: { [key: string]: PatternOutput },
@ -62,11 +63,12 @@ class Backend {
// patterns // patterns
this.patterns = new Map(); this.patterns = new Map();
this.patterns.set("test", new TestPattern()); // this.patterns.set("test", new TestPattern());
this.patterns.set("chaser", new ChaserPattern());
this.patterns.set("hslRandom", new HSLRandomMovementPattern()); this.patterns.set("hslRandom", new HSLRandomMovementPattern());
this.patterns.set("snoop", new SnoopDoggPattern());
this.patterns.set("hslRandomPairs", new HSLRandomMovementPairsPattern()); 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 = { this.state = {
patterns: {}, patterns: {},
@ -142,8 +144,19 @@ class Backend {
let patternOutput = pattern.render(update); let patternOutput = pattern.render(update);
this.state.patterns[patternId] = patternOutput; this.state.patterns[patternId] = patternOutput;
if (patternId === this.state.selectedPattern && patternOutput) { if (patternId === this.state.selectedPattern) {
output = patternOutput; 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 { MovingHeadState } from 'rust_native_module';
import { Pattern, PatternOutput, RenderUpdate } from './proto'; import { Pattern, PatternOutput, RenderUpdate } from './proto';
import { TARGETS } from './stroboInsGesichtLol';
import { startAddresses } from './stage';
import { hsl2rgb, rescale } from './util';
import { Tuple4 } from './types'; 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 { export class ChaserPattern implements Pattern {
render(update: RenderUpdate): PatternOutput { lastUpdate: number;
rgbw: Tuple4<number>;
if (update.beatRelative === null) { constructor() {
return null; this.lastUpdate = -8;
} this.rgbw = [0, 0, 0, 255];
}
let t = update.beatRelative; render(update: RenderUpdate): PatternOutput {
let head_number = Math.floor(t % 4); if (update.beatRelative === null) {
this.lastUpdate = -8;
let result: Tuple4<MovingHeadState> = [{ ...template }, { ...template }, { ...template }, { ...template }]; return null;
[1, 15, 29, 43].forEach((startAddress, i) => {
result[i].startAddress = startAddress;
if (i === head_number) {
result[i].brightness = { type: 'dimmer', value: 1 };
}
});
return result;
} }
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 { IpcRenderer } from 'electron/renderer';
import './App.css';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { MemoryRouter as Router, Route, Switch } from 'react-router-dom';
import { AppState } from '../main/backend'; import { AppState } from '../main/backend';
import PatternPreview from './PatternPreview'; import './App.css';
import GraphVisualization from './Graph';
import ConfigControls from './ConfigControls';
import { BongoCat } from './BongoCat'; 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; 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"> <div style={{ width: "80%", marginTop: 80 }} className="element-row">
{ {
Object.entries(state.patterns).map(([patternId, output]) => ( 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> </div>

View File

@ -1,28 +1,59 @@
import { IpcRenderer } from "electron/renderer"; import { IpcRenderer } from "electron/renderer";
import { useState } from "react";
import { PatternOutput } from "../patterns/proto"; import { PatternOutput } from "../patterns/proto";
import './patterns.css'; 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 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 selectedClass = selected ? " selected" : "";
const styleClass = "pattern-button" + selectedClass; const styleClass = "pattern-button" + selectedClass;
const styleObj: React.CSSProperties = {};
if (patternId === "snoop") {
styleObj.background = "none";
}
return <div style={{ margin: 5 }}> return <div style={{ margin: 5 }}>
<svg className={styleClass}/*style={{ padding: 15, backgroundColor: "#333333", borderColor: "white", borderRadius: 15 }}*/ onClick={() => <svg className={styleClass}
ipcRenderer.send("pattern-select", patternId) style={styleObj}
} width={260 + 2*70} height="150"> 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) => { {output?.map((headState, index) => {
const [r, g, b, w] = headState.rgbw; const [r, g, b, w] = headState.rgbw;
const brightness = "value" in headState.brightness ? headState.brightness.value : 0; 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> </svg>
</div> </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})`}> return <g transform={`translate(${x} ${y})`}>
{white === 0 ? null {white === 0 ? null
: <Cone pan={pan} tilt={tilt} color={`rgba(1, 1, 1, ${white}`} translate={[x, y]} /> : <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 // 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 // TODO: adjust if necessary
const degreesPan = pan / (2* Math.PI) * 360 - 45; const degreesPan = pan / (2 * Math.PI) * 360 - 45;
// console.log(degreesTilt); // 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 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 " /> <path fill={color} d="M 15 30 L 0 150 L 60 150 L 45 30 " />
</g>; </g>;

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB