From 5b2df1c3f6b59d4de8dca9c434f951ef3a51f2f2 Mon Sep 17 00:00:00 2001 From: Maksym Date: Tue, 30 Jul 2024 02:40:52 +0200 Subject: [PATCH] modified: .gitignore modified: src/Logger.ts modified: src/VMR/Bus.ts modified: src/VMR/Control.ts new file: src/VMR/Device.ts modified: src/VMR/Remote.ts modified: src/VMR/Strip.ts modified: src/decorators/minVersion.ts modified: src/decorators/validation.ts modified: src/main.ts new file: src/utils/Error.ts modified: src/utils/getEnvVariable.ts modified: src/variables.ts --- .gitignore | 1 + src/Logger.ts | 71 +++++++++++++++--------- src/VMR/Bus.ts | 58 +++++++++++++++++++ src/VMR/Control.ts | 104 +++++++++++++++++++++++++++++------ src/VMR/Device.ts | 9 +++ src/VMR/Remote.ts | 11 ++-- src/VMR/Strip.ts | 31 ++++------- src/decorators/minVersion.ts | 5 +- src/decorators/validation.ts | 7 ++- src/main.ts | 19 ++++--- src/utils/Error.ts | 8 +++ src/utils/getEnvVariable.ts | 16 ++++-- src/variables.ts | 2 +- 13 files changed, 258 insertions(+), 84 deletions(-) create mode 100644 src/VMR/Device.ts create mode 100644 src/utils/Error.ts diff --git a/.gitignore b/.gitignore index 3275c96..79fcef1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules .vscode logs VoicemeeterRemote.h +.hintrc \ No newline at end of file diff --git a/src/Logger.ts b/src/Logger.ts index aeac260..55a1521 100644 --- a/src/Logger.ts +++ b/src/Logger.ts @@ -2,25 +2,34 @@ import path from "path"; import { DEBUG } from "./variables"; import fs from "fs"; +export type LogOptions = Omit["0"], "type">; + export class Logger { public currentLogFile: string; public tag?: string; - private get pathToLogFile(): string { - return path.join(__dirname, "../log", this.currentLogFile); + public divider: string = "\n"; + private get pathToLogFolder(): string { + return path.join(__dirname, "../logs"); + } + private get pathToCurrentLog(): string { + return path.join(__dirname, "../logs", this.currentLogFile); } - constructor(options?: { tag?: string }) { + constructor(options?: { tag?: string; divider?: string }) { this.currentLogFile = this.createNewLogFile(); - if (options !== undefined) { - this.tag = options.tag; - } + if (options === undefined) return; + if (options.tag !== undefined) this.tag = options.tag; + if (options.divider !== undefined) this.divider = options.divider; } - + private createNewLogFile() { const date = new Date().toISOString().split("T"); date[1] = date[1].split(".")[0].replaceAll(":", "-"); - const fileName = `${date}.log`; - fs.writeFileSync(this.pathToLogFile, ""); + const fileName = `${date.join("-")}.log`; + const newLogPath = path.join(this.pathToLogFolder, fileName); + if (!fs.existsSync(newLogPath)) fs.writeFileSync(newLogPath, ""); + const latestLogPath = path.join(this.pathToLogFolder, "./latest.log"); + if (fs.existsSync(latestLogPath)) fs.writeFileSync(latestLogPath, ""); return fileName; } private toString(object: any) { @@ -32,11 +41,14 @@ export class Logger { else if (typeof object === "string") return object; return "Cannot transfrom object to string"; } - private writeEntryToFile( + private appendTextToLog(content: string) { + fs.appendFileSync(this.pathToCurrentLog, content); + fs.appendFileSync(path.join(this.pathToLogFolder, "./latest.log"), content); + } + private writeEntry( options: { tag?: string; type: "log" | "error" | "warn" | "info"; - divider?: string; }, ...args: any ) { @@ -50,29 +62,38 @@ export class Logger { 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 += "] "; + const tags = [ + ["LTAG", this.tag], + ["TAG", options.tag], + ].filter((i) => i[1] !== undefined); + if (tags.length > 0) { + string += "["; + for (const [tagName, tag] of tags) { + string += `${tagName}: ${tag}`; + } + string += "] "; + } string += `[${options.type.toUpperCase()}] `; - string += strings.join(divider); - fs.appendFileSync(this.pathToLogFile, string); + string += strings.join(this.divider + "\t"); + string += this.divider; + this.appendTextToLog(string); } - public log(...args: any) { + public log(options?: LogOptions, ...args: any) { if (!DEBUG) console.log(...args); - this.writeEntryToFile({ type: "log" }, ...args); + this.writeEntry({ type: "log", ...options }, ...args); } - public error(...args: any) { + public error(options?: LogOptions, ...args: any) { if (!DEBUG) console.error(...args); - this.writeEntryToFile({ type: "error" }, ...args); + this.writeEntry({ type: "error", ...options }, ...args); } - public warn(...args: any) { + public warn(options?: LogOptions, ...args: any) { if (!DEBUG) console.warn(...args); - this.writeEntryToFile({ type: "warn" }, ...args); + this.writeEntry({ type: "warn", ...options }, ...args); } - public info(...args: any) { + public info(options?: LogOptions, ...args: any) { if (!DEBUG) console.info(...args); - this.writeEntryToFile({ type: "info" }, ...args); + this.writeEntry({ type: "info", ...options }, ...args); } } +export const logger = new Logger({}); diff --git a/src/VMR/Bus.ts b/src/VMR/Bus.ts index e69de29..b77e031 100644 --- a/src/VMR/Bus.ts +++ b/src/VMR/Bus.ts @@ -0,0 +1,58 @@ +import { VMObject } from "./VMObject"; +import { minType } from "../decorators/minVersion"; +import { validation } from "../decorators/validation"; +import { Control } from "./Control"; + +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 EQEnabled(): boolean { + return this.getParamValueBoolean(this.buildParameterString("EQ", "on"))[1]; + } + public set EQEnabled(v: boolean) { + this.setParamValueBoolean(this.buildParameterString("EQ", "on"), v); + } + + @validation() + public get EQVariant(): boolean { + return this.getParamValueBoolean(this.buildParameterString("EQ", "AB"))[1]; + } + public set EQVariant(v: boolean) { + this.setParamValueBoolean(this.buildParameterString("EQ", "AB"), v); + } + + @validation() + public get Gain(): number { + return this.getParamValueNumber(this.buildParameterString("Gain"))[1]; + } + public set Gain(v: number) { + this.setParamValueNumber(this.buildParameterString("Gain"), v); + } + + constructor( + control: Control, + public id: number + ) { + // control.validation(); + super(control, "Bus", id); + } + + initialize(): void { + console.log("Nothing to initialize"); + } +} diff --git a/src/VMR/Control.ts b/src/VMR/Control.ts index 2501788..97abf8b 100644 --- a/src/VMR/Control.ts +++ b/src/VMR/Control.ts @@ -1,5 +1,8 @@ import { Remote } from "./Remote"; -import { validation } from "./decorators/validation"; +import { validation } from "../decorators/validation"; +import { Device } from "./Device"; +import { throwError } from "../utils/Error"; +import { logger } from "../Logger"; export enum LoginReturnType { "OK" = 0, @@ -16,15 +19,45 @@ export enum VoicemeeterType { 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 }, + Potato: { virtual: 3, physical: 5 }, + Banana: { virtual: 2, physical: 3 }, + Basic: { 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 }, + Potato: { virtual: 3, physical: 5 }, + Banana: { virtual: 2, physical: 3 }, + Basic: { virtual: 1, physical: 2 }, }; + private static totalBuses = (() => { + const totalBuses = {} as (typeof Control)["totalBuses"]; + for (const voicemeeterVersion in Control.buses) { + if ( + Object.prototype.hasOwnProperty.call(Control.buses, voicemeeterVersion) + ) { + const element = + Control.buses[voicemeeterVersion as keyof (typeof Control)["buses"]]; + totalBuses[voicemeeterVersion as keyof (typeof Control)["buses"]] = + element.physical + element.virtual; + } + } + return totalBuses; + })() as Record; + private static totalStrips = (() => { + const totalStrips = {} as (typeof Control)["totalStrips"]; + for (const voicemeeterVersion in Control.strips) { + if ( + Object.prototype.hasOwnProperty.call(Control.strips, voicemeeterVersion) + ) { + const element = + Control.strips[ + voicemeeterVersion as keyof (typeof Control)["strips"] + ]; + totalStrips[voicemeeterVersion as keyof (typeof Control)["strips"]] = + element.physical + element.virtual; + } + } + return totalStrips; + })() as Record; public loggedIn: boolean = false; public parametersUpdated: boolean = false; @@ -52,20 +85,55 @@ export class Control extends Remote { @validation() public get availableBuses() { - return Control.buses[ - VoicemeeterType[this.voicemeeterType] as keyof typeof VoicemeeterType - ]; + return { + total: + Control.totalBuses[ + VoicemeeterType[this.voicemeeterType] as keyof typeof VoicemeeterType + ], + ...Control.buses[ + VoicemeeterType[this.voicemeeterType] as keyof typeof VoicemeeterType + ], + }; } @validation() public get availableStrips() { - return Control.strips[ - VoicemeeterType[this.voicemeeterType] as keyof typeof VoicemeeterType - ]; + return { + total: + Control.totalStrips[ + VoicemeeterType[this.voicemeeterType] as keyof typeof VoicemeeterType + ], + ...Control.strips[ + VoicemeeterType[this.voicemeeterType] as keyof typeof VoicemeeterType + ], + }; } - public minVersion(minVersion: number) { - if (minVersion > this.voicemeeterType) throw new Error("Test"); + @validation() + public get availableInputDevices(): Device[] { + const numberOfDevices: number = this.run("VBVMR_Input_GetDeviceNumber"); + const devices: Device[] = []; + for (let i = 0; i < numberOfDevices; i++) { + let type, deviceName, hardwareId; + const result = this.run( + "VBVMR_Input_GetDeviceDescA", + i, + type, + deviceName, + hardwareId + ); + logger.log({}, type, deviceName, hardwareId); + + if (result !== 0) throwError("Error happened on VM side"); + } + return devices; + } + @validation() + public get availableOutputDevices(): Device[] { + const numberOfDevices: number = this.run("VBVMR_Output_GetDeviceNumber"); + const devices: Device[] = []; + for (let i = 0; i < numberOfDevices; i++) {} + return devices; } public login() { @@ -74,7 +142,7 @@ export class Control extends Remote { this.interval = setInterval(() => { const result = this.run("VBVMR_IsParametersDirty"); if (result !== 0 && result !== 1) - throw new Error( + throwError( `An error occurred while checking for updated parameters. DLL result code: ${result}` ); const isUpdated = result === 1 ? true : false; @@ -89,7 +157,7 @@ export class Control extends Remote { createTimer(); } const result = this.run("VBVMR_Login") as LoginReturnType; - if (result !== 0) throw new Error(`DLL result code: ${result}`); + if (result !== 0) throwError(`DLL result code: ${result}`); this.loggedIn = true; return result; @@ -102,7 +170,7 @@ export class Control extends Remote { this.interval = undefined; } const result = this.run("VBVMR_Logout") as number; - if (result !== 0) throw new Error(`DLL result code: ${result}`); + if (result !== 0) throwError(`DLL result code: ${result}`); this.loggedIn = false; return result; } diff --git a/src/VMR/Device.ts b/src/VMR/Device.ts new file mode 100644 index 0000000..74332d7 --- /dev/null +++ b/src/VMR/Device.ts @@ -0,0 +1,9 @@ +export type DeviceDriver = "ASIO" | "MME" | "WDM" | "KS"; + +export class Device { + constructor( + public driver: DeviceDriver, + public isInput: boolean, + public name: string + ) {} +} diff --git a/src/VMR/Remote.ts b/src/VMR/Remote.ts index 7ef0285..4b85d44 100644 --- a/src/VMR/Remote.ts +++ b/src/VMR/Remote.ts @@ -1,6 +1,7 @@ import { IKoffiLib, KoffiFunction, load } from "koffi"; import { Parser } from "../utils/getFuncsNamesFromVRHFile"; import path from "path"; +import { throwError } from "../utils/Error"; export type RemoteFuncs = (typeof Remote.allowedFuncs)[number]; @@ -17,7 +18,9 @@ export class Remote { "VBVMR_GetParameterStringA", "VBVMR_Output_GetDeviceNumber", "VBVMR_Input_GetDeviceNumber", - "VBVMR_GetVoicemeeterVersion" + "VBVMR_GetVoicemeeterVersion", + "VBVMR_Input_GetDeviceDescA", + "VBVMR_Output_GetDeviceDescA" ] as const; public dllFuncs = {} as Partial< @@ -35,7 +38,7 @@ export class Remote { 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}`); + return throwError(`Allowed function not found in header file. Caused by function: ${func}`); this.dllFuncs[func] = this.dll.func(funcHeader.rawString); } } @@ -45,9 +48,9 @@ export class Remote { ...args: any ) { if (!Remote.allowedFuncs.includes(command)) - throw new Error("Used function isn't listed in allowed"); + throwError("Used function isn't listed in allowed"); const func = this.dllFuncs[command]; - if(func === undefined) throw new Error("Executed function wasn't initialized"); + if(func === undefined)return throwError("Executed function wasn't initialized"); return func(...args) } } diff --git a/src/VMR/Strip.ts b/src/VMR/Strip.ts index bb46526..7f5d892 100644 --- a/src/VMR/Strip.ts +++ b/src/VMR/Strip.ts @@ -1,6 +1,6 @@ import { VMObject } from "./VMObject"; -import { minType } from "./decorators/minVersion"; -import { validation } from "./decorators/validation"; +import { minType } from "../decorators/minVersion"; +import { validation } from "../decorators/validation"; import { Control } from "./Control"; export type OutputBuses = Record<"A" | "B", Record>; @@ -8,9 +8,7 @@ export type OutputBuses = Record<"A" | "B", Record>; export class Strip extends VMObject { @validation() public get Mono(): boolean { - return this.getParamValueBoolean( - this.buildParameterString("Mono") - )[1]; + return this.getParamValueBoolean(this.buildParameterString("Mono"))[1]; } public set Mono(v: boolean) { this.setParamValueBoolean(this.buildParameterString("Mono"), v); @@ -18,9 +16,7 @@ export class Strip extends VMObject { @validation() public get Mute(): boolean { - return this.getParamValueBoolean( - this.buildParameterString("Mute") - )[1]; + return this.getParamValueBoolean(this.buildParameterString("Mute"))[1]; } public set Mute(v: boolean) { this.setParamValueBoolean(this.buildParameterString("Mute"), v); @@ -28,11 +24,9 @@ export class Strip extends VMObject { @validation() public get Solo(): boolean { - return this.getParamValueBoolean( - this.buildParameterString("Solo") - )[1]; + return this.getParamValueBoolean(this.buildParameterString("Solo"))[1]; } - public set Solor(v: boolean) { + public set Solo(v: boolean) { this.setParamValueBoolean(this.buildParameterString("Solo"), v); } @@ -57,9 +51,7 @@ export class Strip extends VMObject { const buses: number[] = []; for (let i = 0; i < this.control.availableBuses.total; i++) { buses.push( - this.getParamValueNumber( - this.buildParameterString("GainLayer", i) - )[1] + this.getParamValueNumber(this.buildParameterString("GainLayer", i))[1] ); } return buses; @@ -68,9 +60,7 @@ export class Strip extends VMObject { let script = ""; for (let i = 0; i < changedBuses.length; i++) { const gainForBus = changedBuses[i]; - script += `${this.buildParameterString( - "GainLayer", i, - )} = ${gainForBus};`; + script += `${this.buildParameterString("GainLayer", i)} = ${gainForBus};`; } this.setParameters(script); } @@ -113,7 +103,10 @@ export class Strip extends VMObject { this.setParameters(script); } - constructor(control: Control, public id: number) { + constructor( + control: Control, + public id: number + ) { // control.validation(); super(control, "Strip", id); } diff --git a/src/decorators/minVersion.ts b/src/decorators/minVersion.ts index 48ea2ed..ca192e0 100644 --- a/src/decorators/minVersion.ts +++ b/src/decorators/minVersion.ts @@ -1,5 +1,6 @@ import { validate } from "./validation"; -import { Control, VoicemeeterType } from "../Control"; +import { Control, VoicemeeterType } from "../VMR/Control"; +import { throwError } from "../utils/Error"; export function minType(minType: VoicemeeterType) { return (object: any, name: string, descriptor: PropertyDescriptor) => { @@ -10,7 +11,7 @@ export function minType(minType: VoicemeeterType) { descriptor[key] = function (this: Control, ...args: any[]) { validate.bind(this)(); if (this.voicemeeterType < minType) - throw new Error( + return throwError( `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]}` diff --git a/src/decorators/validation.ts b/src/decorators/validation.ts index 5e134f0..3a621a6 100644 --- a/src/decorators/validation.ts +++ b/src/decorators/validation.ts @@ -1,5 +1,6 @@ -import { Control } from "../Control"; -import { Strip } from "../Strip"; +import { Control } from "../VMR/Control"; +import { Strip } from "../VMR/Strip"; +import { throwError } from "../utils/Error"; type validateThisType = Control | Strip; @@ -7,7 +8,7 @@ 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"); + if (!isValid) return throwError("Communication pipe isn't open"); } export function validation() { diff --git a/src/main.ts b/src/main.ts index 87ab92e..8a60866 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,14 +1,17 @@ import { Control, VoicemeeterType } from "./VMR/Control"; -import { Logger } from "./Logger"; +import { logger, Logger } from "./Logger"; +import { VOICEMEETER_REMOTE_DLL } from "./variables"; +import { throwError } from "./utils/Error"; -let control: Control; +if (VOICEMEETER_REMOTE_DLL === undefined) + throwError("The path for VM Remote dll is not specified"); +let control: Control = new Control(VOICEMEETER_REMOTE_DLL!); let interval: NodeJS.Timeout; -const logger = new Logger({}); -function exit() { +function exit(eventName: (typeof exitEvents)[0]) { clearInterval(interval); - control.logout(); - logger.log("Logout"); + if (control !== undefined && control.loggedIn) control.logout(); + logger.log({ tag: `Signal: ${eventName.name}` }, "Logout"); process.exit(); } @@ -19,12 +22,12 @@ const exitEvents = [ { 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)); + process.on(event.name, () => exit(event)); } +logger.log({}, control.availableInputDevices); diff --git a/src/utils/Error.ts b/src/utils/Error.ts new file mode 100644 index 0000000..72712fa --- /dev/null +++ b/src/utils/Error.ts @@ -0,0 +1,8 @@ +import { logger, Logger } from "../Logger"; +import { DEBUG } from "../variables"; + +export function throwError(message: string) { + logger.error({ tag: "CRIT" }, message); + if (DEBUG) throw new Error(message); + process.exit(-1); +} diff --git a/src/utils/getEnvVariable.ts b/src/utils/getEnvVariable.ts index d134983..e0c140d 100644 --- a/src/utils/getEnvVariable.ts +++ b/src/utils/getEnvVariable.ts @@ -1,7 +1,13 @@ -export function getEnvVariable( - name: string, - type?: "string" | "boolean" | "number" -) { +import { logger } from "../Logger"; + +export type EnvironmentVariableType = "string" | "boolean" | "number"; + +function getEnvVariable(name: string, type: "string"): string | undefined; +function getEnvVariable(name: string, type: "boolean"): boolean | undefined; +function getEnvVariable(name: string, type: "number"): number | undefined; +function getEnvVariable(name: string, type: undefined): any | undefined; + +function getEnvVariable(name: string, type?: EnvironmentVariableType) { const variable = process.env[name]; if (process.env[name] === undefined) return undefined; if (type === undefined) return variable; @@ -9,3 +15,5 @@ export function getEnvVariable( else if (type === "string") return variable; else if (type === "number") return new Number(variable).valueOf(); } + +export { getEnvVariable }; diff --git a/src/variables.ts b/src/variables.ts index 295bb31..f953639 100644 --- a/src/variables.ts +++ b/src/variables.ts @@ -1,6 +1,6 @@ import { getEnvVariable } from "./utils/getEnvVariable"; -export const DEBUG = getEnvVariable("DEBUG", "boolean"); +export const DEBUG = getEnvVariable("NODE_ENV", "string") !== "production"; export const VOICEMEETER_REMOTE_DLL = getEnvVariable( "VOICEMEETER_REMOTE_DLL", "string"