diff --git a/boilerbloat/src/main/backend.ts b/boilerbloat/src/main/backend.ts index 5bd606f..3ce09b3 100644 --- a/boilerbloat/src/main/backend.ts +++ b/boilerbloat/src/main/backend.ts @@ -1,7 +1,7 @@ import { ipcMain } from 'electron'; import { blackout } from '../patterns/blackout'; -import { Pattern, PatternOutput, Time } from '../patterns/proto'; +import { Pattern, PatternOutput, RenderUpdate } from '../patterns/proto'; import { TestPattern } from '../patterns/test'; import rust, { BeatTrackerHandle, MovingHeadState, OutputHandle, TrackerConfig } from 'rust_native_module'; import { ChaserPattern } from '../patterns/chaser'; @@ -70,7 +70,7 @@ class Backend { trackerConfig: { mode: "auto", acThreshold: 1000, - zeroCrossingBeatDelay: 0, + zeroCrossingBeatDelay: 50, } } @@ -88,13 +88,14 @@ class Backend { this.beatTracker.setConfig(this.state.trackerConfig); }); - let time: Time = { + let update: RenderUpdate = { absolute: 0, beatRelative: this.state.beatProgress, + bassVolume: 0, } for (let [patternId, pattern] of this.patterns.entries()) { - let patternOutput = pattern.render(time); + let patternOutput = pattern.render(update); this.state.patterns[patternId] = patternOutput; } @@ -121,9 +122,10 @@ class Backend { this.state.graphData = this.beatTracker.getGraphPoints(); let date = new Date(); - let time: Time = { + let update: RenderUpdate = { absolute: date.getTime() / 1000, beatRelative: this.state.beatProgress, + bassVolume: this.beatTracker.getCurrentBass(), } // render all patterns and write selected pattern to DMX @@ -131,7 +133,7 @@ class Backend { let output: Array = blackout; for (let [patternId, pattern] of this.patterns.entries()) { - let patternOutput = pattern.render(time); + let patternOutput = pattern.render(update); this.state.patterns[patternId] = patternOutput; if (patternId === this.state.selectedPattern && patternOutput) { diff --git a/boilerbloat/src/patterns/chaser.ts b/boilerbloat/src/patterns/chaser.ts index 4f73eb5..749e867 100644 --- a/boilerbloat/src/patterns/chaser.ts +++ b/boilerbloat/src/patterns/chaser.ts @@ -1,42 +1,42 @@ import { MovingHeadState } from 'rust_native_module'; -import { Pattern, PatternOutput, Time } from './proto'; +import { Pattern, PatternOutput, RenderUpdate } from './proto'; +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(time: Time): PatternOutput { + render(update: RenderUpdate): PatternOutput { - if (time.beatRelative === null) { - return null; + if (update.beatRelative === null) { + return null; + } + + let t = update.beatRelative; + + let head_number = Math.floor(t % 4); + + let result: Tuple4 = [{ ...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; } - - let t = time.beatRelative; - - let head_number = Math.floor(t % 4); - - let template: MovingHeadState = { - startAddress: 0, - pan: 0, - tilt: 0, - brightness: { - type: 'dimmer', - value: 0.2, - }, - rgbw: [255, 0, 0, 0], - speed: 1, - reset: false, - } - - let result = []; - - for (let [i, startAddress] of [1, 15, 29, 43].entries()) { - result[i] = { ...template }; - result[i].startAddress = startAddress; - - if (i === head_number) { - result[i].brightness = { type: 'dimmer', value: 1 }; - } - } - - return result; - } } diff --git a/boilerbloat/src/patterns/proto.ts b/boilerbloat/src/patterns/proto.ts index a4a2909..4846904 100644 --- a/boilerbloat/src/patterns/proto.ts +++ b/boilerbloat/src/patterns/proto.ts @@ -1,12 +1,14 @@ import { MovingHeadState } from "rust_native_module"; +import { Tuple4 } from "./types"; -export type Time = { +export type RenderUpdate = { absolute: number, beatRelative: number | null, + bassVolume: number, }; -export type PatternOutput = Array | null; +export type PatternOutput = Tuple4 | null; export interface Pattern { - render(time: Time): PatternOutput; + render(update: RenderUpdate): PatternOutput; } diff --git a/boilerbloat/src/patterns/stage.ts b/boilerbloat/src/patterns/stage.ts index 681260b..a710856 100644 --- a/boilerbloat/src/patterns/stage.ts +++ b/boilerbloat/src/patterns/stage.ts @@ -1 +1,10 @@ -export const startAddresses = [1, 15, 29, 43] +import { Tuple4 } from "./types"; + +export const startAddresses: Tuple4 = [1, 15, 29, 43] + +export const panLeft = 0; +export const panRight = -Math.PI; +export const panForward = -0.5 * Math.PI; + +export const tiltUp = 0; +export const tiltDown = -0.5 * Math.PI; // TODO verify diff --git a/boilerbloat/src/patterns/test.ts b/boilerbloat/src/patterns/test.ts index 8da1d3b..e831061 100644 --- a/boilerbloat/src/patterns/test.ts +++ b/boilerbloat/src/patterns/test.ts @@ -1,6 +1,7 @@ import { MovingHeadState } from "rust_native_module"; -import { Pattern, PatternOutput, Time } from "./proto"; +import { Pattern, PatternOutput, RenderUpdate } from "./proto"; import { startAddresses } from "./stage"; +import { Tuple4 } from "./types"; export class TestPattern implements Pattern { @@ -11,8 +12,8 @@ export class TestPattern implements Pattern { [0, 0, 0, 255], ] - render(time: Time): PatternOutput { - let t = time.absolute % this.rgbw.length; + render(update: RenderUpdate): PatternOutput { + let t = update.absolute % this.rgbw.length; let second = Math.floor(t); let brightness = 1 - (t % 1); @@ -33,7 +34,7 @@ export class TestPattern implements Pattern { reset: false } return state; - }) + }) as Tuple4; } } diff --git a/boilerbloat/src/patterns/types.ts b/boilerbloat/src/patterns/types.ts new file mode 100644 index 0000000..1357e06 --- /dev/null +++ b/boilerbloat/src/patterns/types.ts @@ -0,0 +1 @@ +export type Tuple4 = [T, T, T, T]; diff --git a/boilerbloat/src/renderer/App.tsx b/boilerbloat/src/renderer/App.tsx index 85b4545..cd671ee 100644 --- a/boilerbloat/src/renderer/App.tsx +++ b/boilerbloat/src/renderer/App.tsx @@ -8,6 +8,7 @@ import { AppState } from '../main/backend'; import PatternPreview from './PatternPreview'; import GraphVisualization from './Graph'; import ConfigControls from './ConfigControls'; +import { BongoCat } from './BongoCat'; const ipcRenderer = (window as any).electron.ipcRenderer as IpcRenderer; @@ -41,14 +42,17 @@ const FrontendRoot: React.FC = () => { const Frontend: React.FC<{ state: AppState }> = ({ state }) => { - return <> + return
+
- +
-
- {Object.entries(state.patterns).map(([patternId, output]) => ( - - ))} +
+ { + Object.entries(state.patterns).map(([patternId, output]) => ( + + )) + }
{ state.graphData @@ -64,8 +68,7 @@ const Frontend: React.FC<{ state: AppState }> = ({ state }) => {
:
no graph data
} - - ; +
; } diff --git a/boilerbloat/src/renderer/BongoCat.tsx b/boilerbloat/src/renderer/BongoCat.tsx new file mode 100644 index 0000000..3b2b6d6 --- /dev/null +++ b/boilerbloat/src/renderer/BongoCat.tsx @@ -0,0 +1,28 @@ +import cat2 from '../res/bongo2.png'; +import cat_left from '../res/bongo_l.png'; +import cat_right from '../res/bongo_r.png'; +import cat_high from '../res/bongo_w.png'; + +export const BongoCat: React.FC<{ beatProgress: number | null, onClick: () => void, high?: boolean }> = ({ beatProgress, onClick, high }) => { + + let image: string = cat2; + + if (beatProgress !== null) { + const fractional = beatProgress % 1; + const rounded = Math.round(beatProgress); + if (fractional < 0.5) + if (rounded % 2 == 0) { + image = cat_left; + } else { + image = cat_right; + } + } + if (high) + image=cat_high; + + return
+
+ +
+
; +} diff --git a/boilerbloat/src/renderer/ConfigControls.tsx b/boilerbloat/src/renderer/ConfigControls.tsx index 3738432..ead0c58 100644 --- a/boilerbloat/src/renderer/ConfigControls.tsx +++ b/boilerbloat/src/renderer/ConfigControls.tsx @@ -1,26 +1,38 @@ -import { useRef } from "react"; - import { IpcRenderer } from 'electron/renderer'; +import { useState } from "react"; +import './patterns.css'; + + const ipcRenderer = (window as any).electron.ipcRenderer as IpcRenderer; -const ConfigControls: React.FC = () => { +const ConfigControls: React.FC<{ updateDelay: number }> = ({ updateDelay }) => { - const bruh = useRef(null); + const [manual, _setManual] = useState(false); + const setManual = (b: boolean) => { + _setManual(b); + ipcRenderer.send("manual-mode", b); + }; return <> -

Delay:

- { - ipcRenderer.send("update-delay", e.currentTarget.value) - } - } /> -

Manual mode: - { - ipcRenderer.send("manual-mode", e.currentTarget.checked); - } - }> -

+ {!manual ? +
+

Delay:

+ { + if (e.currentTarget.value) + ipcRenderer.send("update-delay", e.currentTarget.value) + } + } /> +
+ : null} +
+
setManual(false)} className={"config-button" + (manual ? "" : " manual")}> + Auto +
+
setManual(true)} className={"config-button" + (manual ? " manual" : "")}> + Manual +
+
; } diff --git a/boilerbloat/src/renderer/PatternPreview.tsx b/boilerbloat/src/renderer/PatternPreview.tsx index dc9152c..391480c 100644 --- a/boilerbloat/src/renderer/PatternPreview.tsx +++ b/boilerbloat/src/renderer/PatternPreview.tsx @@ -1,14 +1,42 @@ import { IpcRenderer } from "electron/renderer"; import { PatternOutput } from "../patterns/proto"; +import './patterns.css'; + const ipcRenderer = (window as any).electron.ipcRenderer as IpcRenderer; -const PatternPreview: React.FC<{ patternId: string, output: PatternOutput }> = ({ patternId }) => { - return ; +const PatternPreview: React.FC<{ patternId: string, output: PatternOutput , selected: boolean}> = ({ patternId, output, selected}) => { + const selectedClass = selected ? " selected" : ""; + const styleClass = "pattern-button" + selectedClass; + return
+ + ipcRenderer.send("pattern-select", patternId) + } width="260" height="150"> + {output?.map((headState, index) => { + const [r, g, b, w] = headState.rgbw; + const brightness = "value" in headState.brightness ? headState.brightness.value : 0; + return + } + )} + +
} +const ColorCone: React.FC<{ color: string, translate: [number, number], white: number }> = ({ color, translate: [x, y], white }) => { + return + {white === 0 ? null + : + } + + +}; + + +const Cone: React.FC<{ color: string, translate: [number, number] }> = ({ color }) => { + return <> + + + ; +}; + export default PatternPreview; diff --git a/boilerbloat/src/renderer/patterns.css b/boilerbloat/src/renderer/patterns.css new file mode 100644 index 0000000..992c822 --- /dev/null +++ b/boilerbloat/src/renderer/patterns.css @@ -0,0 +1,85 @@ +.pattern-button { + padding: 15px; + background-color: #333333; + border-radius: 15px; + border-color: #333333; + border-width: 5px; + border-style: solid; +} +.pattern-button:hover { + border-color: #efefef; +} +.pattern-button.selected { + border-color: #fc2424; +} +.pattern-button.selected:hover{ + border-color: #f77878; +} + +.float-right { + position: absolute; + top: 10px; + right: 10px; +} +.bongo-box { + user-select: none; + padding-top: 10px; + display: flex; + align-items: center; + justify-content: center; + width: 200px; + height: 100px; + border-radius: 15px; + background-color: white; + cursor: pointer; +} +.bongo-box.high { + background-color: greenyellow; +} + +.bongo-box.easteregg { + animation: spin 300ms linear; +} + +@keyframes spin { + from { + transform: rotate(0deg) + } + to { + transform: rotate(360deg) + } +} + +.container { + display: flex; + flex-direction: column; + align-items: center; +} + +.config-button { + width: auto; + min-width: 75px; + height: 30px; + padding: 15px; + text-align: center; + vertical-align: middle; + font-weight: bold; + text-transform: uppercase; + border-radius: 15px; + color: white; + background-color: #474747; + cursor: pointer; +} + +.config-button.manual { + background-color: #a7a7a7; +} + +.element-row { + display: flex; + flex-direction: row; +} +.element-row > div { + margin: 5px; + display: inline-block; +} diff --git a/boilerbloat/src/res/bongo2.png b/boilerbloat/src/res/bongo2.png new file mode 100644 index 0000000..0cab2f8 Binary files /dev/null and b/boilerbloat/src/res/bongo2.png differ diff --git a/boilerbloat/src/res/bongo_l.png b/boilerbloat/src/res/bongo_l.png new file mode 100644 index 0000000..33f737e Binary files /dev/null and b/boilerbloat/src/res/bongo_l.png differ diff --git a/boilerbloat/src/res/bongo_r.png b/boilerbloat/src/res/bongo_r.png new file mode 100644 index 0000000..6ca8a6c Binary files /dev/null and b/boilerbloat/src/res/bongo_r.png differ diff --git a/boilerbloat/src/res/bongo_w.png b/boilerbloat/src/res/bongo_w.png new file mode 100644 index 0000000..9d9c9ae Binary files /dev/null and b/boilerbloat/src/res/bongo_w.png differ