Implement Autocorrelation

This commit is contained in:
2021-11-12 05:44:37 +01:00
parent abb0476a17
commit 41b54f6209
16 changed files with 833 additions and 17 deletions

View File

@@ -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,

View File

@@ -40,7 +40,7 @@ const createWindow = async () => {
},
});
mainWindow.removeMenu();
// mainWindow.removeMenu();
mainWindow.loadURL(resolveHtmlPath('index.html'));

View File

@@ -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,

View File

@@ -0,0 +1,4 @@
body {
color: #c9c9c9;
background-color: #222222;
}

View File

@@ -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>
);

View 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;

View 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;