Implemeeeeeent
This commit is contained in:
parent
a5dbe91bc1
commit
1bc7c6ab27
120
boilerbloat/src/main/backend.ts
Normal file
120
boilerbloat/src/main/backend.ts
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
|
||||||
|
import { ipcMain } from 'electron';
|
||||||
|
import { blackout } from '../patterns/blackout';
|
||||||
|
import { Pattern, PatternOutput, Time } from '../patterns/proto';
|
||||||
|
import { TestPattern } from '../patterns/test';
|
||||||
|
import rust, { BeatTrackerHandle, MovingHeadState, OutputHandle } from 'rust_native_module';
|
||||||
|
|
||||||
|
type AppState = {
|
||||||
|
patterns: { [key: string]: PatternOutput },
|
||||||
|
selectedPattern: string | null,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Backend {
|
||||||
|
|
||||||
|
beatTracker: BeatTrackerHandle;
|
||||||
|
dmxOutput: OutputHandle;
|
||||||
|
|
||||||
|
patterns: Map<string, Pattern>;
|
||||||
|
patternOutputs: Map<string, PatternOutput>;
|
||||||
|
selectedPattern: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
|
||||||
|
// beat tracking
|
||||||
|
|
||||||
|
let beatTracker = rust.getBeatTracker();
|
||||||
|
|
||||||
|
if (beatTracker.type !== 'success') {
|
||||||
|
throw new Error("could not initialize beat tracking");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.beatTracker = beatTracker.value;
|
||||||
|
ipcMain.on('beat-tracking', async (_, arg) => {
|
||||||
|
if (arg === 'tap') {
|
||||||
|
this.beatTracker.tap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// output
|
||||||
|
|
||||||
|
let dmxOutput = rust.openOutput("/dev/ttyUSB0");
|
||||||
|
|
||||||
|
if (dmxOutput.type !== 'success') {
|
||||||
|
throw new Error("could not open DMX output");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dmxOutput = dmxOutput.value;
|
||||||
|
|
||||||
|
// patterns
|
||||||
|
|
||||||
|
this.patterns = new Map();
|
||||||
|
this.patterns.set("test", new TestPattern());
|
||||||
|
|
||||||
|
this.patternOutputs = new Map();
|
||||||
|
this.selectedPattern = "test";
|
||||||
|
|
||||||
|
let time: Time = {
|
||||||
|
absolute: 0,
|
||||||
|
beat_relative: null
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let [patternId, pattern] of this.patterns.entries()) {
|
||||||
|
this.patternOutputs.set(patternId, pattern.render(time));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
getState(): AppState {
|
||||||
|
let result: AppState = {
|
||||||
|
patterns: {},
|
||||||
|
selectedPattern: this.selectedPattern,
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let [patternId, patternOutput] of this.patternOutputs) {
|
||||||
|
result.patterns[patternId] = patternOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
let date = new Date();
|
||||||
|
let time: Time = {
|
||||||
|
absolute: date.getTime() / 1000,
|
||||||
|
beat_relative: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
// render all patterns
|
||||||
|
|
||||||
|
if (!this.patterns) {
|
||||||
|
console.log("big oof?");
|
||||||
|
throw new Error("???");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let [patternId, pattern] of this.patterns.entries()) {
|
||||||
|
this.patternOutputs.set(patternId, pattern.render(time));
|
||||||
|
}
|
||||||
|
|
||||||
|
// write selected pattern
|
||||||
|
|
||||||
|
let output: Array<MovingHeadState> = blackout;
|
||||||
|
|
||||||
|
if (this.selectedPattern !== null) {
|
||||||
|
let selectedPatternOutput = this.patternOutputs.get(this.selectedPattern)
|
||||||
|
|
||||||
|
if (selectedPatternOutput) {
|
||||||
|
output = selectedPatternOutput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dmxOutput.set(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.dmxOutput.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Backend;
|
@ -11,28 +11,20 @@
|
|||||||
import 'core-js/stable';
|
import 'core-js/stable';
|
||||||
import 'regenerator-runtime/runtime';
|
import 'regenerator-runtime/runtime';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { app, BrowserWindow, shell, ipcMain } from 'electron';
|
import { app, BrowserWindow, ipcMain, shell } from 'electron';
|
||||||
import { resolveHtmlPath } from './util';
|
import { resolveHtmlPath } from './util';
|
||||||
|
import Backend from './backend';
|
||||||
|
|
||||||
import rust from 'rust_native_module';
|
let backend = new Backend();
|
||||||
|
|
||||||
|
let mainLoop = setInterval(() => { backend.update(); }, 20);
|
||||||
|
|
||||||
|
ipcMain.handle('poll', () => {
|
||||||
|
return backend.getState();
|
||||||
|
});
|
||||||
|
|
||||||
let mainWindow: BrowserWindow | null = null;
|
let mainWindow: BrowserWindow | null = null;
|
||||||
|
|
||||||
let beat_tracker = rust.getBeatTracker();
|
|
||||||
|
|
||||||
// TODO @thamma why does this not infer that beat_tracker has tap?
|
|
||||||
if (beat_tracker.type === 'success') {
|
|
||||||
|
|
||||||
console.log('Beat Tracker started.');
|
|
||||||
|
|
||||||
ipcMain.on('beat-tracking', async (_, arg) => {
|
|
||||||
if (arg === 'tap') {
|
|
||||||
// see here
|
|
||||||
(beat_tracker as any).tap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const createWindow = async () => {
|
const createWindow = async () => {
|
||||||
const RESOURCES_PATH = app.isPackaged
|
const RESOURCES_PATH = app.isPackaged
|
||||||
? path.join(process.resourcesPath, 'assets')
|
? path.join(process.resourcesPath, 'assets')
|
||||||
@ -99,5 +91,9 @@ app
|
|||||||
// dock icon is clicked and there are no other windows open.
|
// dock icon is clicked and there are no other windows open.
|
||||||
if (mainWindow === null) createWindow();
|
if (mainWindow === null) createWindow();
|
||||||
});
|
});
|
||||||
|
app.on('quit', () => {
|
||||||
|
clearInterval(mainLoop);
|
||||||
|
backend.close();
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.catch(console.log);
|
.catch(console.log);
|
||||||
|
@ -1,290 +0,0 @@
|
|||||||
import {
|
|
||||||
app,
|
|
||||||
Menu,
|
|
||||||
shell,
|
|
||||||
BrowserWindow,
|
|
||||||
MenuItemConstructorOptions,
|
|
||||||
} from 'electron';
|
|
||||||
|
|
||||||
interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions {
|
|
||||||
selector?: string;
|
|
||||||
submenu?: DarwinMenuItemConstructorOptions[] | Menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class MenuBuilder {
|
|
||||||
mainWindow: BrowserWindow;
|
|
||||||
|
|
||||||
constructor(mainWindow: BrowserWindow) {
|
|
||||||
this.mainWindow = mainWindow;
|
|
||||||
}
|
|
||||||
|
|
||||||
buildMenu(): Menu {
|
|
||||||
if (
|
|
||||||
process.env.NODE_ENV === 'development' ||
|
|
||||||
process.env.DEBUG_PROD === 'true'
|
|
||||||
) {
|
|
||||||
this.setupDevelopmentEnvironment();
|
|
||||||
}
|
|
||||||
|
|
||||||
const template =
|
|
||||||
process.platform === 'darwin'
|
|
||||||
? this.buildDarwinTemplate()
|
|
||||||
: this.buildDefaultTemplate();
|
|
||||||
|
|
||||||
const menu = Menu.buildFromTemplate(template);
|
|
||||||
Menu.setApplicationMenu(menu);
|
|
||||||
|
|
||||||
return menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
setupDevelopmentEnvironment(): void {
|
|
||||||
this.mainWindow.webContents.on('context-menu', (_, props) => {
|
|
||||||
const { x, y } = props;
|
|
||||||
|
|
||||||
Menu.buildFromTemplate([
|
|
||||||
{
|
|
||||||
label: 'Inspect element',
|
|
||||||
click: () => {
|
|
||||||
this.mainWindow.webContents.inspectElement(x, y);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]).popup({ window: this.mainWindow });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
buildDarwinTemplate(): MenuItemConstructorOptions[] {
|
|
||||||
const subMenuAbout: DarwinMenuItemConstructorOptions = {
|
|
||||||
label: 'Electron',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
label: 'About ElectronReact',
|
|
||||||
selector: 'orderFrontStandardAboutPanel:',
|
|
||||||
},
|
|
||||||
{ type: 'separator' },
|
|
||||||
{ label: 'Services', submenu: [] },
|
|
||||||
{ type: 'separator' },
|
|
||||||
{
|
|
||||||
label: 'Hide ElectronReact',
|
|
||||||
accelerator: 'Command+H',
|
|
||||||
selector: 'hide:',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Hide Others',
|
|
||||||
accelerator: 'Command+Shift+H',
|
|
||||||
selector: 'hideOtherApplications:',
|
|
||||||
},
|
|
||||||
{ label: 'Show All', selector: 'unhideAllApplications:' },
|
|
||||||
{ type: 'separator' },
|
|
||||||
{
|
|
||||||
label: 'Quit',
|
|
||||||
accelerator: 'Command+Q',
|
|
||||||
click: () => {
|
|
||||||
app.quit();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const subMenuEdit: DarwinMenuItemConstructorOptions = {
|
|
||||||
label: 'Edit',
|
|
||||||
submenu: [
|
|
||||||
{ label: 'Undo', accelerator: 'Command+Z', selector: 'undo:' },
|
|
||||||
{ label: 'Redo', accelerator: 'Shift+Command+Z', selector: 'redo:' },
|
|
||||||
{ type: 'separator' },
|
|
||||||
{ label: 'Cut', accelerator: 'Command+X', selector: 'cut:' },
|
|
||||||
{ label: 'Copy', accelerator: 'Command+C', selector: 'copy:' },
|
|
||||||
{ label: 'Paste', accelerator: 'Command+V', selector: 'paste:' },
|
|
||||||
{
|
|
||||||
label: 'Select All',
|
|
||||||
accelerator: 'Command+A',
|
|
||||||
selector: 'selectAll:',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const subMenuViewDev: MenuItemConstructorOptions = {
|
|
||||||
label: 'View',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
label: 'Reload',
|
|
||||||
accelerator: 'Command+R',
|
|
||||||
click: () => {
|
|
||||||
this.mainWindow.webContents.reload();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Toggle Full Screen',
|
|
||||||
accelerator: 'Ctrl+Command+F',
|
|
||||||
click: () => {
|
|
||||||
this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen());
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Toggle Developer Tools',
|
|
||||||
accelerator: 'Alt+Command+I',
|
|
||||||
click: () => {
|
|
||||||
this.mainWindow.webContents.toggleDevTools();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const subMenuViewProd: MenuItemConstructorOptions = {
|
|
||||||
label: 'View',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
label: 'Toggle Full Screen',
|
|
||||||
accelerator: 'Ctrl+Command+F',
|
|
||||||
click: () => {
|
|
||||||
this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen());
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const subMenuWindow: DarwinMenuItemConstructorOptions = {
|
|
||||||
label: 'Window',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
label: 'Minimize',
|
|
||||||
accelerator: 'Command+M',
|
|
||||||
selector: 'performMiniaturize:',
|
|
||||||
},
|
|
||||||
{ label: 'Close', accelerator: 'Command+W', selector: 'performClose:' },
|
|
||||||
{ type: 'separator' },
|
|
||||||
{ label: 'Bring All to Front', selector: 'arrangeInFront:' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const subMenuHelp: MenuItemConstructorOptions = {
|
|
||||||
label: 'Help',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
label: 'Learn More',
|
|
||||||
click() {
|
|
||||||
shell.openExternal('https://electronjs.org');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Documentation',
|
|
||||||
click() {
|
|
||||||
shell.openExternal(
|
|
||||||
'https://github.com/electron/electron/tree/main/docs#readme'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Community Discussions',
|
|
||||||
click() {
|
|
||||||
shell.openExternal('https://www.electronjs.org/community');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Search Issues',
|
|
||||||
click() {
|
|
||||||
shell.openExternal('https://github.com/electron/electron/issues');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const subMenuView =
|
|
||||||
process.env.NODE_ENV === 'development' ||
|
|
||||||
process.env.DEBUG_PROD === 'true'
|
|
||||||
? subMenuViewDev
|
|
||||||
: subMenuViewProd;
|
|
||||||
|
|
||||||
return [subMenuAbout, subMenuEdit, subMenuView, subMenuWindow, subMenuHelp];
|
|
||||||
}
|
|
||||||
|
|
||||||
buildDefaultTemplate() {
|
|
||||||
const templateDefault = [
|
|
||||||
{
|
|
||||||
label: '&File',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
label: '&Open',
|
|
||||||
accelerator: 'Ctrl+O',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '&Close',
|
|
||||||
accelerator: 'Ctrl+W',
|
|
||||||
click: () => {
|
|
||||||
this.mainWindow.close();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '&View',
|
|
||||||
submenu:
|
|
||||||
process.env.NODE_ENV === 'development' ||
|
|
||||||
process.env.DEBUG_PROD === 'true'
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
label: '&Reload',
|
|
||||||
accelerator: 'Ctrl+R',
|
|
||||||
click: () => {
|
|
||||||
this.mainWindow.webContents.reload();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Toggle &Full Screen',
|
|
||||||
accelerator: 'F11',
|
|
||||||
click: () => {
|
|
||||||
this.mainWindow.setFullScreen(
|
|
||||||
!this.mainWindow.isFullScreen()
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Toggle &Developer Tools',
|
|
||||||
accelerator: 'Alt+Ctrl+I',
|
|
||||||
click: () => {
|
|
||||||
this.mainWindow.webContents.toggleDevTools();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
label: 'Toggle &Full Screen',
|
|
||||||
accelerator: 'F11',
|
|
||||||
click: () => {
|
|
||||||
this.mainWindow.setFullScreen(
|
|
||||||
!this.mainWindow.isFullScreen()
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Help',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
label: 'Learn More',
|
|
||||||
click() {
|
|
||||||
shell.openExternal('https://electronjs.org');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Documentation',
|
|
||||||
click() {
|
|
||||||
shell.openExternal(
|
|
||||||
'https://github.com/electron/electron/tree/main/docs#readme'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Community Discussions',
|
|
||||||
click() {
|
|
||||||
shell.openExternal('https://www.electronjs.org/community');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Search Issues',
|
|
||||||
click() {
|
|
||||||
shell.openExternal('https://github.com/electron/electron/issues');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return templateDefault;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +1,5 @@
|
|||||||
const { contextBridge, ipcRenderer } = require('electron');
|
const { contextBridge, ipcRenderer } = require('electron');
|
||||||
const rust = require('rust_native_module');
|
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('electron', {
|
contextBridge.exposeInMainWorld('electron', {
|
||||||
ipcRenderer: {
|
ipcRenderer: ipcRenderer,
|
||||||
send(channel, data) {
|
|
||||||
const validChannels = ['beat-tracking'];
|
|
||||||
if (validChannels.includes(channel)) {
|
|
||||||
ipcRenderer.send(channel, data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
on(channel, func) {
|
|
||||||
const validChannels = ['tick', 'beat-tracking'];
|
|
||||||
if (validChannels.includes(channel)) {
|
|
||||||
// Deliberately strip event as it includes `sender`
|
|
||||||
ipcRenderer.on(channel, (event, ...args) => func(...args));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rustding: rust
|
|
||||||
});
|
});
|
||||||
|
15
boilerbloat/src/patterns/blackout.ts
Normal file
15
boilerbloat/src/patterns/blackout.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { MovingHeadState } from "rust_native_module";
|
||||||
|
|
||||||
|
export const blackout: Array<MovingHeadState> = []
|
||||||
|
|
||||||
|
for (let startAddress of [1, 15, 29, 43]) {
|
||||||
|
blackout.push({
|
||||||
|
startAddress: startAddress,
|
||||||
|
pan: 0,
|
||||||
|
tilt: 0,
|
||||||
|
brightness: { type: 'off' },
|
||||||
|
rgbw: [0, 0, 0, 0],
|
||||||
|
speed: 1,
|
||||||
|
reset: false,
|
||||||
|
})
|
||||||
|
}
|
@ -1,8 +1,13 @@
|
|||||||
import { MovingHeadState } from 'rust_native_module';
|
import { MovingHeadState } from 'rust_native_module';
|
||||||
import { Pattern, Time } from './proto';
|
import { Pattern, PatternOutput, Time } from './proto';
|
||||||
|
|
||||||
export class ChaserPattern implements Pattern {
|
export class ChaserPattern implements Pattern {
|
||||||
render(time: Time): Array<MovingHeadState> {
|
render(time: Time): PatternOutput {
|
||||||
|
|
||||||
|
if (time.beat_relative === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
let head_number = Math.ceil(time.beat_relative) % 4;
|
let head_number = Math.ceil(time.beat_relative) % 4;
|
||||||
|
|
||||||
let template: MovingHeadState = {
|
let template: MovingHeadState = {
|
||||||
|
@ -2,9 +2,11 @@ import { MovingHeadState } from "rust_native_module";
|
|||||||
|
|
||||||
export type Time = {
|
export type Time = {
|
||||||
absolute: number,
|
absolute: number,
|
||||||
beat_relative: number, //
|
beat_relative: number | null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type PatternOutput = Array<MovingHeadState> | null;
|
||||||
|
|
||||||
export interface Pattern {
|
export interface Pattern {
|
||||||
render(time: Time): Array<MovingHeadState>;
|
render(time: Time): PatternOutput;
|
||||||
}
|
}
|
||||||
|
1
boilerbloat/src/patterns/stage.ts
Normal file
1
boilerbloat/src/patterns/stage.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const startAddresses = [1, 15, 29, 43]
|
39
boilerbloat/src/patterns/test.ts
Normal file
39
boilerbloat/src/patterns/test.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { MovingHeadState } from "rust_native_module";
|
||||||
|
import { Pattern, PatternOutput, Time } from "./proto";
|
||||||
|
import { startAddresses } from "./stage";
|
||||||
|
|
||||||
|
export class TestPattern implements Pattern {
|
||||||
|
|
||||||
|
rgbw = [
|
||||||
|
[255, 0, 0, 0],
|
||||||
|
[0, 255, 0, 0],
|
||||||
|
[0, 0, 255, 0],
|
||||||
|
[0, 0, 0, 255],
|
||||||
|
]
|
||||||
|
|
||||||
|
render(time: Time): PatternOutput {
|
||||||
|
let t = time.absolute % this.rgbw.length;
|
||||||
|
let second = Math.floor(t);
|
||||||
|
let brightness = 1 - (t % 1);
|
||||||
|
|
||||||
|
return startAddresses.map((startAddress) => {
|
||||||
|
|
||||||
|
let [r, g, b, w] = this.rgbw[second];
|
||||||
|
|
||||||
|
let state: MovingHeadState = {
|
||||||
|
startAddress: startAddress,
|
||||||
|
pan: 0,
|
||||||
|
tilt: 0,
|
||||||
|
brightness: {
|
||||||
|
type: "dimmer",
|
||||||
|
value: brightness,
|
||||||
|
},
|
||||||
|
rgbw: [r, g, b, w],
|
||||||
|
speed: 1,
|
||||||
|
reset: false
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,6 +2,7 @@ import { MemoryRouter as Router, Switch, Route } from 'react-router-dom';
|
|||||||
import { IpcRenderer } from 'electron/renderer';
|
import { IpcRenderer } from 'electron/renderer';
|
||||||
|
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
const ipcRenderer = (window as any).electron.ipcRenderer as IpcRenderer;
|
const ipcRenderer = (window as any).electron.ipcRenderer as IpcRenderer;
|
||||||
|
|
||||||
@ -9,20 +10,35 @@ function tap() {
|
|||||||
ipcRenderer.send("beat-tracking", "tap");
|
ipcRenderer.send("beat-tracking", "tap");
|
||||||
}
|
}
|
||||||
|
|
||||||
const Hello = () => {
|
const Frontend: React.FC = () => {
|
||||||
|
|
||||||
return (
|
const [state, setState] = useState<any>();
|
||||||
|
|
||||||
|
const pollMain = async () => {
|
||||||
|
const reply = await ipcRenderer.invoke("poll");
|
||||||
|
setState(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(pollMain, 20);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
});
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div>
|
||||||
|
State: {state ? JSON.stringify(state) : "undef"}
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button onClick={tap}>Tap</button>
|
<button onClick={tap}>Tap</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/" component={Hello} />
|
<Route path="/" component={Frontend} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
|
8
rust_native_module/index.d.ts
vendored
8
rust_native_module/index.d.ts
vendored
@ -1,6 +1,6 @@
|
|||||||
declare module rust_native_module {
|
declare module rust_native_module {
|
||||||
type Result<T> =
|
type Result<T> =
|
||||||
{ type: "success" } & T
|
{ type: "success", value: T }
|
||||||
| { type: "error", message: string };
|
| { type: "error", message: string };
|
||||||
|
|
||||||
type Option<T> =
|
type Option<T> =
|
||||||
@ -26,8 +26,8 @@ declare module rust_native_module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OutputHandle = {
|
type OutputHandle = {
|
||||||
set: (heads: Array<MovingHeadState>) => Result<{}>,
|
set: (heads: Array<MovingHeadState>) => Result<never>,
|
||||||
close: () => Result<{}>,
|
close: () => Result<never>,
|
||||||
}
|
}
|
||||||
|
|
||||||
type BeatTrackerHandle = {
|
type BeatTrackerHandle = {
|
||||||
@ -36,7 +36,7 @@ declare module rust_native_module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function listPorts(): Array<string>;
|
function listPorts(): Array<string>;
|
||||||
function openOutput(): Result<OutputHandle>;
|
function openOutput(port: string): Result<OutputHandle>;
|
||||||
|
|
||||||
function getBeatTracker(): Result<BeatTrackerHandle>;
|
function getBeatTracker(): Result<BeatTrackerHandle>;
|
||||||
}
|
}
|
||||||
|
@ -11,19 +11,22 @@ impl Finalize for Metronome {}
|
|||||||
|
|
||||||
pub fn get_beat_tracker(mut cx: FunctionContext) -> JsResult<JsObject> {
|
pub fn get_beat_tracker(mut cx: FunctionContext) -> JsResult<JsObject> {
|
||||||
let obj = cx.empty_object();
|
let obj = cx.empty_object();
|
||||||
|
let value_obj = cx.empty_object();
|
||||||
|
|
||||||
|
let boxed_tracker = cx.boxed(RefCell::new(Metronome::new(Duration::from_secs(2))));
|
||||||
|
value_obj.set(&mut cx, "_rust_ptr", boxed_tracker)?;
|
||||||
|
|
||||||
|
let tap_function = JsFunction::new(&mut cx, tap)?;
|
||||||
|
value_obj.set(&mut cx, "tap", tap_function)?;
|
||||||
|
|
||||||
|
let get_progress_function = JsFunction::new(&mut cx, get_progress)?;
|
||||||
|
value_obj.set(&mut cx, "getProgress", get_progress_function)?;
|
||||||
|
|
||||||
|
obj.set(&mut cx, "value", value_obj)?;
|
||||||
|
|
||||||
let success_string = cx.string("success".to_string());
|
let success_string = cx.string("success".to_string());
|
||||||
obj.set(&mut cx, "type", success_string)?;
|
obj.set(&mut cx, "type", success_string)?;
|
||||||
|
|
||||||
let boxed_tracker = cx.boxed(RefCell::new(Metronome::new(Duration::from_secs(2))));
|
|
||||||
obj.set(&mut cx, "_rust_ptr", boxed_tracker)?;
|
|
||||||
|
|
||||||
let tap_function = JsFunction::new(&mut cx, tap)?;
|
|
||||||
obj.set(&mut cx, "tap", tap_function)?;
|
|
||||||
|
|
||||||
let get_progress_function = JsFunction::new(&mut cx, get_progress)?;
|
|
||||||
obj.set(&mut cx, "getProgress", get_progress_function)?;
|
|
||||||
|
|
||||||
Ok(obj)
|
Ok(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,31 +29,33 @@ pub fn open_output(mut cx: FunctionContext) -> JsResult<JsObject> {
|
|||||||
let path = path.value(&mut cx);
|
let path = path.value(&mut cx);
|
||||||
|
|
||||||
let obj = cx.empty_object();
|
let obj = cx.empty_object();
|
||||||
let success_string;
|
let value_obj = cx.empty_object();
|
||||||
|
|
||||||
match Controller::new(path) {
|
match Controller::new(path) {
|
||||||
Ok(controller) => {
|
Ok(controller) => {
|
||||||
success_string = cx.string("success");
|
let success_string = cx.string("success");
|
||||||
|
obj.set(&mut cx, "type", success_string)?;
|
||||||
|
|
||||||
let boxed_controller = cx.boxed(RefCell::new(controller));
|
let boxed_controller = cx.boxed(RefCell::new(controller));
|
||||||
obj.set(&mut cx, "_rust_ptr", boxed_controller)?;
|
value_obj.set(&mut cx, "_rust_ptr", boxed_controller)?;
|
||||||
|
|
||||||
let set_output_function = JsFunction::new(&mut cx, set_output)?;
|
let set_output_function = JsFunction::new(&mut cx, set_output)?;
|
||||||
obj.set(&mut cx, "set", set_output_function)?;
|
value_obj.set(&mut cx, "set", set_output_function)?;
|
||||||
|
|
||||||
let close_function = JsFunction::new(&mut cx, close_output)?;
|
let close_function = JsFunction::new(&mut cx, close_output)?;
|
||||||
obj.set(&mut cx, "close", close_function)?;
|
value_obj.set(&mut cx, "close", close_function)?;
|
||||||
|
|
||||||
|
obj.set(&mut cx, "value", value_obj)?;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
success_string = cx.string("error");
|
let success_string = cx.string("error");
|
||||||
|
obj.set(&mut cx, "type", success_string)?;
|
||||||
|
|
||||||
let error_message = cx.string(e.to_string());
|
let error_message = cx.string(e.to_string());
|
||||||
obj.set(&mut cx, "message", error_message)?;
|
obj.set(&mut cx, "message", error_message)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.set(&mut cx, "type", success_string)?;
|
|
||||||
|
|
||||||
Ok(obj)
|
Ok(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,11 +2,11 @@ const mod = require('.')
|
|||||||
|
|
||||||
const o = mod.openOutput("/dev/ttyUSB0")
|
const o = mod.openOutput("/dev/ttyUSB0")
|
||||||
|
|
||||||
if (o.type !== "success") {
|
if (o.type == 'success') {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const movingHeads = [
|
const output = o.value;
|
||||||
|
|
||||||
|
const movingHeads = [
|
||||||
{
|
{
|
||||||
startAddress: 1,
|
startAddress: 1,
|
||||||
pan: 0,
|
pan: 0,
|
||||||
@ -55,10 +55,12 @@ const movingHeads = [
|
|||||||
speed: 1,
|
speed: 1,
|
||||||
reset: false,
|
reset: false,
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
r = o.set(movingHeads);
|
let r = output.set(movingHeads);
|
||||||
console.log(r);
|
console.log(r);
|
||||||
|
|
||||||
o.close();
|
output.close();
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user