Initial commit
This commit is contained in:
commit
fd2f5df26b
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
.vscode
|
||||
logs
|
||||
VoicemeeterRemote.h
|
8894
package-lock.json
generated
Normal file
8894
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
70
package.json
Normal file
70
package.json
Normal file
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"name": "voicemeeter-remote-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"koffi": "^2.8.11",
|
||||
"@nestjs/common": "^10.0.0",
|
||||
"@nestjs/core": "^10.0.0",
|
||||
"@nestjs/platform-express": "^10.0.0",
|
||||
"reflect-metadata": "^0.2.0",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.5.2",
|
||||
"@nestjs/cli": "^10.0.0",
|
||||
"@nestjs/schematics": "^10.0.0",
|
||||
"@nestjs/testing": "^10.0.0",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jest": "^29.5.2",
|
||||
"@types/node": "^20.3.1",
|
||||
"@types/supertest": "^6.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||
"@typescript-eslint/parser": "^7.0.0",
|
||||
"eslint": "^8.42.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"jest": "^29.5.0",
|
||||
"prettier": "^3.0.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^7.0.0",
|
||||
"ts-jest": "^29.1.0",
|
||||
"ts-loader": "^9.4.3",
|
||||
"tsconfig-paths": "^4.2.0"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src/server",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"**/*.(t|j)s"
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
78
src/Logger.ts
Normal file
78
src/Logger.ts
Normal file
|
@ -0,0 +1,78 @@
|
|||
import path from "path";
|
||||
import { DEBUG } from "./variables";
|
||||
import fs from "fs";
|
||||
|
||||
export class Logger {
|
||||
public currentLogFile: string;
|
||||
public tag?: string;
|
||||
private get pathToLogFile(): string {
|
||||
return path.join(__dirname, "../log", this.currentLogFile);
|
||||
}
|
||||
|
||||
constructor(options?: { tag?: string }) {
|
||||
this.currentLogFile = this.createNewLogFile();
|
||||
if (options !== undefined) {
|
||||
this.tag = options.tag;
|
||||
}
|
||||
}
|
||||
|
||||
private createNewLogFile() {
|
||||
const date = new Date().toISOString().split("T");
|
||||
date[1] = date[1].split(".")[0].replaceAll(":", "-");
|
||||
const fileName = `${date}.log`;
|
||||
fs.writeFileSync(this.pathToLogFile, "");
|
||||
return fileName;
|
||||
}
|
||||
private toString(object: any) {
|
||||
if (typeof object === "object")
|
||||
return Object.prototype.toString.call(object);
|
||||
else if (Array.isArray(object)) return object.join();
|
||||
else if (typeof object === "number") return object.toString();
|
||||
else if (typeof object === "boolean") return object.toString();
|
||||
else if (typeof object === "string") return object;
|
||||
return "Cannot transfrom object to string";
|
||||
}
|
||||
private writeEntryToFile(
|
||||
options: {
|
||||
tag?: string;
|
||||
type: "log" | "error" | "warn" | "info";
|
||||
divider?: string;
|
||||
},
|
||||
...args: any
|
||||
) {
|
||||
const strings: string[] = [];
|
||||
for (const argument of args) {
|
||||
const string = this.toString(argument);
|
||||
strings.push(string);
|
||||
}
|
||||
const dateString = new Date().toLocaleString(undefined, {
|
||||
formatMatcher: "best fit",
|
||||
dateStyle: "short",
|
||||
timeStyle: "medium",
|
||||
});
|
||||
const divider = options.divider === undefined ? "\n\t" : options.divider;
|
||||
let string = `[${dateString}] `;
|
||||
if (this.tag !== undefined) string += `[LTAG: ${this.tag}`;
|
||||
if (options.tag !== undefined) string += ` TAG: ${options.tag}] `;
|
||||
else string += "] ";
|
||||
string += `[${options.type.toUpperCase()}] `;
|
||||
string += strings.join(divider);
|
||||
fs.appendFileSync(this.pathToLogFile, string);
|
||||
}
|
||||
public log(...args: any) {
|
||||
if (!DEBUG) console.log(...args);
|
||||
this.writeEntryToFile({ type: "log" }, ...args);
|
||||
}
|
||||
public error(...args: any) {
|
||||
if (!DEBUG) console.error(...args);
|
||||
this.writeEntryToFile({ type: "error" }, ...args);
|
||||
}
|
||||
public warn(...args: any) {
|
||||
if (!DEBUG) console.warn(...args);
|
||||
this.writeEntryToFile({ type: "warn" }, ...args);
|
||||
}
|
||||
public info(...args: any) {
|
||||
if (!DEBUG) console.info(...args);
|
||||
this.writeEntryToFile({ type: "info" }, ...args);
|
||||
}
|
||||
}
|
0
src/VMR/Bus.ts
Normal file
0
src/VMR/Bus.ts
Normal file
109
src/VMR/Control.ts
Normal file
109
src/VMR/Control.ts
Normal file
|
@ -0,0 +1,109 @@
|
|||
import { Remote } from "./Remote";
|
||||
import { validation } from "./decorators/validation";
|
||||
|
||||
export enum LoginReturnType {
|
||||
"OK" = 0,
|
||||
"OK but Voicemeeter Application not launched" = 1,
|
||||
"Cannot get client (unexpected)" = -1,
|
||||
"Unexpected login (logout was expected before)" = -2,
|
||||
}
|
||||
|
||||
export enum VoicemeeterType {
|
||||
Basic = 1,
|
||||
Banana = 2,
|
||||
Potato = 3,
|
||||
}
|
||||
|
||||
export class Control extends Remote {
|
||||
private static buses = {
|
||||
Potato: { total: 8, virtual: 3, physical: 5 },
|
||||
Banana: { total: 5, virtual: 2, physical: 3 },
|
||||
Basic: { total: 2, virtual: 1, physical: 1 },
|
||||
};
|
||||
private static strips = {
|
||||
Potato: { total: 8, virtual: 3, physical: 5 },
|
||||
Banana: { total: 8, virtual: 3, physical: 5 },
|
||||
Basic: { total: 8, virtual: 3, physical: 5 },
|
||||
};
|
||||
|
||||
public loggedIn: boolean = false;
|
||||
public parametersUpdated: boolean = false;
|
||||
private interval: NodeJS.Timeout | undefined;
|
||||
|
||||
@validation()
|
||||
public get voicemeeterType(): VoicemeeterType {
|
||||
const buffer = Buffer.alloc(512);
|
||||
this.run("VBVMR_GetVoicemeeterType", buffer);
|
||||
return buffer.readInt32LE();
|
||||
}
|
||||
|
||||
@validation()
|
||||
public get voicemeeterVersion(): number[] {
|
||||
const buffer = Buffer.alloc(512);
|
||||
this.run("VBVMR_GetVoicemeeterVersion", buffer);
|
||||
const version = buffer.readInt32LE();
|
||||
return [
|
||||
(version & 0xff000000) >> 24,
|
||||
(version & 0x00ff0000) >> 16,
|
||||
(version & 0x0000ff00) >> 8,
|
||||
version & 0x000000ff,
|
||||
];
|
||||
}
|
||||
|
||||
@validation()
|
||||
public get availableBuses() {
|
||||
return Control.buses[
|
||||
VoicemeeterType[this.voicemeeterType] as keyof typeof VoicemeeterType
|
||||
];
|
||||
}
|
||||
|
||||
@validation()
|
||||
public get availableStrips() {
|
||||
return Control.strips[
|
||||
VoicemeeterType[this.voicemeeterType] as keyof typeof VoicemeeterType
|
||||
];
|
||||
}
|
||||
|
||||
public minVersion(minVersion: number) {
|
||||
if (minVersion > this.voicemeeterType) throw new Error("Test");
|
||||
}
|
||||
|
||||
public login() {
|
||||
if (this.loggedIn) return;
|
||||
const createTimer = () => {
|
||||
this.interval = setInterval(() => {
|
||||
const result = this.run("VBVMR_IsParametersDirty");
|
||||
if (result !== 0 && result !== 1)
|
||||
throw new Error(
|
||||
`An error occurred while checking for updated parameters. DLL result code: ${result}`
|
||||
);
|
||||
const isUpdated = result === 1 ? true : false;
|
||||
if (this.parametersUpdated !== isUpdated && isUpdated)
|
||||
console.log(`Updated state`);
|
||||
this.parametersUpdated = isUpdated;
|
||||
}, 20);
|
||||
};
|
||||
if (this.interval === undefined) createTimer();
|
||||
else {
|
||||
clearInterval(this.interval);
|
||||
createTimer();
|
||||
}
|
||||
const result = this.run("VBVMR_Login") as LoginReturnType;
|
||||
if (result !== 0) throw new Error(`DLL result code: ${result}`);
|
||||
this.loggedIn = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public logout() {
|
||||
if (!this.loggedIn) return;
|
||||
if (this.interval !== undefined) {
|
||||
clearInterval(this.interval);
|
||||
this.interval = undefined;
|
||||
}
|
||||
const result = this.run("VBVMR_Logout") as number;
|
||||
if (result !== 0) throw new Error(`DLL result code: ${result}`);
|
||||
this.loggedIn = false;
|
||||
return result;
|
||||
}
|
||||
}
|
53
src/VMR/Remote.ts
Normal file
53
src/VMR/Remote.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { IKoffiLib, KoffiFunction, load } from "koffi";
|
||||
import { Parser } from "../utils/getFuncsNamesFromVRHFile";
|
||||
import path from "path";
|
||||
|
||||
export type RemoteFuncs = (typeof Remote.allowedFuncs)[number];
|
||||
|
||||
export class Remote {
|
||||
public static allowedFuncs = [
|
||||
"VBVMR_Login",
|
||||
"VBVMR_Logout",
|
||||
"VBVMR_GetVoicemeeterType",
|
||||
"VBVMR_SetParameterFloat",
|
||||
"VBVMR_SetParameterStringA",
|
||||
"VBVMR_SetParameters",
|
||||
"VBVMR_IsParametersDirty",
|
||||
"VBVMR_GetParameterFloat",
|
||||
"VBVMR_GetParameterStringA",
|
||||
"VBVMR_Output_GetDeviceNumber",
|
||||
"VBVMR_Input_GetDeviceNumber",
|
||||
"VBVMR_GetVoicemeeterVersion"
|
||||
] as const;
|
||||
|
||||
public dllFuncs = {} as Partial<
|
||||
Record<RemoteFuncs, KoffiFunction>
|
||||
>;
|
||||
public dll: IKoffiLib;
|
||||
|
||||
constructor(dllPath: string) {
|
||||
this.dll = load(dllPath);
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
const parser = new Parser(path.join(__dirname,"../", "VoicemeeterRemote.h"));
|
||||
const results = await parser.parse();
|
||||
for (const func of Remote.allowedFuncs) {
|
||||
const funcHeader = results.find((r) => r.name === func);
|
||||
if (funcHeader === undefined)
|
||||
throw new Error(`Allowed function not found in header file. Caused by function: ${func}`);
|
||||
this.dllFuncs[func] = this.dll.func(funcHeader.rawString);
|
||||
}
|
||||
}
|
||||
|
||||
public run(
|
||||
command: RemoteFuncs,
|
||||
...args: any
|
||||
) {
|
||||
if (!Remote.allowedFuncs.includes(command))
|
||||
throw new Error("Used function isn't listed in allowed");
|
||||
const func = this.dllFuncs[command];
|
||||
if(func === undefined) throw new Error("Executed function wasn't initialized");
|
||||
return func(...args)
|
||||
}
|
||||
}
|
124
src/VMR/Strip.ts
Normal file
124
src/VMR/Strip.ts
Normal file
|
@ -0,0 +1,124 @@
|
|||
import { VMObject } from "./VMObject";
|
||||
import { minType } from "./decorators/minVersion";
|
||||
import { validation } from "./decorators/validation";
|
||||
import { Control } from "./Control";
|
||||
|
||||
export type OutputBuses = Record<"A" | "B", Record<number, boolean>>;
|
||||
|
||||
export class Strip extends VMObject {
|
||||
@validation()
|
||||
public get Mono(): boolean {
|
||||
return this.getParamValueBoolean(
|
||||
this.buildParameterString("Mono")
|
||||
)[1];
|
||||
}
|
||||
public set Mono(v: boolean) {
|
||||
this.setParamValueBoolean(this.buildParameterString("Mono"), v);
|
||||
}
|
||||
|
||||
@validation()
|
||||
public get Mute(): boolean {
|
||||
return this.getParamValueBoolean(
|
||||
this.buildParameterString("Mute")
|
||||
)[1];
|
||||
}
|
||||
public set Mute(v: boolean) {
|
||||
this.setParamValueBoolean(this.buildParameterString("Mute"), v);
|
||||
}
|
||||
|
||||
@validation()
|
||||
public get Solo(): boolean {
|
||||
return this.getParamValueBoolean(
|
||||
this.buildParameterString("Solo")
|
||||
)[1];
|
||||
}
|
||||
public set Solor(v: boolean) {
|
||||
this.setParamValueBoolean(this.buildParameterString("Solo"), v);
|
||||
}
|
||||
|
||||
@validation()
|
||||
public get MuteCenter(): boolean {
|
||||
return this.getParamValueBoolean(this.buildParameterString("MC"))[1];
|
||||
}
|
||||
public set MuteCenter(v: boolean) {
|
||||
this.setParamValueBoolean(this.buildParameterString("MC"), v);
|
||||
}
|
||||
|
||||
@validation()
|
||||
public get Gain(): number {
|
||||
return this.getParamValueNumber(this.buildParameterString("Gain"))[1];
|
||||
}
|
||||
public set Gain(v: number) {
|
||||
this.setParamValueNumber(this.buildParameterString("Gain"), v);
|
||||
}
|
||||
|
||||
@minType(3)
|
||||
public get GainLayers(): number[] {
|
||||
const buses: number[] = [];
|
||||
for (let i = 0; i < this.control.availableBuses.total; i++) {
|
||||
buses.push(
|
||||
this.getParamValueNumber(
|
||||
this.buildParameterString("GainLayer", i)
|
||||
)[1]
|
||||
);
|
||||
}
|
||||
return buses;
|
||||
}
|
||||
public set GainLayers(changedBuses: (undefined | number)[]) {
|
||||
let script = "";
|
||||
for (let i = 0; i < changedBuses.length; i++) {
|
||||
const gainForBus = changedBuses[i];
|
||||
script += `${this.buildParameterString(
|
||||
"GainLayer", i,
|
||||
)} = ${gainForBus};`;
|
||||
}
|
||||
this.setParameters(script);
|
||||
}
|
||||
|
||||
@validation()
|
||||
public get outputBuses() {
|
||||
const buses: OutputBuses = { A: {}, B: {} };
|
||||
for (let i = 0; i < this.control.availableBuses.physical; i++) {
|
||||
buses["A"][i + 1] = this.getParamValueBoolean(
|
||||
this.buildParameterString(`A${i + 1}`)
|
||||
)[1];
|
||||
}
|
||||
for (let i = 0; i < this.control.availableBuses.virtual; i++) {
|
||||
buses["B"][i + 1] = this.getParamValueBoolean(
|
||||
this.buildParameterString(`B${i + 1}`)
|
||||
)[1];
|
||||
}
|
||||
return buses;
|
||||
}
|
||||
public set outputBuses(changedBuses: Partial<OutputBuses>) {
|
||||
let script = "";
|
||||
if (changedBuses.A !== undefined)
|
||||
for (const busId in changedBuses.A) {
|
||||
if (Object.prototype.hasOwnProperty.call(changedBuses.A, busId)) {
|
||||
const element = changedBuses.A[busId];
|
||||
script += `${this.buildParameterString(`A${busId}`)} = ${
|
||||
element ? "1" : "0"
|
||||
};`;
|
||||
}
|
||||
}
|
||||
if (changedBuses.B !== undefined)
|
||||
for (const busId in changedBuses.B) {
|
||||
if (Object.prototype.hasOwnProperty.call(changedBuses.B, busId)) {
|
||||
const outputToBus = changedBuses.B[busId];
|
||||
script += `${this.buildParameterString(`B${busId}`)} = ${
|
||||
outputToBus ? "1" : "0"
|
||||
};`;
|
||||
}
|
||||
}
|
||||
this.setParameters(script);
|
||||
}
|
||||
|
||||
constructor(control: Control, public id: number) {
|
||||
// control.validation();
|
||||
super(control, "Strip", id);
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
console.log("Nothing to initialize");
|
||||
}
|
||||
}
|
91
src/VMR/VMObject.ts
Normal file
91
src/VMR/VMObject.ts
Normal file
|
@ -0,0 +1,91 @@
|
|||
import { Control } from "./Control";
|
||||
import { RemoteFuncs } from "./Remote";
|
||||
|
||||
export type ObjectType = "Strip" | "Bus";
|
||||
|
||||
export abstract class VMObject {
|
||||
constructor(
|
||||
public control: Control,
|
||||
public objectType: ObjectType,
|
||||
public objectId?: number
|
||||
) {
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
abstract initialize(): void;
|
||||
|
||||
protected buildParameterString(...path: (string | number)[]) {
|
||||
let string = `${this.objectType}${
|
||||
this.objectId !== undefined ? `[${this.objectId}]` : ""
|
||||
}`;
|
||||
for (let pathId = 0; pathId < path.length; pathId++) {
|
||||
const parameter = path[pathId];
|
||||
let idForParameter: number | undefined = undefined;
|
||||
if (typeof path[pathId + 1] === "number") {
|
||||
idForParameter = path[pathId + 1] as number;
|
||||
pathId++;
|
||||
}
|
||||
string += `.${parameter}${
|
||||
idForParameter !== undefined ? `[${idForParameter}]` : ""
|
||||
}`;
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
protected getParamValueString(parameter: string): [number, string] {
|
||||
const buffer = Buffer.alloc(512);
|
||||
const result = this.control.run(
|
||||
"VBVMR_GetParameterStringA",
|
||||
parameter,
|
||||
buffer
|
||||
);
|
||||
return [result, buffer.toString()];
|
||||
}
|
||||
protected getParamValueBoolean(parameter: string): [number, boolean] {
|
||||
const buffer = Buffer.alloc(512);
|
||||
const result = this.control.run(
|
||||
"VBVMR_GetParameterStringA",
|
||||
parameter,
|
||||
buffer
|
||||
);
|
||||
return [result, buffer.subarray(0, 5).toString().startsWith("1")];
|
||||
}
|
||||
protected getParamValueNumber(parameter: string): [number, number] {
|
||||
const buffer = Buffer.alloc(512);
|
||||
const result = this.control.run(
|
||||
"VBVMR_GetParameterFloat",
|
||||
parameter,
|
||||
buffer
|
||||
);
|
||||
return [result, buffer.readFloatLE()];
|
||||
}
|
||||
|
||||
protected setParamValueString(parameter: string, value: string): number {
|
||||
const result = this.control.run(
|
||||
"VBVMR_SetParameterStringA",
|
||||
parameter,
|
||||
value
|
||||
);
|
||||
return result;
|
||||
}
|
||||
protected setParamValueBoolean(parameter: string, value: boolean): number {
|
||||
const result = this.control.run(
|
||||
"VBVMR_SetParameterFloat",
|
||||
parameter,
|
||||
value ? 1 : 0
|
||||
);
|
||||
return result;
|
||||
}
|
||||
protected setParamValueNumber(parameter: string, value: number): number {
|
||||
const result = this.control.run(
|
||||
"VBVMR_GetParameterFloat",
|
||||
parameter,
|
||||
value
|
||||
);
|
||||
return result;
|
||||
}
|
||||
protected setParameters(script: string): number {
|
||||
const result = this.control.run("VBVMR_SetParameters", script);
|
||||
return result;
|
||||
}
|
||||
}
|
23
src/decorators/minVersion.ts
Normal file
23
src/decorators/minVersion.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { validate } from "./validation";
|
||||
import { Control, VoicemeeterType } from "../Control";
|
||||
|
||||
export function minType(minType: VoicemeeterType) {
|
||||
return (object: any, name: string, descriptor: PropertyDescriptor) => {
|
||||
const keys = ["set", "get", "value"] as (keyof typeof descriptor)[];
|
||||
for (const key of keys) {
|
||||
if (descriptor[key] === undefined) continue;
|
||||
const originalFunction = descriptor[key];
|
||||
descriptor[key] = function (this: Control, ...args: any[]) {
|
||||
validate.bind(this)();
|
||||
if (this.voicemeeterType < minType)
|
||||
throw new Error(
|
||||
`Used property/method with name: ${name} is not supported by this type of Voicemeeter. Your type: ${
|
||||
VoicemeeterType[this.voicemeeterType]
|
||||
} Minimal required type is ${VoicemeeterType[minType]}`
|
||||
);
|
||||
return originalFunction.apply(this, args);
|
||||
};
|
||||
}
|
||||
return descriptor;
|
||||
};
|
||||
}
|
26
src/decorators/validation.ts
Normal file
26
src/decorators/validation.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { Control } from "../Control";
|
||||
import { Strip } from "../Strip";
|
||||
|
||||
type validateThisType = Control | Strip;
|
||||
|
||||
export function validate(this: validateThisType) {
|
||||
let isValid = true;
|
||||
if (this instanceof Control && !this.loggedIn) isValid = false;
|
||||
else if (this instanceof Strip && !this.control.loggedIn) isValid = false;
|
||||
if (!isValid) throw new Error("Communication pipe isn't open");
|
||||
}
|
||||
|
||||
export function validation() {
|
||||
return (object: any, name: string, descriptor: PropertyDescriptor) => {
|
||||
const keys = ["set", "get", "value"] as (keyof typeof descriptor)[];
|
||||
for (const key of keys) {
|
||||
if (descriptor[key] === undefined) continue;
|
||||
const originalFunction = descriptor[key];
|
||||
descriptor[key] = function (this: validateThisType, ...args: any[]) {
|
||||
validate.bind(this)();
|
||||
return originalFunction.apply(this, args);
|
||||
};
|
||||
}
|
||||
return descriptor;
|
||||
};
|
||||
}
|
30
src/main.ts
Normal file
30
src/main.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { Control, VoicemeeterType } from "./VMR/Control";
|
||||
import { Logger } from "./Logger";
|
||||
|
||||
let control: Control;
|
||||
let interval: NodeJS.Timeout;
|
||||
const logger = new Logger({});
|
||||
|
||||
function exit() {
|
||||
clearInterval(interval);
|
||||
control.logout();
|
||||
logger.log("Logout");
|
||||
process.exit();
|
||||
}
|
||||
|
||||
const exitEvents = [
|
||||
{ name: "SIGINT" },
|
||||
{ name: "SIGABRT" },
|
||||
{ name: "SIGKILL" },
|
||||
{ name: "SIGUSR1" },
|
||||
{ name: "SIGUSR2" },
|
||||
{ name: "beforeExit" },
|
||||
{ name: "exit" },
|
||||
{ name: "uncaughtException" },
|
||||
{ name: "unhandledRejection" },
|
||||
] as { name: NodeJS.Signals }[];
|
||||
|
||||
for (const event of exitEvents) {
|
||||
process.on(event.name, exit.bind(null));
|
||||
}
|
||||
|
12
src/utils/fillRemoteClass.ts
Normal file
12
src/utils/fillRemoteClass.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import fs from "fs/promises";
|
||||
import { Parser } from "./getFuncsNamesFromVRHFile";
|
||||
|
||||
async function main() {
|
||||
const parser = new Parser("./VoicemeeterRemote.h");
|
||||
console.log(await parser.parse());
|
||||
const fileContent = await fs.readFile("./src/Remote.ts", {encoding: "utf-8"});
|
||||
// fileContent.
|
||||
|
||||
}
|
||||
|
||||
main();
|
11
src/utils/getEnvVariable.ts
Normal file
11
src/utils/getEnvVariable.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
export function getEnvVariable(
|
||||
name: string,
|
||||
type?: "string" | "boolean" | "number"
|
||||
) {
|
||||
const variable = process.env[name];
|
||||
if (process.env[name] === undefined) return undefined;
|
||||
if (type === undefined) return variable;
|
||||
if (type === "boolean") return new Boolean(variable).valueOf();
|
||||
else if (type === "string") return variable;
|
||||
else if (type === "number") return new Number(variable).valueOf();
|
||||
}
|
56
src/utils/getFuncsNamesFromVRHFile.ts
Normal file
56
src/utils/getFuncsNamesFromVRHFile.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { promises as fs } from "fs";
|
||||
|
||||
interface Parameter {
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface ParsedFunction {
|
||||
name: string;
|
||||
params: Parameter[];
|
||||
returnType: string;
|
||||
rawString: string;
|
||||
}
|
||||
|
||||
export class Parser {
|
||||
constructor(private filePath: string) {}
|
||||
public async parse() {
|
||||
const data = await fs.readFile(this.filePath, { encoding: "utf-8" });
|
||||
const lines = data.split("\n").map((l) => l.trim());
|
||||
let i = 0;
|
||||
let isComment = false;
|
||||
const functionArray = [] as ParsedFunction[];
|
||||
while (++i < lines.length) {
|
||||
const line = lines[i];
|
||||
if (!isComment && line.includes("/*")) {
|
||||
isComment = true;
|
||||
continue;
|
||||
}
|
||||
if (isComment && line.includes("*/")) {
|
||||
isComment = false;
|
||||
continue;
|
||||
}
|
||||
if (line.includes("typedef") || line.includes("//") || line.includes("#"))
|
||||
continue;
|
||||
if (line.includes(" __stdcall ") && line.endsWith(");")) {
|
||||
const split = line
|
||||
.substring(0, line.length - ");".length)
|
||||
.split(" __stdcall ");
|
||||
|
||||
const returnType = split[0];
|
||||
const [functionName, parametersString] = split[1].split("(");
|
||||
let params = parametersString
|
||||
.split(", ")
|
||||
.map((p) => p.split(/\s(?!\*)/));
|
||||
functionArray.push({
|
||||
rawString: line,
|
||||
name: functionName,
|
||||
params: params.map((p) => ({ name: p[1], type: p[0] })),
|
||||
returnType,
|
||||
});
|
||||
}
|
||||
}
|
||||
return functionArray;
|
||||
}
|
||||
}
|
||||
|
8
src/variables.ts
Normal file
8
src/variables.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { getEnvVariable } from "./utils/getEnvVariable";
|
||||
|
||||
export const DEBUG = getEnvVariable("DEBUG", "boolean");
|
||||
export const VOICEMEETER_REMOTE_DLL = getEnvVariable(
|
||||
"VOICEMEETER_REMOTE_DLL",
|
||||
"string"
|
||||
);
|
||||
export const API_PORT = getEnvVariable("API_PORT", "number");
|
109
tsconfig.json
Normal file
109
tsconfig.json
Normal file
|
@ -0,0 +1,109 @@
|
|||
{
|
||||
"include": ["src/**/*", "src/utils/**/*"],
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "ES2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
"experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||
// "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user