Implement Autocorrelation
This commit is contained in:
@@ -6,11 +6,15 @@ import { TestPattern } from '../patterns/test';
|
||||
import rust, { BeatTrackerHandle, MovingHeadState, OutputHandle } from 'rust_native_module';
|
||||
import { ChaserPattern } from '../patterns/chaser';
|
||||
|
||||
type AppState = {
|
||||
export type AppState = {
|
||||
patterns: { [key: string]: PatternOutput },
|
||||
selectedPattern: string | null,
|
||||
|
||||
beatProgress: number | null,
|
||||
graphData: {
|
||||
bassFiltered: Array<number>,
|
||||
autoCorrelated: Array<number>,
|
||||
} | null,
|
||||
};
|
||||
|
||||
class Backend {
|
||||
@@ -61,8 +65,13 @@ class Backend {
|
||||
patterns: {},
|
||||
selectedPattern: null,
|
||||
beatProgress: null,
|
||||
graphData: null,
|
||||
}
|
||||
|
||||
ipcMain.on('pattern-select', async (_, arg) => {
|
||||
this.state.selectedPattern = arg;
|
||||
});
|
||||
|
||||
let time: Time = {
|
||||
absolute: 0,
|
||||
beatRelative: this.state.beatProgress,
|
||||
@@ -93,6 +102,8 @@ class Backend {
|
||||
this.state.beatProgress = null;
|
||||
}
|
||||
|
||||
this.state.graphData = this.beatTracker.getGraphPoints();
|
||||
|
||||
let date = new Date();
|
||||
let time: Time = {
|
||||
absolute: date.getTime() / 1000,
|
||||
|
||||
@@ -40,7 +40,7 @@ const createWindow = async () => {
|
||||
},
|
||||
});
|
||||
|
||||
mainWindow.removeMenu();
|
||||
// mainWindow.removeMenu();
|
||||
|
||||
mainWindow.loadURL(resolveHtmlPath('index.html'));
|
||||
|
||||
|
||||
@@ -2,13 +2,31 @@ import { MovingHeadState } from 'rust_native_module';
|
||||
import { Pattern, PatternOutput, Time } from './proto';
|
||||
|
||||
export class ChaserPattern implements Pattern {
|
||||
|
||||
lastBeat: number;
|
||||
lastTime: number;
|
||||
|
||||
constructor() {
|
||||
this.lastBeat = 0;
|
||||
this.lastTime = 0;
|
||||
}
|
||||
|
||||
render(time: Time): PatternOutput {
|
||||
|
||||
if (time.beatRelative === null) {
|
||||
this.lastBeat = 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
let head_number = Math.ceil(time.beatRelative) % 4;
|
||||
let t = time.beatRelative;
|
||||
|
||||
if (t < this.lastTime) {
|
||||
this.lastBeat += 1;
|
||||
}
|
||||
|
||||
this.lastTime = t;
|
||||
|
||||
let head_number = this.lastBeat % 4;
|
||||
|
||||
let template: MovingHeadState = {
|
||||
startAddress: 0,
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
body {
|
||||
color: #c9c9c9;
|
||||
background-color: #222222;
|
||||
}
|
||||
|
||||
@@ -4,15 +4,19 @@ import { IpcRenderer } from 'electron/renderer';
|
||||
import './App.css';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { AppState } from '../main/backend';
|
||||
import PatternPreview from './PatternPreview';
|
||||
import GraphVisualization from './Graph';
|
||||
|
||||
const ipcRenderer = (window as any).electron.ipcRenderer as IpcRenderer;
|
||||
|
||||
function tap() {
|
||||
ipcRenderer.send("beat-tracking", "tap");
|
||||
}
|
||||
|
||||
const Frontend: React.FC = () => {
|
||||
const FrontendRoot: React.FC = () => {
|
||||
|
||||
const [state, setState] = useState<any>();
|
||||
const [state, setState] = useState<AppState>();
|
||||
|
||||
const pollMain = async () => {
|
||||
const reply = await ipcRenderer.invoke("poll");
|
||||
@@ -25,20 +29,52 @@ const Frontend: React.FC = () => {
|
||||
});
|
||||
|
||||
return <>
|
||||
<div>
|
||||
State: {state ? JSON.stringify(state) : "undef"}
|
||||
</div>
|
||||
{
|
||||
state
|
||||
? <Frontend state={state} />
|
||||
: <div> oops </div>
|
||||
}
|
||||
</>;
|
||||
};
|
||||
|
||||
|
||||
const Frontend: React.FC<{ state: AppState }> = ({ state }) => {
|
||||
|
||||
return <>
|
||||
<div>
|
||||
<button onClick={tap}>Tap</button>
|
||||
</div>
|
||||
<div>
|
||||
{Object.entries(state.patterns).map(([patternId, output]) => (
|
||||
<PatternPreview key={patternId} patternId={patternId} output={output} />
|
||||
))}
|
||||
</div>
|
||||
{
|
||||
state.graphData
|
||||
? <div>
|
||||
<p> Bass Filtered </p>
|
||||
<p>
|
||||
<GraphVisualization points={state.graphData.bassFiltered} />
|
||||
</p>
|
||||
<p> Autocorrelation </p>
|
||||
<p>
|
||||
<GraphVisualization points={state.graphData.autoCorrelated} />
|
||||
</p>
|
||||
</div>
|
||||
: <div> no graph data </div>
|
||||
}
|
||||
<div>
|
||||
{JSON.stringify(state)}
|
||||
</div>
|
||||
</>;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Router>
|
||||
<Switch>
|
||||
<Route path="/" component={Frontend} />
|
||||
<Route path="/" component={FrontendRoot} />
|
||||
</Switch>
|
||||
</Router>
|
||||
);
|
||||
|
||||
45
boilerbloat/src/renderer/Graph.tsx
Normal file
45
boilerbloat/src/renderer/Graph.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
const GraphVisualization: React.FC<{ points: Array<number>, min?: number, max?: number }> = ({ points, min, max }) => {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
const minY = min ? min : Math.min(...points);
|
||||
const maxY = max ? max : Math.max(...points);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
const backgroundColor = '#333333';
|
||||
const foregroundColor = '#FFA500';
|
||||
|
||||
// clear
|
||||
|
||||
ctx.fillStyle = backgroundColor;
|
||||
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||
|
||||
// draw points
|
||||
|
||||
ctx.fillStyle = foregroundColor;
|
||||
for (const [x, f] of points.entries()) {
|
||||
const yFract = 1 - (f - minY) / (maxY - minY);
|
||||
|
||||
const y = yFract * ctx.canvas.height;
|
||||
|
||||
ctx.fillRect(x, y, 1, 1);
|
||||
}
|
||||
|
||||
}, [points]);
|
||||
|
||||
return <><canvas ref={canvasRef} width={points.length} height={100} />{`${minY.toFixed(2)} - ${maxY.toFixed(2)}`}</>
|
||||
}
|
||||
|
||||
export default GraphVisualization;
|
||||
14
boilerbloat/src/renderer/PatternPreview.tsx
Normal file
14
boilerbloat/src/renderer/PatternPreview.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { IpcRenderer } from "electron/renderer";
|
||||
import { PatternOutput } from "../patterns/proto";
|
||||
|
||||
const ipcRenderer = (window as any).electron.ipcRenderer as IpcRenderer;
|
||||
|
||||
const PatternPreview: React.FC<{ patternId: string, output: PatternOutput }> = ({ patternId }) => {
|
||||
return <button onClick={() => {
|
||||
ipcRenderer.send("pattern-select", patternId);
|
||||
}}>
|
||||
{patternId}
|
||||
</button>;
|
||||
}
|
||||
|
||||
export default PatternPreview;
|
||||
Reference in New Issue
Block a user