Update. Partytime!
This commit is contained in:
parent
13baa93aff
commit
c5f8aba5e0
@ -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)]
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
35
boilerbloat/src/patterns/stroboInsGesichtLol.ts
Normal file
35
boilerbloat/src/patterns/stroboInsGesichtLol.ts
Normal 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
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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>;
|
||||
|
BIN
boilerbloat/src/res/strobo.jpg
Normal file
BIN
boilerbloat/src/res/strobo.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 70 KiB |
Loading…
Reference in New Issue
Block a user