voicemeeter-connector
Version:
A Connector to use the Voicemeeter API
1 lines • 53.9 kB
Source Map (JSON)
{"version":3,"file":"index.modern.mjs","sources":["../src/lib/constants.ts","../src/lib/dll-handler.ts","../src/lib/voicemeeter-connector.ts","../src/index.ts"],"sourcesContent":["import koffi from \"koffi\";\r\n\r\nexport const InterfaceTypes = {\r\n strip: 0,\r\n bus: 1,\r\n};\r\n\r\nexport enum StripProperties {\r\n Mono = \"Mono\",\r\n Mute = \"Mute\",\r\n Solo = \"Solo\",\r\n MC = \"MC\",\r\n Gain = \"Gain\",\r\n Pan_x = \"Pan_x\",\r\n Pan_y = \"Pan_y\",\r\n Color_x = \"Color_x\",\r\n Color_y = \"Color_y\",\r\n fx_x = \"fx_x\",\r\n fx_y = \"fx_y\",\r\n Audibility = \"Audibility\",\r\n Comp = \"Comp\",\r\n Gate = \"Gate\",\r\n EqGain1 = \"EqGain1\",\r\n EqGain2 = \"EqGain2\",\r\n EqGain3 = \"EqGain3\",\r\n Label = \"Label\",\r\n A1 = \"A1\",\r\n A2 = \"A2\",\r\n A3 = \"A3\",\r\n A4 = \"A4\",\r\n A5 = \"A5\",\r\n B1 = \"B1\",\r\n B2 = \"B2\",\r\n B3 = \"B3\",\r\n FadeTo = \"FadeTo\",\r\n}\r\nexport enum BusProperties {\r\n Mono = \"Mono\",\r\n Mute = \"Mute\",\r\n EQ = \"EQ.on\",\r\n Gain = \"Gain\",\r\n NormalMode = \"mode.normal\",\r\n AmixMode = \"mode.Amix\",\r\n BmixMode = \"mode.Bmix\",\r\n RepeatMode = \"mode.Repeat\",\r\n CompositeMode = \"mode.Composite\",\r\n FadeTo = \"FadeTo\",\r\n Label = \"Label\",\r\n}\r\n\r\nexport enum MacroButtonModes {\r\n DEFAULT = 0x00_00_00_00,\r\n STATEONLY = 0x00_00_00_02,\r\n TRIGGER = 0x00_00_00_03,\r\n COLOR = 0x00_00_00_04,\r\n}\r\n\r\nexport const InitialAudioCallbackState = {\r\n pointer: null,\r\n awaitUnregister: [],\r\n ended: true,\r\n};\r\n\r\nexport enum AudioCallbackModes {\r\n INPUT = 1,\r\n OUTPUT = 2,\r\n MAIN = 4,\r\n}\r\n\r\nexport enum AudioCallbackCommands {\r\n STARTING = 1,\r\n ENDING = 2,\r\n CHANGE = 3,\r\n BUFFER_IN = 10,\r\n BUFFER_OUT = 11,\r\n BUFFER_MAIN = 20,\r\n}\r\n\r\nexport const AudioInfoStruct = koffi.struct(\"VBVMR_T_AUDIOINFO\", {\r\n samplerate: \"long\",\r\n nbSamplePerFrame: \"long\",\r\n});\r\n\r\nexport const AudioBufferStruct = koffi.struct(\"VBVMR_T_AUDIOBUFFER\", {\r\n audiobuffer_sr: \"long\",\r\n audiobuffer_nbs: \"long\",\r\n audiobuffer_nbi: \"long\",\r\n audiobuffer_nbo: \"long\",\r\n audiobuffer_r: koffi.array(\"float*\", 128),\r\n audiobuffer_w: koffi.array(\"float*\", 128),\r\n});\r\n","import Registry from \"winreg\";\r\n\r\nconst DLLHandler = {\r\n getDLLPath: async (): Promise<any> => {\r\n const regKey = new Registry({\r\n hive: Registry.HKLM,\r\n key: String.raw`\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\VB:Voicemeeter {17359A74-1236-5467}`,\r\n });\r\n return new Promise((resolve) => {\r\n regKey.values((err: any, items: any) => {\r\n if (err) {\r\n throw new Error(err);\r\n }\r\n const unistallerPath = items.find((i: any) => i.name === \"UninstallString\").value;\r\n const fileNameIndex = unistallerPath.lastIndexOf(\"\\\\\");\r\n resolve(unistallerPath.slice(0, fileNameIndex));\r\n });\r\n });\r\n },\r\n};\r\n\r\nexport default DLLHandler;\r\n","/* eslint-disable no-control-regex */\r\nimport koffi from \"koffi\";\r\n\r\nimport {\r\n AudioCallbackBuffer,\r\n AudioCallbackEvent,\r\n AudioCallbackFunction,\r\n AudioCallbackInfo,\r\n AudioCallbackState,\r\n Device,\r\n VBVMR_T_AUDIOBUFFER,\r\n VBVMR_T_AUDIOINFO,\r\n VMLibrary,\r\n VoiceMeeterTypes,\r\n} from \"../types/voicemeeter-types\";\r\nimport {\r\n AudioBufferStruct,\r\n AudioCallbackCommands,\r\n AudioCallbackModes,\r\n AudioInfoStruct,\r\n BusProperties,\r\n InitialAudioCallbackState,\r\n MacroButtonModes,\r\n StripProperties,\r\n} from \"./constants\";\r\nimport DLLHandler from \"./dll-handler\";\r\n\r\n/**\r\n * @ignore\r\n */\r\nlet libVM: VMLibrary;\r\n/**\r\n * @ignore\r\n */\r\nlet audioCallbackProtoPointer: koffi.IKoffiCType;\r\n/**\r\n * @ignore\r\n */\r\nlet instance: Voicemeeter;\r\n\r\nexport default class Voicemeeter {\r\n /**\r\n * Initializes the voice meeter dll connection.\r\n * This call is neccessary to use the api. It returns a promise with a VoiceMeeter instance\r\n */\r\n static init = async (): Promise<Voicemeeter> => {\r\n const dllPath = await DLLHandler.getDLLPath();\r\n\r\n return new Promise((resolve: (instance: Voicemeeter) => any) => {\r\n if (!instance) {\r\n instance = new Voicemeeter();\r\n }\r\n const lib = koffi.load(`${dllPath}/VoicemeeterRemote64.dll`);\r\n\r\n libVM = {\r\n VBVMR_Login: lib.func(\"long __stdcall VBVMR_Login(void)\"),\r\n VBVMR_Logout: lib.func(\"long __stdcall VBVMR_Logout(void)\"),\r\n VBVMR_RunVoicemeeter: lib.func(\"long __stdcall VBVMR_RunVoicemeeter(long mode)\"),\r\n VBVMR_IsParametersDirty: lib.func(\"long __stdcall VBVMR_IsParametersDirty(void)\"),\r\n VBVMR_GetLevel: lib.func(\"long __stdcall VBVMR_GetLevel(long type, long channel, _Out_ float* value)\"),\r\n VBVMR_GetParameterFloat: lib.func(\"long __stdcall VBVMR_GetParameterFloat(const char* param, _Out_ float* value)\"),\r\n VBVMR_GetParameterStringA: lib.func(\"long __stdcall VBVMR_GetParameterStringA(const char* param, _Out_ char* value)\"),\r\n VBVMR_SetParameters: lib.func(\"long __stdcall VBVMR_SetParameters(const char* param)\"),\r\n VBVMR_Output_GetDeviceNumber: lib.func(\"long __stdcall VBVMR_Output_GetDeviceNumber(void)\"),\r\n VBVMR_Output_GetDeviceDescA: lib.func(\r\n \"long __stdcall VBVMR_Output_GetDeviceDescA(long index, _Out_ long* type, _Out_ char* name, _Out_ char* hardwareId)\"\r\n ),\r\n VBVMR_Input_GetDeviceNumber: lib.func(\"long __stdcall VBVMR_Input_GetDeviceNumber(void)\"),\r\n VBVMR_Input_GetDeviceDescA: lib.func(\r\n \"long __stdcall VBVMR_Input_GetDeviceDescA(long index, long* type, char* name, char* hardwareId)\"\r\n ),\r\n VBVMR_GetVoicemeeterType: lib.func(\"long __stdcall VBVMR_GetVoicemeeterType(_Out_ long* type)\"),\r\n VBVMR_GetVoicemeeterVersion: lib.func(\"long __stdcall VBVMR_GetVoicemeeterVersion(_Out_ long* version)\"),\r\n VBVMR_MacroButton_IsDirty: lib.func(\"long __stdcall VBVMR_MacroButton_IsDirty(void)\"),\r\n VBVMR_MacroButton_GetStatus: lib.func(\r\n \"long __stdcall VBVMR_MacroButton_GetStatus(long nuLogicalButton, _Out_ float* pValue, long bitmode)\"\r\n ),\r\n VBVMR_MacroButton_SetStatus: lib.func(\r\n \"long __stdcall VBVMR_MacroButton_SetStatus(long nuLogicalButton, float fValue, long bitmode)\"\r\n ),\r\n VBVMR_AudioCallbackRegister: lib.func(\r\n \"long __stdcall VBVMR_AudioCallbackRegister(long mode, void* audioCallback, void* lpUser, char* szClientName)\"\r\n ),\r\n VBVMR_AudioCallbackStart: lib.func(\"long __stdcall VBVMR_AudioCallbackStart(void)\"),\r\n VBVMR_AudioCallbackStop: lib.func(\"long __stdcall VBVMR_AudioCallbackStop(void)\"),\r\n VBVMR_AudioCallbackUnregister: lib.func(\"long __stdcall VBVMR_AudioCallbackUnregister(void* audioCallback)\"),\r\n };\r\n\r\n audioCallbackProtoPointer = koffi.pointer(\r\n koffi.proto(\"long __stdcall AudioCallback(void* lpUser, long nCommand, void* lpData, long nnn)\")\r\n );\r\n\r\n instance.isInitialised = true;\r\n resolve(instance);\r\n });\r\n };\r\n\r\n private isInitialised = false;\r\n private isConnected = false;\r\n private outputDevices: Device[] = [];\r\n private inputDevices: Device[] = [];\r\n private version = \"\";\r\n private type: VoiceMeeterTypes;\r\n private eventPool = [] as Array<() => void>;\r\n private stringParameters = [\"Label\", \"FadeTo\", \"FadeBy\", \"AppGain\", \"AppMute\", \"name\", \"ip\"];\r\n private timerInterval: NodeJS.Timeout;\r\n private audioCallbackStates: Record<AudioCallbackModes, AudioCallbackState> = {\r\n [AudioCallbackModes.MAIN]: { ...InitialAudioCallbackState },\r\n [AudioCallbackModes.INPUT]: { ...InitialAudioCallbackState },\r\n [AudioCallbackModes.OUTPUT]: { ...InitialAudioCallbackState },\r\n };\r\n private awaitAudioCallbackEvents: { start: Array<() => void>; stop: Array<() => void> } = {\r\n start: [],\r\n stop: [],\r\n };\r\n\r\n /**\r\n * Starts a connection to VoiceMeeter\r\n */\r\n public connect = (): { success: boolean; message: string; code: number } | never => {\r\n if (!this.isInitialised) {\r\n throw new Error(\"Await the initialisation before connect\");\r\n }\r\n if (this.isConnected) {\r\n // If already connected, still return success status instead of throwing\r\n return { success: true, message: \"Already connected.\", code: 0 };\r\n }\r\n\r\n const loginResult = libVM.VBVMR_Login();\r\n\r\n switch (loginResult) {\r\n case 0: {\r\n // Complete success, Voicemeeter is running\r\n this.isConnected = true;\r\n this.type = this.getVoicemeeterType();\r\n this.version = this.getVoicemeeterVersion();\r\n this.timerInterval = setInterval(this.checkPropertyChange, 10);\r\n return { success: true, message: \"Successfully connected to VoiceMeeter (VM Running).\", code: 0 };\r\n }\r\n case 1: {\r\n // Connected successfully but Voicemeeter application is not running\r\n // Pipe is established, can wait for VM to start\r\n this.isConnected = true;\r\n this.type = undefined;\r\n this.version = \"Unknown\";\r\n this.timerInterval = setInterval(this.checkPropertyChange, 10);\r\n return { success: true, message: \"Connected to VoiceMeeter API (VM App Not Running).\", code: 1 };\r\n }\r\n case -1: {\r\n // Failed to get client (unexpected error) - throw error\r\n this.isConnected = false;\r\n throw new Error(\"VoiceMeeter connection failed: Unable to get client (Unexpected error -1).\");\r\n }\r\n case -2: {\r\n // Unexpected login (logout should have been executed first) - throw error\r\n this.isConnected = false;\r\n throw new Error(\"VoiceMeeter connection failed: Unexpected login (Expected logout first -2).\");\r\n }\r\n default: {\r\n // Unknown return value - throw error\r\n this.isConnected = false;\r\n throw new Error(`VoiceMeeter connection failed with unknown error code: ${loginResult}.`);\r\n }\r\n }\r\n };\r\n\r\n /**\r\n * Getter $outputDevices\r\n * @return {Device[] }\r\n */\r\n public get $outputDevices(): Device[] {\r\n return this.outputDevices;\r\n }\r\n\r\n /**\r\n * Getter $inputDevices\r\n * @return {Device[] }\r\n */\r\n public get $inputDevices(): Device[] {\r\n return this.inputDevices;\r\n }\r\n\r\n /**\r\n * Getter $version\r\n * @return {string }\r\n */\r\n public get $version(): string {\r\n return this.version;\r\n }\r\n\r\n /**\r\n * Getter $type\r\n * @return {VoiceMeeterTypes}\r\n */\r\n public get $type(): VoiceMeeterTypes {\r\n return this.type;\r\n }\r\n\r\n /**\r\n * Terminates the connection to VoiceMeeter\r\n */\r\n public disconnect = () => {\r\n if (!this.isConnected) {\r\n throw new Error(\"Not connected \");\r\n }\r\n try {\r\n this.unregisterAllAudioCallbacks().catch(() => {});\r\n if (libVM.VBVMR_Logout() === 0) {\r\n clearInterval(this.timerInterval);\r\n this.isConnected = false;\r\n return;\r\n }\r\n throw new Error(\"Disconnect failed\");\r\n } catch {\r\n throw new Error(\"Disconnect failed\");\r\n }\r\n };\r\n\r\n /**\r\n * Updates all input and ouput devices\r\n */\r\n public updateDeviceList = () => {\r\n if (!this.isConnected) {\r\n throw new Error(\"Not connected \");\r\n }\r\n this.outputDevices = [];\r\n this.inputDevices = [];\r\n const outputDeviceNumber = libVM.VBVMR_Output_GetDeviceNumber();\r\n for (let i = 0; i < outputDeviceNumber; i++) {\r\n const hardwareIdPtr = Buffer.alloc(256);\r\n const namePtr = Buffer.alloc(256);\r\n const typePtr = [0];\r\n\r\n libVM.VBVMR_Output_GetDeviceDescA(i, typePtr, namePtr, hardwareIdPtr);\r\n this.outputDevices.push({\r\n name: namePtr.toString().replaceAll(/\\u0000+$/g, \"\"),\r\n hardwareId: hardwareIdPtr.toString().replaceAll(/\\u0000+$/g, \"\"),\r\n type: typePtr[0],\r\n });\r\n }\r\n\r\n const inputDeviceNumber = libVM.VBVMR_Input_GetDeviceNumber();\r\n for (let i = 0; i < inputDeviceNumber; i++) {\r\n const hardwareIdPtr = Buffer.alloc(256);\r\n const namePtr = Buffer.alloc(256);\r\n const typePtr = [0];\r\n\r\n libVM.VBVMR_Input_GetDeviceDescA(i, typePtr, namePtr, hardwareIdPtr);\r\n this.inputDevices.push({\r\n name: namePtr.toString().replaceAll(/\\u0000+$/g, \"\"),\r\n hardwareId: hardwareIdPtr.toString().replaceAll(/\\u0000+$/g, \"\"),\r\n type: typePtr[0],\r\n });\r\n }\r\n };\r\n\r\n /**\r\n * Returns wheter a parameter has been changed\r\n */\r\n public isParametersDirty = () => {\r\n return libVM.VBVMR_IsParametersDirty();\r\n };\r\n\r\n /**\r\n * Gets a bus parameter.\r\n * @param {number} index Index of the bus\r\n * @param {BusProperties} property Property which should be get\r\n */\r\n\r\n public getBusParameter = (index: number, property: BusProperties) => {\r\n return this.getParameter(\"Bus\", index, property);\r\n };\r\n\r\n /**\r\n * Gets a strip parameter\r\n * @param {number} index Index of the strip\r\n * @param {StripProperties} property Property which should be get\r\n */\r\n public getStripParameter = (index: number, property: StripProperties) => {\r\n return this.getParameter(\"Strip\", index, property);\r\n };\r\n\r\n /**\r\n * Sets a parameter of a strip.\r\n * @param {number} index Strip number\r\n * @param {StripProperties} property Propertyname which should be changed\r\n * @param {any} value Property value\r\n */\r\n public setStripParameter = (index: number, property: StripProperties, value: any) => {\r\n return this.setParameter(\"Strip\", index, property, value);\r\n };\r\n\r\n /**\r\n * Sets a parameter of a bus.\r\n * @param {number} index Bus number\r\n * @param {StripProperties} property Propertyname which should be changed\r\n * @param {any} value Property value\r\n */\r\n public setBusParameter = (index: number, property: BusProperties, value: any) => {\r\n return this.setParameter(\"Bus\", index, property, value);\r\n };\r\n\r\n /**\r\n * @param {()=>any} fn Function which should be called if something changes\r\n */\r\n public attachChangeEvent = (fn: () => any) => {\r\n this.eventPool.push(fn);\r\n };\r\n /**\r\n * @param parameterName Name of the parameter that should be get\r\n * @returns {any} Parameter value\r\n */\r\n public getOption = (parameterName: string) => {\r\n if (!this.isConnected) {\r\n throw new Error(\"Not correct connected \");\r\n }\r\n // Some parameters return string values and require some post-processing, this checks for those parameters\r\n if (this.stringParameters.some((str) => parameterName.includes(str))) {\r\n const strPtr = Buffer.alloc(512);\r\n libVM.VBVMR_GetParameterStringA(parameterName, strPtr);\r\n return [...String.fromCharCode.apply(null, strPtr)]\r\n .filter((e: string) => {\r\n return e !== \"\\0\";\r\n })\r\n .join(\"\");\r\n }\r\n const valuePtr = [0];\r\n libVM.VBVMR_GetParameterFloat(parameterName, valuePtr);\r\n return valuePtr[0];\r\n };\r\n /**\r\n * Sets an option.\r\n * @param {string} option Option to set\r\n */\r\n public setOption = (option: string) => {\r\n const script = Buffer.alloc(option.length + 1);\r\n script.fill(0).write(option);\r\n libVM.VBVMR_SetParameters(script);\r\n return new Promise((resolve) => {\r\n setTimeout(resolve, 200);\r\n });\r\n };\r\n\r\n /**\r\n * Checks if any macro button has unsaved changes\r\n */\r\n public isMacroButtonDirty = () => {\r\n return libVM.VBVMR_MacroButton_IsDirty();\r\n };\r\n\r\n /**\r\n * Gets the status of a specific macro button\r\n * @param {number} buttonIndex - The index of the macro button\r\n * @param {number} [bitmode=0] - Bit mode parameter (optional, defaults to MacroButtonModes.DEFAULT)\r\n * @returns {number} The current status value of the macro button\r\n * @throws {Error} Throws an error if failed to get the button status\r\n */\r\n public getMacroButtonStatus = (buttonIndex: number, bitmode: number = MacroButtonModes.DEFAULT): number => {\r\n const valuePtr = [0];\r\n const result = libVM.VBVMR_MacroButton_GetStatus(buttonIndex, valuePtr, bitmode);\r\n if (result === 0) {\r\n return valuePtr[0];\r\n }\r\n throw new Error(`Failed to get macro button ${buttonIndex} status`);\r\n };\r\n\r\n /**\r\n * Sets the status of a specific macro button\r\n * @param {number} buttonIndex - The index of the macro button\r\n * @param {number} value - The status value to set\r\n * @param {number} [bitmode=0] - Bit mode parameter (optional, defaults to MacroButtonModes.DEFAULT)\r\n * @throws {Error} Throws an error if failed to set the button status\r\n */\r\n public setMacroButtonStatus = (buttonIndex: number, value: number, bitmode: number = MacroButtonModes.DEFAULT): void => {\r\n const result = libVM.VBVMR_MacroButton_SetStatus(buttonIndex, value, bitmode);\r\n if (result !== 0) {\r\n throw new Error(`Failed to set macro button ${buttonIndex} status`);\r\n }\r\n };\r\n\r\n /**\r\n * The amount of input channels per voicemeeter version\r\n */\r\n public static inputChannelCountMap: Record<Exclude<VoiceMeeterTypes, undefined>, number> = {\r\n voicemeeter: 12,\r\n voicemeeterBanana: 22,\r\n voicemeeterPotato: 34,\r\n };\r\n\r\n /**\r\n * The amount of output channels per voicemeeter version\r\n */\r\n public static outputChannelCountMap: Record<Exclude<VoiceMeeterTypes, undefined>, number> = {\r\n voicemeeter: 16,\r\n voicemeeterBanana: 40,\r\n voicemeeterPotato: 64,\r\n };\r\n\r\n /**\r\n * Registers an audio callback function to process/change real time audio stream data from Voicemeeter.\r\n * @description For more detailed info about the audio callback system, see the Voicemeeter Remote API pdf and VoicemeeterRemote.h on https://github.com/vburel2018/Voicemeeter-SDK\r\n * @param {AudioCallbackModes} mode - The audio callback type. See {@link AudioCallbackModes}\r\n * @param {string} clientName - Name of the application registering the callback (max 64 ASCII chars)\r\n * @param {AudioCallbackFunction} callback - Function called for each audio stream event.\r\n * The first argument is an Error if one occurs, otherwise null. The second argument provides the decoded audio event.\r\n * @param {Object} [config] - Optional configuration object.\r\n * @param {Buffer} [config.lpUser] - Optional user context pointer passed to the callback.\r\n * @param {boolean} [config.restartOnChangedStream=true] - If true, automatically restarts the callback when the audio stream changes. Defaults to true\r\n * @throws {Error} Throws an error if the callback is already registered, or if registration fails.\r\n */\r\n public registerAudioCallback = (\r\n mode: AudioCallbackModes,\r\n clientName: string,\r\n callback: AudioCallbackFunction,\r\n config?: {\r\n lpUser?: Buffer;\r\n restartOnChangedStream?: boolean;\r\n }\r\n ): void => {\r\n if (this.audioCallbackStates[mode].pointer !== null) {\r\n throw new Error(`Audio callback for \"${mode}\" is already registered.`);\r\n }\r\n\r\n this.audioCallbackStates[mode].pointer = koffi.register(\r\n (lpUser: Buffer, nCommand: number, lpData: unknown, nnn: number): number => {\r\n let audioCallbackEvent: AudioCallbackEvent | undefined;\r\n\r\n try {\r\n switch (nCommand) {\r\n case AudioCallbackCommands.STARTING: {\r\n this.audioCallbackStates[mode].ended = false;\r\n audioCallbackEvent = { lpUser, nnn, command: nCommand, data: this.convertToAudioCallbackInfo(lpData) };\r\n this.resolveAudioCallbackEvent(\"start\");\r\n break;\r\n }\r\n case AudioCallbackCommands.ENDING: {\r\n this.audioCallbackStates[mode].ended = true;\r\n audioCallbackEvent = { lpUser, nnn, command: nCommand, data: this.convertToAudioCallbackInfo(lpData) };\r\n this.resolveAudioCallbackEvent(\"stop\");\r\n break;\r\n }\r\n case AudioCallbackCommands.CHANGE: {\r\n audioCallbackEvent = { lpUser, nnn, command: nCommand, data: this.convertToAudioCallbackInfo(lpData) };\r\n /*\r\n CHANGE occurs when the main audio stream, sample rate or buffer size has changed.\r\n the audio callback is stopped by voicemeeter when this happens,\r\n See: https://github.com/vburel2018/Voicemeeter-SDK/blob/3be2c1c36563afbd6df3da8436406c77d2cc1f10/example0/vmr_client.c#L718\r\n This is just an exact copy of the implentation there.\r\n */\r\n if (config?.restartOnChangedStream ?? true) {\r\n setTimeout(() => {\r\n this.startAudioCallback().catch((unknownError) => {\r\n const error = this.convertToErrorObject(unknownError);\r\n error.message = \"Failed to restart callback after changed stream: \" + error.message;\r\n callback(error, audioCallbackEvent);\r\n });\r\n }, 50);\r\n }\r\n break;\r\n }\r\n case AudioCallbackCommands.BUFFER_IN:\r\n case AudioCallbackCommands.BUFFER_OUT:\r\n case AudioCallbackCommands.BUFFER_MAIN: {\r\n audioCallbackEvent = { lpUser, nnn, command: nCommand, data: this.convertToAudioCallbackBuffer(lpData) };\r\n break;\r\n }\r\n default: {\r\n return 0;\r\n }\r\n }\r\n\r\n // If the stream has stopped, check if any unregisters are scheduled to resolve their promises\r\n if (\r\n nCommand === AudioCallbackCommands.ENDING &&\r\n this.audioCallbackStates[mode].awaitUnregister.length > 0 &&\r\n this.audioCallbackStates[mode].pointer !== null\r\n ) {\r\n koffi.unregister(this.audioCallbackStates[mode].pointer);\r\n this.audioCallbackStates[mode].pointer = null;\r\n while (this.audioCallbackStates[mode].awaitUnregister.length > 0) {\r\n const resolve = this.audioCallbackStates[mode].awaitUnregister.shift();\r\n if (resolve !== undefined) {\r\n resolve();\r\n }\r\n }\r\n }\r\n } catch (unknownError: unknown) {\r\n const error = this.convertToErrorObject(unknownError);\r\n callback(error, audioCallbackEvent);\r\n return 0;\r\n }\r\n callback(null, audioCallbackEvent);\r\n return 0;\r\n },\r\n audioCallbackProtoPointer\r\n );\r\n\r\n const clientNamePtr = Buffer.alloc(64);\r\n clientNamePtr.write(clientName);\r\n\r\n const result = libVM.VBVMR_AudioCallbackRegister(\r\n mode,\r\n this.audioCallbackStates[mode].pointer,\r\n config?.lpUser ?? null,\r\n clientNamePtr\r\n );\r\n\r\n switch (result) {\r\n case 0: {\r\n return;\r\n }\r\n case -1: {\r\n throw new Error(\"Failed to register audio callback\");\r\n }\r\n // VoicemeeterRemote.h says this case should have result = 1, but in reality this appears to be -2\r\n case -2: {\r\n const outClientName = clientNamePtr.toString().replace(\"/\\u0000+$/g\", \"\");\r\n throw new Error(`Audio callback already registered by: ${outClientName}`);\r\n }\r\n default: {\r\n throw new Error(`Unexpected result registering audio callback: ${result}`);\r\n }\r\n }\r\n };\r\n\r\n /**\r\n * Starts the audio stream to the audio callback.\r\n * @returns {Promise<void>} Resolves when started, rejects with Error if failed.\r\n */\r\n public startAudioCallback = (): Promise<void> => {\r\n return new Promise((resolve, reject) => {\r\n const result = libVM.VBVMR_AudioCallbackStart();\r\n if (result === 0) {\r\n this.awaitAudioCallbackEvents.start.push(() => resolve());\r\n return;\r\n }\r\n\r\n let errorReason: string;\r\n switch (result) {\r\n case -1: {\r\n errorReason = \"Failed to start audio callback\";\r\n break;\r\n }\r\n case -2: {\r\n errorReason = \"No audio callback registered\";\r\n break;\r\n }\r\n default: {\r\n errorReason = `Unexpected result starting audio callback ${result}`;\r\n }\r\n }\r\n reject(new Error(errorReason));\r\n });\r\n };\r\n\r\n /**\r\n * Stops the audio stream to the audio callback.\r\n * @returns {Promise<void>} Resolves when stopped, rejects with Error if failed.\r\n */\r\n public stopAudioCallback = (): Promise<void> => {\r\n return new Promise((resolve, reject) => {\r\n const result = libVM.VBVMR_AudioCallbackStop();\r\n if (result === 0) {\r\n this.awaitAudioCallbackEvents.stop.push(() => resolve());\r\n return;\r\n }\r\n\r\n let errorReason: string;\r\n switch (result) {\r\n case -1: {\r\n errorReason = \"Failed to stop audio callback\";\r\n break;\r\n }\r\n case -2: {\r\n errorReason = \"No audio callback registered\";\r\n break;\r\n }\r\n default: {\r\n errorReason = `Unexpected result stopping audio callback: ${result}`;\r\n break;\r\n }\r\n }\r\n reject(errorReason);\r\n });\r\n };\r\n\r\n /**\r\n * Unregisters the audio callback.\r\n * @description Internally voicemeeter automatically calls stopAudioCallback(), so it's not strictly necessary to stop and then unregister.\r\n * @param {AudioCallbackModes} mode - The audio callback type. See {@link AudioCallbackModes}\r\n * @returns {Promise<void>} Resolves when unregistered, rejects with Error if failed.\r\n */\r\n public unregisterAudioCallback = (mode: AudioCallbackModes): Promise<void> => {\r\n return new Promise((resolve, reject) => {\r\n if (this.audioCallbackStates[mode].pointer === null) {\r\n reject(new Error(`No audio callback registered for \"${mode}\"`));\r\n return;\r\n }\r\n\r\n const result = libVM.VBVMR_AudioCallbackUnregister(this.audioCallbackStates[mode].pointer);\r\n switch (result) {\r\n case 0: {\r\n // It is not safe to unregister when the callback hasn't ended, wait for ENDING first\r\n if (this.audioCallbackStates[mode].ended) {\r\n koffi.unregister(this.audioCallbackStates[mode].pointer);\r\n this.audioCallbackStates[mode].pointer = null;\r\n resolve();\r\n } else {\r\n // Wait for ENDING first\r\n this.audioCallbackStates[mode].awaitUnregister.push(() => resolve());\r\n }\r\n return;\r\n }\r\n case -1: {\r\n reject(new Error(\"Failed to unregister audio callback\"));\r\n break;\r\n }\r\n case -2: {\r\n // -2 means the callback has already been unregistered, this case shouldn't occur\r\n this.audioCallbackStates[mode].pointer = null;\r\n resolve();\r\n break;\r\n }\r\n default: {\r\n reject(new Error(`Unexpected result unregistering audio callback ${result}`));\r\n }\r\n }\r\n });\r\n };\r\n\r\n /**\r\n * Unregisters all registered audio callbacks.\r\n * @returns {Promise<void[]>} Resolves when all callbacks are unregistered.\r\n */\r\n public unregisterAllAudioCallbacks = (): Promise<void[]> => {\r\n const promises: Array<Promise<void>> = [];\r\n for (const [mode, state] of Object.entries(this.audioCallbackStates)) {\r\n if (state.pointer === null) {\r\n continue;\r\n }\r\n promises.push(this.unregisterAudioCallback(<AudioCallbackModes>(<unknown>mode)));\r\n }\r\n return Promise.all(promises);\r\n };\r\n\r\n /**\r\n * Resolves all pending promises for a given audio callback event type.\r\n * @param {\"start\"|\"stop\"} type - The event type to resolve ('start' or 'stop').\r\n */\r\n private resolveAudioCallbackEvent(type: \"start\" | \"stop\"): void {\r\n while (this.awaitAudioCallbackEvents[type].length > 0) {\r\n const resolve = this.awaitAudioCallbackEvents[type].shift();\r\n if (resolve !== undefined) {\r\n resolve();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Converts raw callback data to an AudioCallbackInfo object.\r\n * @param {unknown} lpData - Raw data pointer from Voicemeeter.\r\n * @returns {AudioCallbackInfo} Decoded audio callback info.\r\n */\r\n private convertToAudioCallbackInfo = (lpData: unknown): AudioCallbackInfo => {\r\n const rawData = koffi.decode(lpData, AudioInfoStruct) as VBVMR_T_AUDIOINFO;\r\n return {\r\n sampleRate: rawData.samplerate,\r\n samplesPerFrame: rawData.nbSamplePerFrame,\r\n };\r\n };\r\n\r\n /**\r\n * Converts raw callback data to an AudioCallbackBuffer object.\r\n * @param {unknown} lpData - Raw data pointer from Voicemeeter.\r\n * @returns {AudioCallbackBuffer} Decoded audio buffer data.\r\n */\r\n private convertToAudioCallbackBuffer = (lpData: unknown): AudioCallbackBuffer => {\r\n const rawData = koffi.decode(lpData, AudioBufferStruct) as VBVMR_T_AUDIOBUFFER;\r\n const data: AudioCallbackBuffer = {\r\n sampleRate: rawData.audiobuffer_sr,\r\n samplesPerFrame: rawData.audiobuffer_nbs,\r\n inputChannelCount: rawData.audiobuffer_nbi,\r\n outputChannelCount: rawData.audiobuffer_nbo,\r\n inputChannels: [],\r\n outputChannels: [],\r\n };\r\n\r\n for (let i = 0; i < rawData.audiobuffer_nbi; i++) {\r\n data.inputChannels.push(new Float32Array(koffi.view(rawData.audiobuffer_r[i], rawData.audiobuffer_nbs * 4)));\r\n }\r\n for (let i = 0; i < rawData.audiobuffer_nbo; i++) {\r\n data.outputChannels.push(new Float32Array(koffi.view(rawData.audiobuffer_w[i], rawData.audiobuffer_nbs * 4)));\r\n }\r\n return data;\r\n };\r\n\r\n /**\r\n * Converts an unknown error value to an Error object.\r\n * @param {unknown} unknownError - An unknown error value.\r\n * @returns {Error} Converted Error object.\r\n */\r\n private convertToErrorObject = (unknownError: unknown): Error => {\r\n if (unknownError instanceof Error) {\r\n return unknownError;\r\n }\r\n if (typeof unknownError === \"string\") {\r\n return new Error(unknownError);\r\n }\r\n return new Error(`Unknown error: ${String(unknownError)}`);\r\n };\r\n\r\n /**\r\n * Checks whether properties has been changed and calls all event listeners\r\n */\r\n private checkPropertyChange = () => {\r\n let hasChanges = false;\r\n\r\n if (this.isParametersDirty() === 1) {\r\n hasChanges = true;\r\n }\r\n\r\n if (this.isMacroButtonDirty() === 1) {\r\n hasChanges = true;\r\n }\r\n\r\n if (hasChanges) {\r\n for (const eventListener of this.eventPool) {\r\n eventListener();\r\n }\r\n }\r\n };\r\n\r\n /**\r\n * Gets installed voicemeeter type.\r\n * Means Voicemeeter(normal,banana,potato)\r\n */\r\n private getVoicemeeterType = (): VoiceMeeterTypes => {\r\n const typePtr = [0];\r\n if (libVM.VBVMR_GetVoicemeeterType(typePtr) !== 0) {\r\n throw new Error(\"running failed\");\r\n }\r\n\r\n switch (typePtr[0]) {\r\n case 1: {\r\n // Voicemeeter\r\n return \"voicemeeter\";\r\n }\r\n case 2: {\r\n // Voicemeeter Banana\r\n return \"voicemeeterBanana\";\r\n }\r\n case 3: {\r\n // Voicemeeter Potato\r\n return \"voicemeeterPotato\";\r\n }\r\n default: {\r\n throw new Error(\"Voicemeeter seems not to be installed\");\r\n }\r\n }\r\n };\r\n\r\n /**\r\n * Returns the installed voicemeeter version\r\n */\r\n private getVoicemeeterVersion = () => {\r\n const versionPtr = [0];\r\n if (libVM.VBVMR_GetVoicemeeterVersion(versionPtr) !== 0) {\r\n throw new Error(\"running failed\");\r\n }\r\n // For info on this see: https://github.com/mirror/equalizerapo/blob/53d885f7f1a097b457e17a5206b7d60f647877a8/VoicemeeterClient/VoicemeeterRemote.h#L122-L135\r\n const version =\r\n `${(versionPtr[0] & 0xff_00_00_00) >> 24}` +\r\n `.${(versionPtr[0] & 0x00_ff_00_00) >> 16}` +\r\n `.${(versionPtr[0] & 0x00_00_ff_00) >> 8}` +\r\n `.${versionPtr[0] & 0x00_00_00_ff}`;\r\n return version;\r\n };\r\n\r\n /**\r\n * Gets a parameter of voicemeeter\r\n * @param {'Strip'|'Bus'} selector Strip or Bus\r\n * @param {number} index Number of strip or bus\r\n * @param {StripProperties|BusProperties} property Property which should be read\r\n */\r\n private getParameter = (selector: \"Strip\" | \"Bus\", index: number, property: StripProperties | BusProperties) => {\r\n const parameterName = `${selector}[${index}].${property}`;\r\n return this.getOption(parameterName);\r\n };\r\n\r\n /**\r\n * Sets a parameter of a bus or Strip\r\n * @param {'Strip'|'Bus'} selector\r\n * @param {number} index Number of strip or bus\r\n * @param {StripProperties|BusProperties} property Propertyname which should be changed\r\n * @param {any} value Property value\r\n */\r\n private setParameter = (\r\n selector: \"Strip\" | \"Bus\",\r\n index: number,\r\n property: StripProperties | BusProperties,\r\n value: any\r\n ): Promise<any> => {\r\n if (!this.isConnected) {\r\n throw new Error(\"Not connected \");\r\n }\r\n const scriptString = `${selector}[${index}].${property}=${value};`;\r\n return this.setOption(scriptString);\r\n };\r\n /**\r\n * Gets realtime audio level see the VoicemeeterRemote API: [VoicemeeterRemote.h GetLevel](https://github.com/mirror/equalizerapo/blob/7aece1b788fce5aa11873f3842a0d01f7c78454b/VoicemeeterClient/VoicemeeterRemote.h#L284),\r\n * for more details about the parameters\r\n * @param {0|1|2|3} type 0 = pre fader input levels. 1 = post fader input levels. 2= post Mute input levels. 3= output levels\r\n * @param channel audio channel zero based index\r\n * @returns {float} Current audio level\r\n */\r\n public getLevel = (type: 0 | 1 | 2 | 3, channel: number) => {\r\n const levelPtr = [0];\r\n libVM.VBVMR_GetLevel(type, channel, levelPtr);\r\n return levelPtr[0];\r\n };\r\n}\r\n","import * as constants from \"./lib/constants\";\r\n\r\nconst { BusProperties, InterfaceTypes, StripProperties, MacroButtonModes, AudioCallbackModes, AudioCallbackCommands } = constants;\r\n\r\nexport { AudioCallbackCommands, AudioCallbackModes, BusProperties, InterfaceTypes, MacroButtonModes, StripProperties };\r\n\r\nexport { default as Voicemeeter } from \"./lib/voicemeeter-connector\";\r\nexport * as types from \"./types/voicemeeter-types\";\r\n"],"names":["StripProperties","BusProperties","MacroButtonModes","InitialAudioCallbackState","pointer","awaitUnregister","ended","AudioCallbackModes","AudioCallbackCommands","AudioInfoStruct","koffi","struct","samplerate","nbSamplePerFrame","AudioBufferStruct","audiobuffer_sr","audiobuffer_nbs","audiobuffer_nbi","audiobuffer_nbo","audiobuffer_r","array","audiobuffer_w","strip","bus","_t","_","t","libVM","audioCallbackProtoPointer","instance","Voicemeeter","constructor","isInitialised","this","isConnected","outputDevices","inputDevices","version","type","eventPool","stringParameters","timerInterval","audioCallbackStates","MAIN","_extends","INPUT","OUTPUT","awaitAudioCallbackEvents","start","stop","connect","Error","success","message","code","loginResult","VBVMR_Login","getVoicemeeterType","getVoicemeeterVersion","setInterval","checkPropertyChange","undefined","disconnect","unregisterAllAudioCallbacks","catch","VBVMR_Logout","clearInterval","_unused","updateDeviceList","outputDeviceNumber","VBVMR_Output_GetDeviceNumber","i","hardwareIdPtr","Buffer","alloc","namePtr","typePtr","VBVMR_Output_GetDeviceDescA","push","name","toString","replaceAll","hardwareId","inputDeviceNumber","VBVMR_Input_GetDeviceNumber","VBVMR_Input_GetDeviceDescA","isParametersDirty","VBVMR_IsParametersDirty","getBusParameter","index","property","getParameter","getStripParameter","setStripParameter","value","setParameter","setBusParameter","attachChangeEvent","fn","getOption","parameterName","some","str","includes","strPtr","VBVMR_GetParameterStringA","String","fromCharCode","apply","filter","e","join","valuePtr","VBVMR_GetParameterFloat","setOption","option","script","length","fill","write","VBVMR_SetParameters","Promise","resolve","setTimeout","isMacroButtonDirty","VBVMR_MacroButton_IsDirty","getMacroButtonStatus","buttonIndex","bitmode","DEFAULT","VBVMR_MacroButton_GetStatus","setMacroButtonStatus","VBVMR_MacroButton_SetStatus","registerAudioCallback","mode","clientName","callback","config","_config$lpUser","register","lpUser","nCommand","lpData","nnn","audioCallbackEvent","STARTING","command","data","convertToAudioCallbackInfo","resolveAudioCallbackEvent","ENDING","CHANGE","_config$restartOnChan","restartOnChangedStream","startAudioCallback","unknownError","error","convertToErrorObject","BUFFER_IN","BUFFER_OUT","BUFFER_MAIN","convertToAudioCallbackBuffer","unregister","shift","clientNamePtr","result","VBVMR_AudioCallbackRegister","outClientName","replace","reject","VBVMR_AudioCallbackStart","errorReason","stopAudioCallback","VBVMR_AudioCallbackStop","unregisterAudioCallback","VBVMR_AudioCallbackUnregister","promises","state","Object","entries","all","rawData","decode","sampleRate","samplesPerFrame","inputChannelCount","outputChannelCount","inputChannels","outputChannels","Float32Array","view","hasChanges","eventListener","VBVMR_GetVoicemeeterType","versionPtr","VBVMR_GetVoicemeeterVersion","selector","getLevel","channel","levelPtr","VBVMR_GetLevel","$outputDevices","$inputDevices","$version","$type","init","async","dllPath","regKey","Registry","hive","HKLM","key","raw","values","err","items","unistallerPath","find","fileNameIndex","lastIndexOf","slice","DLLHandler","lib","load","func","VBVMR_RunVoicemeeter","proto","inputChannelCountMap","voicemeeter","voicemeeterBanana","voicemeeterPotato","outputChannelCountMap","InterfaceTypes","constants"],"mappings":"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